diff --git a/.codex/bootstrap/tests-docs.v1.json b/.codex/bootstrap/tests-docs.v1.json new file mode 100644 index 0000000..e8ebf8c --- /dev/null +++ b/.codex/bootstrap/tests-docs.v1.json @@ -0,0 +1,8 @@ +{ + "contract": "tests-docs-bootstrap-v1", + "adapter": "node-ts", + "branch": "codex/bootstrap-tests-docs-v1", + "generated_at": "2026-02-17T05:42:34.624Z", + "generated_by": "/Users/d/.codex/scripts/bootstrap/global_tests_docs_bootstrap.mjs", + "changed_files": [] +} diff --git a/.codex/prompts/test-critic.md b/.codex/prompts/test-critic.md new file mode 100644 index 0000000..6c29c8c --- /dev/null +++ b/.codex/prompts/test-critic.md @@ -0,0 +1,14 @@ +You are a QA Test Critic reviewing only changed files and related tests. + +Review criteria: +1. Tests assert behavior outcomes, not implementation details. +2. Each changed behavior includes edge/error/boundary coverage. +3. Mocks are used only at external boundaries. +4. UI tests cover loading/empty/error/success and disabled/focus-visible states. +5. Assertions would fail under realistic regressions. +6. Flag brittle selectors, snapshot spam, and tautological assertions. +7. Flag missing docs updates for API/command or architecture changes. + +Output: +- Emit ReviewFindingV1 findings only. +- Priority order: critical, high, medium, low. diff --git a/.codex/scripts/run_verify_commands.sh b/.codex/scripts/run_verify_commands.sh new file mode 100755 index 0000000..aef497b --- /dev/null +++ b/.codex/scripts/run_verify_commands.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERIFY_FILE="${1:-.codex/verify.commands}" +if [[ ! -f "$VERIFY_FILE" ]]; then + echo "missing verify commands file: $VERIFY_FILE" >&2 + exit 1 +fi + +failed=0 +while IFS= read -r cmd || [[ -n "$cmd" ]]; do + [[ -z "$cmd" ]] && continue + [[ "$cmd" =~ ^# ]] && continue + echo ">>> $cmd" + if ! bash -lc "$cmd"; then + failed=1 + break + fi +done < "$VERIFY_FILE" + +exit "$failed" diff --git a/.codex/verify.commands b/.codex/verify.commands new file mode 100644 index 0000000..4d72142 --- /dev/null +++ b/.codex/verify.commands @@ -0,0 +1,7 @@ +pnpm lint +pnpm typecheck +pnpm test:coverage +pnpm test:integration +pnpm test:e2e:smoke +pnpm docs:generate +pnpm docs:check diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml new file mode 100644 index 0000000..0dd204e --- /dev/null +++ b/.github/workflows/quality-gates.yml @@ -0,0 +1,46 @@ +name: quality-gates + +on: + pull_request: + push: + branches: [main, master] + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Policy checks + run: node scripts/ci/require-tests-and-docs.mjs + + - name: Verify commands + run: bash .codex/scripts/run_verify_commands.sh + + - name: Diff coverage + run: | + python -m pip install --upgrade pip diff-cover + diff-cover coverage/lcov.info --compare-branch=origin/main --fail-under=90 + + - name: Upload test artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts + path: | + playwright-report/ + test-results/ + coverage/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4bdead5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +## Definition of Done: Tests + Docs (Blocking) + +- Any production code change must include meaningful test updates in the same PR. +- Meaningful tests must include at least: + - one primary behavior assertion + - two non-happy-path assertions (edge, boundary, invalid input, or failure mode) +- Trivial assertions are forbidden (`expect(true).toBe(true)`, snapshot-only without semantic assertions, render-only smoke tests without behavior checks). +- Mock only external boundaries (network, clock, randomness, third-party SDKs). Do not mock the unit under test. +- UI changes must cover state matrix: loading, empty, error, success, disabled, focus-visible. +- API/command surface changes must update generated contract artifacts and request/response examples. +- Architecture-impacting changes must include an ADR in `/docs/adr/`. +- Required checks are blocking when `fail` or `not-run`: lint, typecheck, tests, coverage, diff coverage, docs check. +- Reviewer -> fixer -> reviewer loop is required before merge. diff --git a/README.md b/README.md index a437270..6935867 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,48 @@ npm install # Run in development mode npm run tauri dev +# Run in low-disk lean development mode +npm run dev:lean + # Build for production npm run tauri build ``` +### Normal Dev vs Lean Dev + +- **Normal dev (`npm run tauri dev`)** + - Fastest repeated startup once Rust and frontend caches are warm. + - Uses persistent local build artifacts (for example `src-tauri/target`, Vite cache in `node_modules/.vite`). +- **Lean dev (`npm run dev:lean`)** + - Runs the same Tauri dev flow, but moves heavy build caches to temporary locations. + - Automatically removes heavy build artifacts when you exit. + - Uses less persistent disk space, but startup/rebuild can be slower. + +### Cleanup Commands + +- **Targeted heavy cleanup** (keeps dependencies for speed): + +```bash +npm run clean:heavy +``` + +Removes only heavy build artifacts: +- `dist` +- `src-tauri/target` +- `node_modules/.vite` + +- **Full local reproducible cleanup** (maximum space recovery): + +```bash +npm run clean:full +``` + +Removes: +- `dist` +- `src-tauri/target` +- `node_modules/.vite` +- `node_modules` (recreated by `npm install`) + ### First Connection 1. Click "Add" in the sidebar to create a connection diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md new file mode 100644 index 0000000..f48132d --- /dev/null +++ b/docs/adr/0000-template.md @@ -0,0 +1,16 @@ +# 0000. Title + +## Status +Proposed | Accepted | Superseded + +## Context +What problem or constraint forced this decision? + +## Decision +What was chosen? + +## Consequences +What improves, what tradeoffs are accepted, what risks remain? + +## Alternatives Considered +Option A, Option B, and why they were rejected. diff --git a/openapi/openapi.generated.json b/openapi/openapi.generated.json new file mode 100644 index 0000000..3e752f6 --- /dev/null +++ b/openapi/openapi.generated.json @@ -0,0 +1,9 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "API Contract", + "version": "1.0.0" + }, + "paths": {}, + "components": {} +} diff --git a/package.json b/package.json index 86ea2fd..c25efa9 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,15 @@ "type": "module", "scripts": { "dev": "vite", + "dev:lean": "./scripts/lean-dev.sh", "build": "tsc && vite build", "preview": "vite preview", "lint": "tsc --noEmit", "lint:fix": "eslint . --fix && prettier --write .", "format": "prettier --write .", - "tauri": "tauri" + "tauri": "tauri", + "clean:heavy": "./scripts/cleanup-heavy.sh", + "clean:full": "./scripts/cleanup-full.sh" }, "dependencies": { "@dagrejs/dagre": "^2.0.3", diff --git a/scripts/ci/require-tests-and-docs.mjs b/scripts/ci/require-tests-and-docs.mjs new file mode 100644 index 0000000..6509f6a --- /dev/null +++ b/scripts/ci/require-tests-and-docs.mjs @@ -0,0 +1,41 @@ +import { execSync } from 'node:child_process'; + +const defaultBaseRef = (() => { + try { + return execSync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' }).trim().replace('refs/remotes/', ''); + } catch { + return 'origin/main'; + } +})(); + +const baseRef = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : defaultBaseRef; +const diff = execSync(`git diff --name-only ${baseRef}...HEAD`, { encoding: 'utf8' }) + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); + +const isProdCode = (file) => /^(src|app|server|api)\//.test(file) && !/\.(test|spec)\.[cm]?[jt]sx?$/.test(file); +const isTest = (file) => /^tests\//.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file); +const isDoc = (file) => /^docs\//.test(file) || /^openapi\//.test(file) || file === 'README.md'; +const isApiSurface = (file) => /^(src|app|server|api)\/.*(route|controller|handler|webhook|api|command)/.test(file); +const isArchChange = (file) => /^src\/(auth|db|infra|queue|events|architecture)\//.test(file) || /^infra\//.test(file); +const isAdr = (file) => /^docs\/adr\/\d{4}-.*\.md$/.test(file); + +const prodChanged = diff.some(isProdCode); +const testsChanged = diff.some(isTest); +const apiChanged = diff.some(isApiSurface); +const docsChanged = diff.some(isDoc); +const archChanged = diff.some(isArchChange); +const adrChanged = diff.some(isAdr); + +const failures = []; +if (prodChanged && !testsChanged) failures.push('Policy failure: production code changed without test updates.'); +if (apiChanged && !docsChanged) failures.push('Policy failure: API/command changes without docs/OpenAPI updates.'); +if (archChanged && !adrChanged) failures.push('Policy failure: architecture-impacting change without ADR.'); + +if (failures.length > 0) { + for (const failure of failures) console.error(failure); + process.exit(1); +} + +console.log('Policy checks passed.'); diff --git a/scripts/cleanup-full.sh b/scripts/cleanup-full.sh new file mode 100755 index 0000000..6c71c25 --- /dev/null +++ b/scripts/cleanup-full.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +cd "$REPO_ROOT" + +paths=( + "dist" + "src-tauri/target" + "node_modules/.vite" + "node_modules" +) + +for path in "${paths[@]}"; do + if [ -e "$path" ]; then + rm -rf "$path" + echo "removed $path" + fi +done diff --git a/scripts/cleanup-heavy.sh b/scripts/cleanup-heavy.sh new file mode 100755 index 0000000..82aeeec --- /dev/null +++ b/scripts/cleanup-heavy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +cd "$REPO_ROOT" + +paths=( + "dist" + "src-tauri/target" + "node_modules/.vite" +) + +for path in "${paths[@]}"; do + if [ -e "$path" ]; then + rm -rf "$path" + echo "removed $path" + fi +done diff --git a/scripts/lean-dev.sh b/scripts/lean-dev.sh new file mode 100755 index 0000000..9d84140 --- /dev/null +++ b/scripts/lean-dev.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +cd "$REPO_ROOT" + +LEAN_TMP_ROOT="$(mktemp -d -t dbviz-lean-dev-XXXXXX)" +LEAN_CARGO_TARGET_DIR="$LEAN_TMP_ROOT/cargo-target" +LEAN_VITE_CACHE_DIR="$LEAN_TMP_ROOT/vite-cache" +LEAN_DEV_PORT="${LEAN_DEV_PORT:-1420}" + +mkdir -p "$LEAN_CARGO_TARGET_DIR" "$LEAN_VITE_CACHE_DIR" + +cleanup() { + local exit_code=$? + + if [ -d "$LEAN_TMP_ROOT" ]; then + rm -rf "$LEAN_TMP_ROOT" + fi + + npm run clean:heavy >/dev/null 2>&1 || true + + exit "$exit_code" +} +trap cleanup EXIT INT TERM + +export CARGO_TARGET_DIR="$LEAN_CARGO_TARGET_DIR" +export VITE_CACHE_DIR="$LEAN_VITE_CACHE_DIR" +export TAURI_DEV_PORT="$LEAN_DEV_PORT" + +echo "[lean-dev] temporary cargo target: $CARGO_TARGET_DIR" +echo "[lean-dev] temporary vite cache: $VITE_CACHE_DIR" +echo "[lean-dev] tauri dev port: $TAURI_DEV_PORT" + +npm run tauri dev -- --port "$TAURI_DEV_PORT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4c378b3..a8616df 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -4,7 +4,7 @@ "version": "0.1.0", "identifier": "com.dbviz.app", "build": { - "beforeDevCommand": "npm run dev", + "beforeDevCommand": "npm run dev -- --port ${TAURI_DEV_PORT:-1420}", "devUrl": "http://localhost:1420", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" diff --git a/vite.config.ts b/vite.config.ts index 2f56295..eeae9fe 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,12 @@ import { resolve } from "path"; // @ts-expect-error process is a nodejs global const host = process.env.TAURI_DEV_HOST; +// @ts-expect-error process is a nodejs global +const envPort = Number(process.env.TAURI_DEV_PORT || process.env.PORT || "1420"); +const port = Number.isFinite(envPort) ? envPort : 1420; +const hmrPort = port + 1; +// @ts-expect-error process is a nodejs global +const cacheDir = process.env.VITE_CACHE_DIR || "node_modules/.vite"; export default defineConfig(async () => ({ plugins: [react(), tailwindcss()], @@ -14,15 +20,16 @@ export default defineConfig(async () => ({ }, }, clearScreen: false, + cacheDir, server: { - port: 1420, + port, strictPort: true, host: host || false, hmr: host ? { protocol: "ws", host, - port: 1421, + port: hmrPort, } : undefined, watch: {