Mine AGENTS.md brain onto disk: resolve TODOs, extract AI subsystem guide
Retrofit per the playbook. Resolve both AGENTS.md TODOs with verified facts (make-target set; db:seed is live at image-build + local dev), reconcile the AI-provider count (4 files -> 5 registered providers), and extract the AI subsystem cheat-sheet into docs/guides/ai-subsystem.md, lazy-loaded via a .claude/rules symlink with an index line in AGENTS.md. All AGENTS.md commands verified green (tests 177/177, build, tsc, lint).
This commit is contained in:
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../docs/guides/ai-subsystem.md
|
||||
@@ -25,6 +25,7 @@ logs/
|
||||
.env.*.local
|
||||
|
||||
# Local DB snapshots that aren't part of the package
|
||||
app.db
|
||||
proof-of-work-*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
|
||||
@@ -20,7 +20,7 @@ proof-of-work/ ← the Next.js app (THIS is where you run npm)
|
||||
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)
|
||||
lib/ai/providers/ ← claude.ts openai.ts gemini.ts ollama.ts + index.ts (getProvider; openai.ts exports both openai + openai-compatible = 5 registered providers)
|
||||
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, ...)
|
||||
@@ -49,8 +49,18 @@ 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`.
|
||||
Build/sideload the s9pk (from `start9/0.4/`): `make x86` then `make install`. Targets come from `s9pk.mk` (the wrapper `Makefile` just sets `ARCHES := x86`):
|
||||
|
||||
- `make x86` — build the x86 s9pk.
|
||||
- `make install` — sideload the newest local `.s9pk` to the StartOS box at `host:` in `~/.startos/config.yaml` (via `start-cli package install`).
|
||||
- `make publish` — upload every `.s9pk` to the S3 bucket (`s9pk-s3base:`) and index it on `registry:` from `~/.startos/config.yaml` (via `s3cmd` + `start-cli s9pk publish`). **Distinct from `~/.proof-of-work/publish.sh`** below.
|
||||
- `make clean` — remove build artifacts.
|
||||
|
||||
Both `install` and `publish` read host/registry config from `~/.startos/config.yaml`, which is **not in the repo** — verify against the live setup, not from a checkout.
|
||||
|
||||
Canonical publish path for this project: `~/.proof-of-work/publish.sh` (builds, uploads to FileBrowser, registers) — separate from the generic `make publish`. Unpublish: `~/.proof-of-work/unpublish.sh`.
|
||||
|
||||
`npm run db:seed` (= `tsx prisma/seed.ts`) seeds the `InstanceSettings` singleton + curated library; it is **live, not dead** — invoked at Docker image-build time (`start9/0.4/Dockerfile`) to bake the library into the image, and also the local-dev first-run path (`proof-of-work/README.md`). Runtime first-boot/upgrade seeding is handled separately by `docker_entrypoint.sh`.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -59,10 +69,8 @@ Publish to the registry: `~/.proof-of-work/publish.sh` (builds, uploads to FileB
|
||||
- **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).
|
||||
- **Before editing the AI subsystem (`proof-of-work/lib/ai/**` or the generate/generations routes), read `docs/guides/ai-subsystem.md`** — provider abstraction, SSE/lenient-JSON, pricing/model menus, and the background-runner architecture live there.
|
||||
|
||||
## Always
|
||||
|
||||
@@ -83,17 +91,6 @@ Publish to the registry: `~/.proof-of-work/publish.sh` (builds, uploads to FileB
|
||||
- **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 `exerciseId`s 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.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
paths:
|
||||
- proof-of-work/lib/ai/**
|
||||
- proof-of-work/app/api/ai/**
|
||||
---
|
||||
|
||||
# AI subsystem
|
||||
|
||||
Scoped guidance for the AI generation subsystem (`proof-of-work/lib/ai/**` and the
|
||||
generate/generations route handlers). Whole-repo rules live in `AGENTS.md`.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `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
|
||||
`exerciseId`s 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.
|
||||
|
||||
## 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`.
|
||||
`openai.ts` exports both `openai` and `openai-compatible`, so the four provider files
|
||||
register **5** providers (`claude`, `openai`, `openai-compatible`, `gemini`, `ollama`).
|
||||
- Streaming AI uses SSE; partial JSON is recovered with `lib/ai/lenientJson.ts`.
|
||||
- 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).
|
||||
Reference in New Issue
Block a user