Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .agents/skills/ci-pipeline/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
name: ci-pipeline
description: Load when a GitHub Actions run fails, when a PR is blocked by DCO, PR-title validation or branch protection, or when changing workflows under `.github/workflows/`.
---

# CI Pipeline

Use this skill to diagnose and fix CI, and to get a PR into a mergeable state.

## Start

- Get the real state first: `gh pr checks <pr>` and `gh run view <id> --log-failed`.
Do not guess from the check name.
- Distinguish pre-existing red checks on `master` from breakage introduced by the PR:
`gh run list --branch master --workflow <file>`.
- Read `references/failure-playbook.md` for failures already diagnosed once.
- Read `references/routing-evals.md` only when changing this skill's routing.

## Workflows

| File | What it does |
| --- | --- |
| `build-test.yml` | `build_node` (client lint/format/build/typecheck) and `build_csharp` (dotnet format, build, test). Runs on push and PR to `master`. |
| `deploy.yml` | Builds and pushes `gomoku-server` and `gomoku-rapfi` images, builds the client, rsyncs to the VPS, runs `docker compose pull/up`, health-checks the API. Push to `master` plus `workflow_dispatch`. |
| `docker-integration-test.yml` | Docker-level integration checks. |
| `rapfi-test.yml` | Rapfi engine checks. |
| `contribution-guidelines-check.yml` | Includes `Validate PR Title`. |

## Merge Requirements

- **DCO**: every commit in the PR needs a `Signed-off-by` trailer. Use
`git commit --signoff`. Adding a new signed commit on top does **not** clear an
earlier unsigned commit — squash or amend the branch and force-push
(`git reset --soft origin/master && git commit --signoff && git push --force-with-lease`).
- **Validate PR Title**: the title must be `type(scope): summary` and the scope is
required. Allowed scopes: `client`, `server`, `fullstack`, `devops`.
- **Branch protection** on `master`: an approving review plus passing checks. Never
merge with `--admin` unless the user explicitly asks for it.

## Rules

- Fix the root cause of a red check; do not disable or skip the check to go green.
- When a fix cannot be reproduced locally, add a temporary debug step to the workflow,
run it via `workflow_dispatch`, read the output, then remove the debug step in the
same PR.
- Prefer `workflow_dispatch` for iterating on `deploy.yml` from a branch instead of
merging to `master` to test.
- Do not report a workflow as fixed until a full run is green.

## Gotchas

- A job that fails in 2–3 seconds usually failed before running anything — deprecated
action versions, a missing action, or a permission problem, not the project's code.
- A workflow that was failing instantly can hide *real* downstream failures. Expect new
red checks to appear right after fixing the trivial one.
- Local reproduction can differ from CI for environment reasons (tool versions resolved
differently, stale `node_modules`). Compare tool versions in CI before assuming the
code is at fault — see `client-build-conventions`.
85 changes: 85 additions & 0 deletions .agents/skills/ci-pipeline/references/failure-playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# CI Failure Playbook

Failures already diagnosed on this repo, with the fix that actually worked.

## `build_node` / `build_csharp` fail in ~2 seconds

**Symptom:** `This request has been automatically failed because it uses a deprecated
version of actions/upload-artifact: v3`.

**Cause:** GitHub hard-fails runs using deprecated artifact actions, before any project
step executes.

**Fix:** bump to `actions/upload-artifact@v4` (and `download-artifact@v4`).

**Note:** this masked every later failure in `build-test.yml`, which had been red on
`master` for months. Fixing it surfaced the real breakages below.

## Client build: `Option 'baseUrl' has been removed` / `moduleResolution=node10 has been removed`

**Symptom:** `tsc` rejects `tsconfig.json` options that are valid for the TypeScript
version pinned in `yarn.lock` (5.7.2). Cannot be reproduced locally.

**Cause:** `@gomoku/story` runs bare `tsc` in its `build` script but did not declare
`typescript` as its own dependency. On the runner, PATH resolution fell through to the
system-wide `/usr/local/bin/tsc` shipped in the GitHub image — a much newer compiler.

**Fix:** declare the tool in the workspace that invokes it
(`"typescript": "^5.7.2"` in `packages/gomoku-story/package.json`).

**Debug trick:** add a temporary step printing `which tsc`,
`node_modules/typescript/package.json` version and `yarn --version`, run it via
`workflow_dispatch`, then remove it.

## Client build resolves the wrong Yarn

**Symptom:** dependency resolution in CI differs from local, warnings about the
lockfile, unexpected package versions.

**Cause:** `corepack` is not enabled by default on runners, so `yarn` resolves to the
preinstalled Yarn Classic instead of the `packageManager`-pinned Yarn 4.5.0.

**Fix:** add a `corepack enable` step after `actions/setup-node` and before
`yarn install`.

**Note:** enabling corepack alone did not fix the TypeScript failure above — the two
issues looked identical from the check name but were independent.

## rapfi image fails to compile

**Symptom:** `static assertion failed: Failed to find a supported instruction set` in
`eval/mix10nnue.cpp` during `cmake --build`.

**Cause:** the Dockerfile disabled every SIMD instruction set; the mix10 NNUE code
requires at least one.

**Fix:** `-DUSE_SSE=ON`. SSE is available both on GitHub runners and on the target VPS;
AVX2/AVX512 are not safe to assume.

## rapfi container crash-loops after a successful build

**Symptom:** `SyntaxError: Unexpected token '.'` from `body-parser` at startup.

**Cause:** `apt-get install nodejs` on `ubuntu:22.04` installs Node 12, which predates
optional chaining used by the Express dependency tree.

**Fix:** install a modern runtime via NodeSource (`setup_20.x`) instead of the distro
package.

## Deployed client calls `http://localhost:62411`

**Symptom:** the production bundle points at a dev API host.

**Cause:** `vite build` defaults to mode `production` and therefore loads
`.env.production`, but the repo's production file is `envs/.env.prod` (the server's
`EnvironmentLoader` hardcodes that name). Vite silently fell back to `.env.local`,
which is loaded in every mode.

**Fix:** build with `vite build --mode prod` so Vite loads `envs/.env.prod`. Do not
rename the file — the .NET server reads it by name.

**Verification:** grep the built bundle, and the deployed one, for the expected host:

```bash
grep -o "api.gomoku.app" GomokuClient/packages/gomoku-core/dist/assets/index-*.js
```
21 changes: 21 additions & 0 deletions .agents/skills/ci-pipeline/references/routing-evals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# CI Pipeline Routing Evals

## Positive

- "The DCO check is failing on my PR."
- "Why did build_node fail?"
- "This PR can't be merged, the base branch policy blocks it."
- "Add a workflow that runs the client tests on PRs."
- "Validate PR Title is red."

## Negative

- "Deploy this branch to the VPS and verify the API." → `deploy-operations`
- "The client build picks the wrong env file." → `client-build-conventions`
- "Check that the board renders correctly after the deploy." → `live-playtest-qa`

## Forbidden

- Do not load this skill to silence or skip a failing check.
- Do not use it to justify merging with `--admin`; branch protection changes need
explicit user permission.
69 changes: 69 additions & 0 deletions .agents/skills/client-build-conventions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: client-build-conventions
description: Load when building or changing `GomokuClient` — Yarn 4 workspaces, Turborepo tasks, tooling dependencies, Vite environment wiring, or debugging why a client build behaves differently locally and in CI.
---

# Client Build Conventions

Use this skill for anything under `GomokuClient/`.

## Start

- `GomokuClient` is the workspace root. Run commands from there, not from the repo root.
- Read `packages/gomoku-core/vite.config.ts` before touching env or alias behaviour.
- Read `references/routing-evals.md` only when changing this skill's routing.

## Layout

| Package | Role |
| --- | --- |
| `@gomoku/core` | The deployed app: React 19, Vite 6, TanStack Router. Build output `packages/gomoku-core/dist` is what the deploy job ships. |
| `@gomoku/story` | Storybook component library, built before `core` (`turbo.json` declares `gomoku-core#build` depends on `gomoku-story#build`). |
| `@gomoku/api` | kubb-generated API client. Source-only: it has no `build` script, consumers compile it directly. |
| `@gomoku/eslint-config`, `@gomoku/tailwind-config` | Shared config packages. |

Package manager is pinned by `"packageManager": "yarn@4.5.0"`. Node is pinned to
`20.11.0` by `engines` and by the CI setup step.

## Environment Wiring

- Vite's `envDir` points at the repo-level `envs/` directory, not the package.
- `envs/.env.prod` is the production file, and the .NET server reads it **by that exact
name** (`EnvironmentLoader` maps `ASPNETCORE_ENVIRONMENT=production` → `.env.prod`).
Do not rename it to `.env.production` to please Vite.
- Because of that name, production client builds must pass the matching mode:
`vite build --mode prod`. Plain `vite build` runs in mode `production`, finds no
`.env.production`, and silently falls back to `.env.local` — which points
`VITE_API_URL` at `http://localhost:62411`.
- `.env.local` is loaded in every mode, so a missing mode file fails silently rather
than loudly.

## Rules

- Every workspace that invokes a CLI in its own scripts must declare that CLI as its
own dependency. Relying on hoisting works locally and breaks in CI, where a
system-wide binary on `PATH` can win instead.
- Enable corepack (`corepack enable`) before `yarn install` in any automation, so the
pinned Yarn 4 is used rather than a preinstalled Yarn Classic.
- Verify production builds by inspecting the bundle, not by trusting the config:

```bash
grep -o "api.gomoku.app" packages/gomoku-core/dist/assets/index-*.js
grep -o "localhost:62411" packages/gomoku-core/dist/assets/index-*.js
```

- Keep the deploy job's artifact path aligned with `packages/gomoku-core/dist`.

## Gotchas

- **Stale workspace symlinks.** If the repo was moved or re-cloned, `node_modules/@gomoku/*`
can still point at the old absolute path, producing `TS2307: Cannot find module
'@gomoku/api'` in every file. Fix by reinstalling, not by editing tsconfig:
`rm -rf node_modules packages/*/node_modules .yarn/cache && yarn install`.
- **`yarn workspace <pkg> exec tsc --version` is not a reliable probe** — it can resolve
a global binary. To check the version actually used, read
`node_modules/typescript/package.json` or run `node node_modules/typescript/lib/tsc.js --version`.
- `YN0066: ... Cannot apply hunk` for the built-in TypeScript compat patch is a warning
on a cold Yarn cache, not the cause of build failures.
- The root `build` script runs `yarn workspace @gomoku/story build` before
`turbo run build`; keep that ordering in mind when changing task graphs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Client Build Conventions Routing Evals

## Positive

- "The production bundle is calling localhost instead of the API."
- "Add a dependency to the storybook package."
- "`yarn build` fails with Cannot find module '@gomoku/api'."
- "Where does the deployed client build come from?"
- "Change how VITE_API_URL is wired."

## Negative

- "The deploy job can't reach the VPS." → `deploy-operations`
- "DCO is failing on my PR." → `ci-pipeline`
- "Check that quick pairing works on the live site." → `live-playtest-qa`

## Forbidden

- Do not rename `envs/.env.prod`; the .NET server reads that filename directly.
- Do not "fix" module resolution by loosening `tsconfig` when the real cause is a stale
`node_modules` or an undeclared tooling dependency.
78 changes: 78 additions & 0 deletions .agents/skills/deploy-operations/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
name: deploy-operations
description: Load when deploying gomoku.app or api.gomoku.app, editing `.github/workflows/deploy.yml`, `docker-compose.prod.yml` or `deploy/vps-setup.sh`, changing Docker/nginx/certbot state on the VPS, choosing a port on the shared VPS, or checking container and API health.
---

# Deploy Operations

Use this skill for anything that touches the live gomoku environment.

## Start

- Read `.github/workflows/deploy.yml` and `docker-compose.prod.yml` before changing
deployment behaviour.
- Read `references/vps-topology.md` for host layout, port blocks, and neighbours.
- Prefer the GitHub Actions deployment for reproducible changes. Use direct SSH for
inspection, emergency repair, or explicit user-authorised host work.
- Read `references/routing-evals.md` only when changing this skill's routing.

## Known Environment

- Client: `https://gomoku.app` → nginx static root `/home/aleksandrs/gomoku/client`.
- API: `https://api.gomoku.app` → nginx proxy → `127.0.0.1:7001` → `gomoku-server`
container port `8080`.
- AI: `rapfi` container, no published host port, reached as `http://rapfi:5005`.
- Compose files live in `/home/aleksandrs/gomoku/compose` (`docker-compose.yml` plus a
generated `.env` holding `DOCKERHUB_USERNAME`).
- Images: `<DOCKER_USERNAME>/gomoku-server:latest`, `<DOCKER_USERNAME>/gomoku-rapfi:latest`.
- Health: `curl https://api.gomoku.app/health` → `{"status":"Healthy"}`. The route is
header-versioned (`X-Version`), so a plain request works without extra headers.
- `/etc/nginx/conf.d/gomoku-upstream.conf` holds the `gomoku_api` upstream
(`keepalive 32`) and the `$connection_upgrade` map. `map`/`upstream` must live in the
http context, and `conf.d` is included before `sites-enabled`.
- One-time host setup lives in `deploy/vps-setup.sh` (Docker, nginx vhosts, certbot).
- CI secrets: `VPS_SSH_KEY`, `DOCKER_USERNAME`, `DOCKER_PASSWORD`. The old
`VERCEL_TOKEN` / `VERCEL_ORG_ID` / `VERCEL_PROJECT_ID` secrets are obsolete.

## Rules

- Port convention on this VPS: production services end in `1`, dev services end in `3`,
one numeric block per project. Gomoku owns the `7xxx` block; production is `7001`.
Check `references/vps-topology.md` before claiming a new port.
- Bind service ports to `127.0.0.1` in compose and let host nginx terminate TLS.
Do not publish container ports on `0.0.0.0`.
- Keep deploy verification hard-failing on the API health check. A deploy job must not
pass while the API or a container is crash-looping.
- After triggering a deploy, find the run for that commit, wait for the `deploy` job,
then report what is actually live — or report the failing step's logs.
- Reference secret names only. Never store key material, private-key paths, or raw
credential output in the repo, logs, or PR bodies.
- The VPS is shared with unrelated pet projects. Never stop, delete, or reconfigure a
neighbour's PM2 process, container, nginx site, or data directory without explicit
user confirmation, and report freed RAM/disk afterwards.

## Gotchas

- **nginx cannot traverse the home directory by default.** `/home/aleksandrs` is mode
`750`; nginx runs as `www-data` and returns 500 for the static client until the
directory is traversable (`chmod o+x /home/aleksandrs`). This needs no sudo — the
owner can do it over plain SSH.
- **`sudo` on the VPS requires a password.** The agent cannot run privileged commands.
Hand the user a copy-pasteable block (nginx config changes, certbot, Docker install).
- **RAM is the binding constraint**, not disk or CPU. The host has 3.7 GiB total and
neighbours consume most of it; check `free -h` before adding services.
- `rapfi` must not be published to the host; only `gomoku-server` talks to it.
- **SignalR game hubs run through `api.gomoku.app`.** nginx's default
`proxy_read_timeout` of 60s would drop idle game sockets, so the API vhost raises the
read/send timeouts. Never hardcode `proxy_set_header Connection "upgrade"` — use the
`$connection_upgrade` map, otherwise plain HTTP requests also claim an upgrade and
upstream keepalive cannot work.
- HTTP/2 is not enabled. On nginx 1.24 it is a `listen 443 ssl http2;` parameter, and
the socket is shared with neighbouring projects' vhosts — treat it as a change that
needs user confirmation.
- The compose file is copied to the VPS as `docker-compose.yml`; `DOCKERHUB_USERNAME`
is written into a sibling `.env` by the deploy job, because the compose file
interpolates it into image names.
- DNS for both hostnames is served by Vercel nameservers even though hosting moved to
the VPS — the A records already point at the VPS, so a hosting change needs no DNS
work.
23 changes: 23 additions & 0 deletions .agents/skills/deploy-operations/references/routing-evals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Deploy Operations Routing Evals

## Positive

- "Deploy the current branch and check that api.gomoku.app is healthy."
- "Why is gomoku.app returning 500?"
- "Add a staging service on the VPS — which port should it use?"
- "The rapfi container keeps restarting."
- "Change the nginx config for api.gomoku.app."

## Negative

- "Fix the failing build_node check on this PR." → `ci-pipeline`
- "Why does the client call localhost in production?" → `client-build-conventions`
- "Play a game on gomoku.app and check the board renders." → `live-playtest-qa`
- "Review this React component."

## Forbidden

- Do not load this skill just because a task mentions Docker or GitHub Actions in the
abstract; it is for the live gomoku environment specifically.
- Do not use this skill to justify touching neighbouring projects on the shared VPS
without explicit user confirmation.
Loading
Loading