diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..e2e91d2c --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,70 @@ +#!/bin/sh +# OwnCord pre-commit hook — fast, staged-file-aware checks that mirror CI's +# cheapest gates so errors surface in seconds instead of a 15-minute CI run. +# +# Enable once per clone: npm run hooks:install (git config core.hooksPath .githooks) +# Bypass in an emergency: git commit --no-verify or OWNCORD_SKIP_HOOKS=1 +# CI still enforces everything — bypassing only defers the failure. + +[ "${OWNCORD_SKIP_HOOKS:-0}" = "1" ] && exit 0 + +repo_root=$(git rev-parse --show-toplevel) || exit 1 +cd "$repo_root" || exit 1 + +staged=$(git diff --cached --name-only --diff-filter=ACMR) +[ -z "$staged" ] && exit 0 + +fail() { + printf '\npre-commit: FAIL: %s\n(bypass with --no-verify; CI will still enforce this)\n' "$1" >&2 + exit 1 +} + +# ---------- Server (Go) ---------- +go_staged=$(printf '%s\n' "$staged" | grep '^Server/.*\.go$' | grep -v '^Server/db/dbgen/') +if [ -n "$go_staged" ]; then + if command -v go >/dev/null 2>&1; then + # shellcheck disable=SC2086 — repo paths contain no spaces + unformatted=$(gofmt -l $go_staged) + [ -n "$unformatted" ] && fail "gofmt needed (run gofmt -w on): $unformatted" + (cd Server && go vet ./...) || fail "go vet" + else + printf 'pre-commit: WARNING: go not installed; skipping Go checks.\n' >&2 + fi +fi + +# sqlc inputs changed -> regenerated db/dbgen must be part of the same commit. +if printf '%s\n' "$staged" | grep -qE '^Server/(db/queries/|migrations/|sqlc\.yaml|sqlc\.version)'; then + if command -v sqlc >/dev/null 2>&1; then + (cd Server && make sqlc-verify) \ + || fail "db/dbgen is stale — run 'make sqlc-generate' in Server/ and stage the result" + else + printf 'pre-commit: WARNING: sqlc not installed (make sqlc-install); CI will run sqlc-verify.\n' >&2 + fi +fi + +# Protocol schema changed -> regenerated Go + TS constants must be in the same commit. +if printf '%s\n' "$staged" | grep -qE '^(docs/protocol-schema\.json|Server/scripts/genprotocol/)'; then + if command -v go >/dev/null 2>&1; then + (cd Server && make protocol-verify) \ + || fail "protocol constants are stale — run 'make protocol-generate' in Server/ and stage the result" + fi +fi + +# ---------- Client (TypeScript) ---------- +ts_staged=$(printf '%s\n' "$staged" | grep -E '^Client/tauri-client/(src|tests)/.*\.ts$' | grep -v '/generated/') +if [ -n "$ts_staged" ]; then + if [ ! -d Client/tauri-client/node_modules ]; then + printf 'pre-commit: WARNING: node_modules missing in Client/tauri-client; skipping client checks (run npm install there).\n' >&2 + else + rel=$(printf '%s\n' "$ts_staged" | sed 's|^Client/tauri-client/||') + cd Client/tauri-client || exit 1 + # shellcheck disable=SC2086 + npx oxlint $rel || fail "oxlint" + # shellcheck disable=SC2086 + npx prettier --check $rel || fail "prettier (run: npm run format)" + npm run -s typecheck || fail "tsc --noEmit" + cd "$repo_root" || exit 1 + fi +fi + +exit 0 diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 00000000..e6f668c7 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,61 @@ +#!/bin/sh +# OwnCord pre-push hook — build-level gates that mirror CI's expensive-but-common +# failure modes (tag-variant compiles, typecheck, type-aware lint). +# +# Bypass: git push --no-verify or OWNCORD_SKIP_HOOKS=1 +# Extra: OWNCORD_PREPUSH_TESTS=1 additionally runs the server test suite (-race). + +[ "${OWNCORD_SKIP_HOOKS:-0}" = "1" ] && exit 0 + +repo_root=$(git rev-parse --show-toplevel) || exit 1 +cd "$repo_root" || exit 1 + +fail() { + printf '\npre-push: FAIL: %s\n(bypass with --no-verify; CI will still enforce this)\n' "$1" >&2 + exit 1 +} + +# What changed relative to origin/main decides which side's gates run. +# Markdown/docs changes never trigger builds. +changed=$(git diff --name-only origin/main...HEAD 2>/dev/null) || changed="__all__" +[ "$changed" = "__all__" ] || changed=$(printf '%s\n' "$changed" | grep -v '\.md$') +[ -z "$changed" ] && exit 0 + +server_changed=0 +client_changed=0 +if [ "$changed" = "__all__" ]; then + server_changed=1 + client_changed=1 +else + if printf '%s\n' "$changed" | grep -q '^Server/'; then server_changed=1; fi + if printf '%s\n' "$changed" | grep -q '^Client/tauri-client/'; then client_changed=1; fi + if printf '%s\n' "$changed" | grep -q '^docs/protocol-schema\.json'; then + server_changed=1 + client_changed=1 + fi +fi + +if [ "$server_changed" = 1 ] && command -v go >/dev/null 2>&1; then + echo "pre-push: server build (all tag variants)..." + (cd Server \ + && go build ./... \ + && go build -tags otel ./... \ + && go build -tags wazero ./... \ + && go build -tags otel,wazero ./...) || fail "server build (a tag variant failed to compile)" + if [ "${OWNCORD_PREPUSH_TESTS:-0}" = "1" ]; then + echo "pre-push: server tests (-race)..." + (cd Server && go test -race ./...) || fail "server tests" + fi +fi + +if [ "$client_changed" = 1 ]; then + if [ -d Client/tauri-client/node_modules ]; then + echo "pre-push: client typecheck + eslint..." + (cd Client/tauri-client && npm run -s typecheck) || fail "tsc --noEmit" + (cd Client/tauri-client && npx eslint src/) || fail "eslint" + else + printf 'pre-push: WARNING: node_modules missing in Client/tauri-client; skipping client checks.\n' >&2 + fi +fi + +exit 0 diff --git a/.gitignore b/.gitignore index 4dcdb234..e99aa527 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,14 @@ .env Server/.env -# Claude Code local config -.claude/ +# Claude Code — shared project config (CLAUDE.md, .claude/settings.json, +# .claude/skills/, .claude/hooks/, .mcp.json) is committed; local-only +# state and overrides are not. +.claude/settings.local.json +.claude/*.local.json +CLAUDE.local.md Client/.claude/ Server/.claude/ -CLAUDE.md # GitHub Copilot instructions (internal) .github/copilot-instructions.md @@ -21,7 +24,7 @@ docs/CODEMAPS/ docs/designs/ docs/research/ docs/superpowers/ -skills/ +/skills/ # Server runtime artifacts Server/chatserver.exe @@ -58,17 +61,15 @@ node_modules/ # AI tooling .gstack/ .claude-flow/ -.mcp.json .superpowers/ # Internal dev tools tools/ .cache/ -# Internal dev files -SKILL.md +# Internal dev files (root-level scratch only; .claude/skills/ and CLAUDE.md are committed) +/SKILL.md TODOS.md -CLAUDE.md DESIGN.md Client/CLIENT-REVIEW.md diff --git a/docs/contributing.md b/docs/contributing.md index 07e0198d..b47c31d0 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -83,6 +83,21 @@ How to set up the development environment and contribute to OwnCord. | `npm run format:check` | Prettier check only (no writes) | | `npm run knip` | Dead code and unused export detection | +### Git hooks (recommended) + +Committed hooks in `.githooks/` catch the most common CI failures locally. Enable once per clone (from the repo root): + +```bash +npm run hooks:install # = git config core.hooksPath .githooks +``` + +| Hook | What it runs | +|------|--------------| +| `pre-commit` | gofmt + `go vet` (when Go files staged), oxlint + prettier + `tsc --noEmit` (when client TS staged), `sqlc-verify` / `protocol-verify` (when their inputs staged) | +| `pre-push` | Server build in all build-tag variants, client typecheck + type-aware ESLint. Set `OWNCORD_PREPUSH_TESTS=1` to also run `go test -race ./...` | + +Bypass with `--no-verify` or `OWNCORD_SKIP_HOOKS=1` when needed — CI still enforces everything. + ## Plugin Development Plugins are WASM modules loaded at runtime when the server is built with `-tags wazero`. diff --git a/package.json b/package.json index e650fc71..e02ad0b3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "private": true, "scripts": { "changelog": "changelogen", - "release": "changelogen --release" + "release": "changelogen --release", + "hooks:install": "git config core.hooksPath .githooks" }, "devDependencies": { "changelogen": "^0.6.2"