Files
proof-of-work/AGENTS.md
T
Keysat ac9b71ed16 Add AGENTS.md, ROADMAP.md, and CLAUDE.md symlink
Onboarding doc for fresh agent sessions: stack, commands, layout,
conventions, and an Always/Never list of gotchas hit during the AI
overhaul. Current state section tracks the 1.1.0:7 checkpoint.
ROADMAP.md holds the longer-term backlog. CLAUDE.md symlinks AGENTS.md
so Claude Code loads it. Secrets kept out — private registry/file-host
URLs and creds referenced by file location, not value.
2026-06-12 20:02:27 -05:00

7.7 KiB
Raw Blame History

AGENTS.md — Proof of Work

Self-hosted multi-user workout logger (Next.js app) packaged as a StartOS 0.4 s9pk, published to a private Start9 registry.

Stack (versions that matter)

  • Next.js 14 (App Router, server components + server actions, SSE streaming)
  • React 18, TypeScript 5, TailwindCSS 3
  • Prisma 5 ORM over SQLite (WAL mode; tuned at boot)
  • bcrypt (native — NOT bcryptjs), zod 3 for validation
  • Vitest 4 for tests
  • @start9labs/start-sdk for the 0.4 packaging layer
  • Node >= 20 to build

Layout (two projects in one repo)

proof-of-work/            ← the Next.js app (THIS is where you run npm)
  app/                    ← App Router routes; app/api/** = route handlers
  app/main/               ← authed UI; navigation.tsx = sidebar
  components/             ← React components (workouts/, ai/, settings/)
  lib/ai/                 ← AI subsystem (see below)
  lib/ai/providers/       ← claude.ts openai.ts gemini.ts ollama.ts + index.ts (getProvider)
  prisma/schema.prisma    ← schema (mirror; real DB migrates via entrypoint ALTERs)
  prisma/*.seed.json      ← curated exercise library + AI templates (reconciled each boot)
  tests/                  ← Vitest specs (ai-*.test.ts, routes-*.test.ts, ...)
start9/0.4/               ← StartOS packaging wrapper
  docker_entrypoint.sh    ← boot: first-boot seed, additive ALTERs, library reconcile
  Makefile / s9pk.mk      ← s9pk build (ARCHES := x86)
  startos/versions/       ← one file per ExVer version + index.ts (the version graph)
~/.proof-of-work/         ← publish.sh + unpublish.sh (NOT in repo; self-hosted registry)

workout-planner/ is scratch (only logs/) — ignore. start9/0.4/*.s9pk are build artifacts.

Commands

Run app commands from proof-of-work/ (running tsc/vitest/next from repo root fails — wrong cwd):

cd proof-of-work
npm run dev            # local dev server
npm run build          # next build (run this to catch route/type errors before shipping)
npm run lint           # next lint
npm test               # vitest run (full suite)
npx vitest run tests/ai-pricing.test.ts      # single file
npx vitest run -t "findPrice"                # single test by name
npx tsc --noEmit       # typecheck only
npx prisma generate    # REQUIRED after editing schema.prisma (else TS can't see new fields)

Build/sideload the s9pk (from start9/0.4/): make x86 then make install. Publish to the registry: ~/.proof-of-work/publish.sh (builds, uploads to FileBrowser, registers). Unpublish: ~/.proof-of-work/unpublish.sh.

Conventions

  • Versioning is ExVer: 1.1.0:4 (note the colon). Every release = a new start9/0.4/startos/versions/vMAJOR.MINOR.PATCH.N.ts file, imported into versions/index.ts and promoted to current (previous current moves into other[]).
  • Bump the version BEFORE building the s9pk — Start9 0.4 won't recognize a rebuild as an update otherwise.
  • Schema changes are additive ALTERs in docker_entrypoint.sh, guarded by PRAGMA table_info checks. Keep schema.prisma in sync as the mirror, but the entrypoint is what migrates live /data. Never write a destructive migration.
  • Commit subject = vX.Y.Z:N — short summary, imperative, body explains the why.
  • Git remote is self-hosted (a private Start9 registry + a FileBrowser artifact host), NOT GitHub. The actual registry/file-host URLs are constants in ~/.proof-of-work/{publish,unpublish}.sh; FileBrowser creds live in ~/.keysat/filebrowser.env (outside the repo, gitignored). Default branch is master.
  • AI provider abstraction: each provider yields an async iterable of GenerateChunk (text / usage / done / error); add new ones under lib/ai/providers/ and register in index.ts.
  • Streaming AI uses SSE; partial JSON is recovered with lib/ai/lenientJson.ts.
  • Tests live in proof-of-work/tests/; mock server-action deps with vi.hoisted() + vi.mock.
  • Pricing/model menus live in lib/ai/pricing.ts (PRICES, MODEL_MENU) — keep them paired so every menu model has a price entry (there's a test enforcing this).

Always

  • Run npx prisma generate after any schema.prisma edit, then npx tsc --noEmit.
  • Run npm test AND npm run build before shipping a version.
  • Add the boot-time ALTER TABLE (with an existence guard) for any new column, in docker_entrypoint.sh.
  • Treat API keys / secrets as plaintext in /data BY DESIGN (threat model: the operator owns /data). Reference env-var names (DATABASE_URL, etc.); never hardcode values.
  • Keep migrations idempotent and additive; data already on a user's server must survive upgrades.
  • Verify the published file actually changed (size / 404 / Last-Modified) after publish.sh.

Never

  • Never add Co-Authored-By / "Generated with" trailers to commits — the user authors commits solo. (This was done wrong in earlier commits; do not repeat.)
  • Never reintroduce nonce-based CSP — it broke first paint. Use the static 'unsafe-inline' CSP in next.config.js.
  • Never run app commands from the repo root — always cd proof-of-work first.
  • Never export non-HTTP-method symbols from a route.ts — Next.js rejects the build (helpers go in lib/, e.g. lib/ai/activateConfig.ts).
  • Never commit app.db, *.bak, or any user data — they're gitignored; double-check git status before git add.
  • Never click Uninstall on a StartOS package during a data cutover — it destroys the volume; use Stop.
  • Never assume GitHub — don't add a GitHub remote or push there.

AI subsystem cheat-sheet

  • generate/route.ts kicks off a detached background runner (generationRunner.ts) and returns an id; the client attaches via SSE (generations/[id]/stream) and can also poll the row. Navigating away does NOT cancel generation.
  • System prompt = systemPromptBase.ts (output contract: JSON-only, library exerciseIds only, suggested weights) + the template's coaching prompt + PROGRAM_OUTPUT_SHAPE + library + optional history block (historyContext.ts).
  • Multi-config: AIConfigProfile rows per user; UserPreferences.activeAIConfigId points at the active one and is mirrored into the legacy ai* columns for back-compat.

TODO (verify before relying on)

  • Exact make target set beyond x86 (Makefile sets ARCHES := x86; make install sideloads — confirm host config).
  • Whether npm run db:seed is needed in any current workflow (entrypoint handles seeding on the server).

Current state

Latest version is 1.1.0:7 (built locally, installed on the StartOS server). The registry is currently empty — all versions were unpublished; nothing is downloadable until publish.sh runs again.

Working: workout logging, programs (manual + AI), multi-user, curated library, full AI subsystem (5 providers, multi-config, background generation, history detail, cost/duration, Ollama auto-detect, infinite-scroll exercise history).

In progress: none — repo is at a clean checkpoint.

Decided but not implemented: tiered AI prompt formatting — JSON-Schema enforcement (Ollama format / OpenAI response_format), pipe-separated library, XML-tagged sections, Ollama-only few-shot. Targets local-model output quality; would ship as 1.1.0:8.

Known issues: earlier commits (8f149d35b0535f) carry Co-Authored-By trailers to scrub from history. publish.sh Step 3 (registry register) silently no-op'd on 1.1.0:6 and :7 — uploaded the file but didn't register; investigate before relying on those versions appearing in the registry.

Next steps:

  1. Scrub Co-Authored-By trailers from history (filter-branch or rebase).
  2. Re-publish current version once the Step-3 registry-register failure is diagnosed.
  3. Implement the tiered AI prompt formatting (1.1.0:8).