# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Trunk The trunk branch is **`master`** (GitHub default branch for `AlessioBrillo/dominus`). The `github-workflow` skill aligns to this. A future rename to `main` is a deliberate, separate change. ## Project DOMINUS is an open-source domain investment decision-support tool, available as a self-hosted community edition (AGPL v3) and a managed cloud service (DOMINUS Cloud). The community edition is single-user by default, zero-cost on APIs, and runs at €0 infrastructure cost. DOMINUS Cloud adds multi-tenancy, managed PostgreSQL, team accounts, and priority support. The core asset is the **scoring engine**: a heuristic valuator that outputs `expected_value`, `confidence`, `suggested_buy_max`, and `suggested_list_price` per domain candidate. The engine must be more conservative than commercial appraisal tools, not more generous. See [ADR-0001](docs/adr/0001-project-architecture.md) for original architecture decisions (superseded by ADR-0026, ADR-0027 for the SaaS era). ## Current stack | Layer | Technology | | ------------------ | -------------------------------------------------------------- | | **Backend** | Node.js 22+, Express 5 | | **Database** | SQLite (community) / PostgreSQL (cloud) | | **CLI** | Commander (23 commands) | | **API** | Express REST (34 route modules) | | **Frontend** | React 19 + Vite 8 + Tailwind CSS 4 + Recharts + TanStack Table | | **Trademark** | USPTO public API (no key) + EUIPO OAuth2 | | **Infrastructure** | Docker, Docker Compose, Terraform (Hetzner), GitHub Actions | See [ADR-0001](docs/adr/0001-project-architecture.md) for the original rationale behind the technology choices. ADR-0025 through ADR-0028 document the SaaS transition. ## Pipeline architecture Five-stage pipeline with async-first execution — acquisition and auto-listing are manual and out of scope: 1. **Candidate generation** — keyword combos, brandable names, closeout CSVs imported manually 2. **DNS pre-filter** — fast bulk check via Unbound recursive resolver (DNSSEC-validated; Node.js DNS fallback for non-Available verdicts only; never reports Available without DNSSEC validation) 3. **RDAP confirmation** — precise availability status + premium detection via public RDAP 4. **Scoring** — heuristic engine (see below) 5. **Trademark gate** — mandatory USPTO/EUIPO check; any match blocks the candidate Pipeline runs are **async by default**: the CLI and API enqueue jobs to the `job_queue` table and return immediately. The in-process `JobWorker` polls for pending jobs and executes handlers asynchronously (configurable via `WORKER_ENABLED`, default `true`). Callers can use `--sync` on the CLI, `dominus runs wait ` for polling, or the `/api/v1/runs/:id/job` endpoint for API status. The `PipelineRunService` exposes both `runSync()` and `enqueueRun()` paths; the legacy synchronous path is maintained for backward compatibility. Plus a **portfolio tracker**: renewal clock per domain, monthly keep/drop verdict. All five stages are implemented and running. See [ADR-0003](docs/adr/0003-pipeline-stage-separation.md) for the architectural design. ## Scoring engine signals - **Intrinsic**: length, pronounceability, hyphens/numbers (penalty), TLD - **Commercial**: keyword search volume × CPC from Google Keyword Planner - **Market**: comparables from NameBio sales for similar names - **For expired/closeout domains**: domain age, backlinks, Wayback history. Wayback is fetched automatically (CDX provider); age and backlinks come from the imported closeout CSV — there is no backlink provider. Weights are tuned manually against real comparable sales. ML is out of scope at this scale — heuristic only. See [ADR-0002](docs/adr/0002-scoring-engine-design.md) for the conservatism principle and [ADR-0008](docs/adr/0008-backtest-engine.md) for the backtest-driven tuning loop. ## Key design decisions - **Decision-first UX**: one question per candidate — _buy / pass_. One question per portfolio domain — _keep / drop / reprice_. - **Trademark gate is non-negotiable** — it runs on every candidate before any buy recommendation. See [ADR-0006](docs/adr/0006-trademark-gate-mandate.md). - **Provider abstraction is non-negotiable** — never hardcode a specific API client into core logic. See [ADR-0004](docs/adr/0004-provider-abstraction-pattern.md). - **Cost discipline**: no paid API is required for the community edition. Every feature runs at €0 infra cost. See [ADR-0001](docs/adr/0001-project-architecture.md). - **Community-first**: the AGPL community edition has every feature that DOMINUS Cloud has. Monetisation is on managed infrastructure, not feature gating. See [ADR-0025](docs/adr/0025-license-change-agpl-commercial.md). - **Renewal clock matters more than acquisition volume**: a domain that doesn't sell is a recurring liability. Drop logic is a first-class feature. - **Zero lock-in**: migrate from DOMINUS Cloud to self-hosted with a single database dump. The community edition reads the same schema. ## Production operations - **Backup**: Automatic daily backup via scheduler (`SCHEDULER_BACKUP_CRON`, default: 04:00). Manual backup: `dominus maintenance backup`. See [ADR-0022](docs/adr/0022-backup-and-operations.md). - **Database maintenance**: `dominus maintenance vacuum` runs integrity check + VACUUM. - **Pruning**: `dominus maintenance prune` removes expired cache/runs data. - **Local CI**: Pre-push hook runs full quality gate (typecheck, build, lint, format, test) before every push. No GitHub Actions minutes required. ## Project status DOMINUS v1.5.2 — the artifact tells the truth: `dominus doctor` reports its own version, database state, backup age and disk space on any installation, fresh or existing. `npm run launch:check` judges a community deployment and a Cloud deployment with the same implementation (profiles, auto-detection, `--paid`), writes JSON and Markdown evidence on pass and on fail, and checks the things that break silently in public: the canonical origin, TLS, HSTS/CSP, HTML/JSON parity, robots → sitemap and the live trademark canary (ADR-0100). `npm run launch:rehearse` builds the production-parity Cloud stack, runs the gate against it and writes the evidence, with no credentials and no spend. The community edition is the first soft launch; the Cloud stays behind the fiscal position, the third-party audit ([audit-scope.md](docs/security/audit-scope.md)) and a real staging deployment ([first-apply.md](docs/deployment/first-apply.md)). Before it: the verified install channel, the legal surface, the data-subject rights and a release state checked against the world. v1.0.0 introduced the DOMINUS Cloud MVP. Next: execute the first staging apply, the audit, and the fiscal position. See the [ADR series](docs/adr/README.md) for the full architecture documentation, and [ROADMAP.md](ROADMAP.md) for planned releases. Resolved design decisions: 1. **Starting segment: economic closeouts first** — Stage 1 imports closeout CSVs (`run --closeout-csv`), carrying age/backlinks/wayback into the expiry signal. See [ADR-0003](docs/adr/0003-pipeline-stage-separation.md). 2. **Interface: CLI + Dashboard** — the React dashboard is the primary UI for DOMINUS Cloud; the CLI remains fully functional for automation. See [ADR-0028](docs/adr/0028-frontend-architecture-professional-dashboard.md). 3. **Registrar**: manual purchases by default. The [RegistrarProvider] interface has one verified production implementation, Cloudflare; Dynadot, Porkbun and Namecheap providers are registered but marked experimental (unverified against vendor APIs — see ADR-0082). Use `REGISTRAR_PROVIDER=manual` (default) unless you have verified credentials. See [ADR-0004](docs/adr/0004-provider-abstraction-pattern.md). 4. **Drop policy** — defaults live in config (`DROP_SCORE_THRESHOLD`, `DROP_RENEWAL_HORIZON_DAYS`); thresholds are tuned via backtest feedback. 5. **Async-default execution** — pipeline runs enqueue to job_queue by default; call `--sync` for immediate synchronous execution. The worker is enabled by default (`WORKER_ENABLED=true`). See [ADR-0023](docs/adr/0023-job-queue-worker-pool-architecture.md). 6. **License**: AGPL v3 + commercial option. Community edition is free forever. See [ADR-0025](docs/adr/0025-license-change-agpl-commercial.md). 7. **SaaS model**: hosting-only monetisation — no feature gating. See [ADR-0026](docs/adr/0026-monetization-and-saas-model.md). 8. **Database abstraction**: SQLite for community, PostgreSQL for cloud. See [ADR-0027](docs/adr/0027-saas-architecture-multi-tenant.md). Providers default to free/manual data: `KeywordProvider` reads an optional local file (`KEYWORD_DATA_PATH`) or uses Google Suggest; `CompsProvider` reads an optional local file (`COMPS_DATA_PATH`). Google Ads (keywords) and NameBio (comparables) are optional API implementations. Adding a provider means one new implementation file — no core logic changes. Providers that cannot be verified against the real service are not shipped (see [ADR-0082](docs/adr/0082-removal-of-unimplemented-features.md)). See [ADR-0004](docs/adr/0004-provider-abstraction-pattern.md). ## CI gate The authoritative gate is the Woodpecker pipeline (`.woodpecker/ci.yml`), run locally on the committer's machine with Docker via `ci-local` (or on a Woodpecker server when one is registered). GitHub Actions is a billing-blocked mirror: its workflows are disabled, its badge is absent, and it is not part of the merge gate. - **Local:** `ci-local` (runs `.woodpecker/ci.yml` on a throwaway copy of the tree, never the checkout). Exit 0 = green. Equivalent to `npm run gate`, plus PostgreSQL integration, frontend, pack/installer and e2e smoke jobs. - **The local gate (`npm run gate`)** runs typecheck → build → db:sync:check → lint → format:check → check:migration-compat → check:sql-portability → notices:check → adr:index:check → check:pack → release:verify --pack → test:installers → check:doc-drift → check:release-consistency → **check:quickstart** → check:workflows → check:legal-placeholders → backend tests with coverage → frontend typecheck/lint/test-with-coverage/build → the end-to-end smoke gate (`npm run test:e2e:smoke`). - **The quickstart gate:** `npm run check:quickstart` runs the command the README advertises, in a throwaway directory, and asserts the contract a reader is entitled to: exit 0, no log lines on stdout, the five numbers, nothing written to the disk, a time budget — and that the output documented in the README is the output the command produces. - **The release state:** `npm run release:doctor` compares the version `package.json` announces with the world: a clean tree, a tag for the version, the tag on the remote, and — with `--remote` — the package on npm and the image in the registry. A release that is announced but not published is a command that fails, not a discovery days later. - **End-to-end smoke:** the gate ends with a Playwright subset — auth, portfolio and the WCAG 2.1 AA gate — so the local gate cannot be green while the shipped SPA is broken. It needs the Chromium build once per machine: `npm run e2e:install`. The full suite stays in CI (`npm run test:e2e`). `SKIP_E2E_SMOKE=1` is the only escape hatch and it prints a warning in the log. - **With Docker:** `npm run gate:docker` additionally renders both compose stacks, lints Prometheus alert rules, and (when Docker is available) starts base+Unbound and prod-overlay to assert `/api/v1/health` on a fresh Postgres volume. Run this before any tag or any change to compose/Dockerfile/deploy/. - **Cloud acceptance:** `npm run acceptance:cloud` is the Cloud edition's own gate (ADR-0095). It is not part of `npm run gate`; run it before any Cloud deploy. - **Pre-push:** husky `pre-push` → `npm run gate`. Bypass only with an explicit `--no-verify`. - **Done / merge-ready** means the gate is green on the final tree. See `~/.agents/CI.md`. - **Secret scanning:** TruffleHog runs in CI over the pushed diff with verification enabled, so any credential-shaped URI in a tracked file is reported. Keep database URIs password-free in tracked files (`postgresql://@:5432/`); the password goes in a separate variable.