feat: initial docs repo with project inventory and all documentation

- Added REPO_INVENTORY.md with all repos, branches, remotes, and staging info
- Added .gitignore
- Synced all existing docs from local workspace
- Centralized documentation hub for GrowQR team
This commit is contained in:
-Puter
2026-06-22 15:04:27 +05:30
commit e6685203fe
71 changed files with 35044 additions and 0 deletions

463
runbook.md Normal file
View File

@@ -0,0 +1,463 @@
# GrowQR Demo Runbook
Use this when restarting the full local demo after everything has been stopped. It should bring back the workflows dashboard, growqr-app frontend session launchers, backend, Rivet workflow actor runner, interview-service, roleplay-service, QScore service, resume-builder, matchmaking, and the per-user Gitea/OpenCode stack.
## What Must Be Running
- Docker Desktop
- `qscore-service`: Postgres, Redis, API, worker
- `interview-service`: API, Postgres, MinIO, bucket setup
- `roleplay-service`: API, Postgres, MinIO, bucket setup
- `resume-builder`: Docker container with Postgres (port 5433), API (port 8002)
- `matchmaking`: API on host port 8006
- `growqr-backend`: backend Postgres plus local Node backend
- `workflows--dashboard`: Next.js frontend
- `growqr-app/frontend`: Next.js frontend for the new interview/roleplay session UI
- Per-user OpenCode containers, created by backend provisioning (Gitea is central/shared)
The known demo ports are:
- Workflows dashboard: `http://localhost:3000`
- GrowQR app frontend session launchers: `http://localhost:3002`
- Backend: `http://localhost:4000`
- Backend Rivet proxy: `http://localhost:4000/api/rivet`
- Interview service: `http://localhost:8007`
- Roleplay service: `http://localhost:8008`
- QScore service: `http://localhost:8000`
- Resume builder: `http://localhost:8002`
- Matchmaking service: `http://localhost:8006`
- Resume builder DB: `localhost:5433`
- QScore Redis: `localhost:6379`
- Central Gitea: `http://localhost:3001` (shared org-wide, changes.md §2A)
- Backend Postgres: `localhost:5432`
- Per-user OpenCode containers: dynamically allocated from `20000-29999`
## Required Env Files
Before starting, verify these files exist and contain the local demo values:
- `growqr-backend/.env`
- `growqr-backend/.env.local`
- `workflows--dashboard/.env.local`
- `growqr-app/frontend/.env.local`
- `interview-service/.env`
- `roleplay-service/.env`
- `growqr-app/resume-builder/.env.local` (created by setup script)
Do not print secrets into chat or commit them. The important non-secret shape is:
- Backend service URLs should point to `127.0.0.1`:
- `INTERVIEW_SERVICE_URL=http://127.0.0.1:8007`
- `ROLEPLAY_SERVICE_URL=http://127.0.0.1:8008`
- `QSCORE_SERVICE_URL=http://127.0.0.1:8000`
- `RESUME_SERVICE_URL=http://127.0.0.1:8002`
- `MATCHMAKING_SERVICE_URL=http://127.0.0.1:8006`
- `GROWQR_APP_FRONTEND_URL=http://localhost:3002`
- Backend per-user containers should use:
- `OPENCODE_IMAGE=ghcr.io/anomalyco/opencode:latest`
- `USER_CONTAINER_HOST=127.0.0.1`
- `USER_DATA_ROOT=./.data/users`
- Frontend should use:
- `NEXT_PUBLIC_RIVET_ENDPOINT=http://127.0.0.1:4000/api/rivet`
- `NEXT_PUBLIC_GROWQR_BACKEND_URL=http://127.0.0.1:4000` (NOT `GROWQR_BACKEND_URL` — must have NEXT_PUBLIC_ prefix for client-side)
- `OPENCODE_API_KEY=<from backend .env>` (must match backend)
- `LLM_BASE_URL=https://opencode.ai/zen/v1`
- `LLM_MODEL=kimi-k2.6`
- Resume builder should use:
- `DATABASE_URL=postgresql+asyncpg://postgres:postgres@host.docker.internal:5433/growqr_resume` (host.docker.internal when running in Docker, localhost when running locally)
- `API_PORT=8002`
- `OPENCODE_API_KEY=<from backend .env>`**CRITICAL: must be the EXACT same key as backend.** Both services share the same OpenCode account.
- `OPENCODE_BASE_URL=https://opencode.ai/zen/v1`
- `AI_MODEL=kimi-k2.6`
- `CLERK_JWKS_URL=https://noted-elephant-23.accounts.dev/.well-known/jwks.json`
- `CLERK_ISSUER=https://noted-elephant-23.accounts.dev`
- `CORS_ORIGINS="http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000"`
- `A2A_ALLOWED_KEYS=dev-a2a-key`
- `REDIS_URL=redis://host.docker.internal:6379/0`
- `DEBUG=true`
- `SERVICE_TOKEN` and `A2A_ALLOWED_KEY` must match across backend and services.
- Clerk keys must match between frontend and backend.
- Gemini and OpenCode keys must be present for the audio/session and LLM paths.
### OpenCode API Key Sync (IMPORTANT)
The backend and resume-builder share the same OpenCode API key. When copying values, always use `growqr-backend/.env` as the **source of truth**. The resume-builder's `.env.local` was previously out of sync (25-char dummy key vs 67-char real key), causing silent LLM failures.
To verify keys are synced:
```bash
diff <(grep OPENCODE_API_KEY growqr-backend/.env | head -1) <(grep OPENCODE_API_KEY growqr-app/resume-builder/.env.local | head -1)
# Should show no differences
```
Note: interview-service and roleplay-service use Gemini (not OpenCode) — they don't need an OpenCode key.
## Start Order
Run all commands from `/Users/divyansh/Desktop/growQR` unless a command changes directory.
### 0. Start Central Gitea first (NEW per changes.md §2A)
The central Gitea is now a shared compose service in the backend's docker-compose. **Note:** Gitea health check uses port 3000 internally (container), mapped to 3001 on host. Port mapping is `3001:3000`.
```bash
docker compose -f growqr-backend/docker-compose.yml up -d gitea
```
Wait until Gitea is healthy (~30 seconds on first startup):
```bash
curl http://127.0.0.1:3001/api/v1/version
# Expected: {"version":"1.22.6"}
```
Per-user Gitea containers are NO LONGER spawned dynamically. All users share the central Gitea instance with one repo per user under the `growqr` org.
### 1. Start QScore first
QScore owns the Redis instance exposed on `localhost:6379`. The interview service currently uses that host Redis for workflow task dispatch, so QScore should come up first.
```bash
docker compose -f qscore-service/docker-compose.yml up -d --build
```
Wait until all QScore containers are healthy/running:
```bash
docker compose -f qscore-service/docker-compose.yml ps
curl http://127.0.0.1:8000/health
# Expected: {"status":"ok"}
```
### 2. Start Interview Service
```bash
docker compose -f interview-service/docker-compose.yml up -d --build
```
Verify:
```bash
curl http://127.0.0.1:8007/health
# Expected: {"status":"ok","service":"interview-service","a2a":true,"agent":true}
```
### 3. Start Roleplay Service
```bash
docker compose -f roleplay-service/docker-compose.yml up -d --build
```
Verify:
```bash
curl http://127.0.0.1:8008/health
# Expected: {"status":"ok","service":"roleplay-service","a2a":true,"agent":true}
```
### 4. Start Resume Builder (NEW)
The resume builder runs as a Docker container built from `growqr-app/resume-builder/Dockerfile`. It uses its own Postgres database on port 5433.
```bash
# Start resume-builder Postgres
docker compose -f growqr-app/resume-builder/docker-compose.yml up -d postgres
# Build resume-builder image (first time only)
docker build -t growqr-resume-builder growqr-app/resume-builder
# Run migrations
cd growqr-app/resume-builder
uv run --env-file .env.local alembic upgrade head
# Start the service with synced OpenCode key from backend
cd /Users/divyansh/Desktop/growQR
OPENCODE_KEY=$(grep OPENCODE_API_KEY growqr-backend/.env | head -1 | cut -d= -f2)
docker rm -f growqr-resume-builder 2>/dev/null
docker run -d --name growqr-resume-builder \
-p 8002:8002 \
-e DATABASE_URL=postgresql+asyncpg://postgres:postgres@host.docker.internal:5433/growqr_resume \
-e OPENCODE_API_KEY="${OPENCODE_KEY}" \
-e OPENCODE_BASE_URL=https://opencode.ai/zen/v1 \
-e AI_MODEL=kimi-k2.6 \
-e CLERK_JWKS_URL=https://noted-elephant-23.accounts.dev/.well-known/jwks.json \
-e CLERK_ISSUER=https://noted-elephant-23.accounts.dev \
-e CORS_ORIGINS="http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000" \
-e A2A_ALLOWED_KEYS=dev-a2a-key \
-e REDIS_URL=redis://host.docker.internal:6379/0 \
-e DEBUG=true \
growqr-resume-builder uvicorn app.main:app --host 0.0.0.0 --port 8002
# Verify
curl http://127.0.0.1:8002/health
# Expected: {"status":"healthy","service":"resume-builder","mcp":true,"agent":true,"a2a":true}
```
### 5. Start Backend Postgres
For the demo path that avoids Rivet `no_capacity`, run the backend locally with its embedded Rivet runner and start only the backend Postgres from compose:
```bash
docker compose -f growqr-backend/docker-compose.yml up -d postgres
```
**Important:** If the backend was previously running with older schema, you may need to add missing columns:
```bash
docker exec growqr-postgres psql -U growqr -d growqr -c "
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS gitea_repo_name TEXT;
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS gitea_repo_owner TEXT;
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS image_version TEXT;
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS migration_version TEXT;
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS prompt_version TEXT;
"
```
Then migrate:
```bash
cd /Users/divyansh/Desktop/growQR/growqr-backend
npm run db:migrate
```
Start the backend:
```bash
cd /Users/divyansh/Desktop/growQR/growqr-backend
RIVET_RUN_ENGINE=1 npm run dev
```
Keep this terminal open. Verify in a separate terminal:
```bash
curl http://127.0.0.1:4000/healthz
# Expected: {"ok":true}
```
The backend will auto-provision OpenCode containers for existing users on boot (reconcileOnBoot).
### 6. Start Matchmaking
The matchmaking service powers the Scout/job-search handoff. It runs on port 8000 internally and port 8006 on the host.
```bash
docker compose -f matchmaking/docker-compose.yml up -d --build
curl http://127.0.0.1:8006/api/v1/health
# Expected: {"status":"healthy", ...}
```
### 7. Start Frontends
Start the dashboard on port 3000:
```bash
cd /Users/divyansh/Desktop/growQR/workflows--dashboard
npm run build
npm run start
```
Start the growqr-app frontend on port 3002. This app now owns the product UI for interview and roleplay sessions launched from workflow chat.
```bash
cd /Users/divyansh/Desktop/growQR/growqr-app/frontend
npm install --legacy-peer-deps
npm run dev -- -p 3002
```
Keep both terminals open. Open:
```text
http://localhost:3000/v2/workflows
```
## Provision OpenCode Containers
OpenCode containers are per-user and spawned dynamically by the backend Docker manager. Gitea is NO LONGER per-user — it's a central shared service (changes.md §2A).
After provisioning, `docker ps` should show containers with names like:
```text
growqr-opencode-user_<userId>
```
If they are missing, sign into the dashboard and allow the user stack bootstrap to run. The local data is under:
```text
growqr-backend/.data/users
```
That folder is intentionally gitignored and should stay local.
## Demo Path — Interview-to-Offer Accelerator
Use this path for the Interview-to-Offer workflow demo:
1. Open `http://localhost:3000/v2/workflows`.
2. Type in the chat: `I have an interview at Google for SWE`
3. The AI responds with a conversational message and the **WorkflowDiscovery card** (red, 4 agents, 4 steps) appears below.
4. Click **"Start this workflow"** on the card.
5. The workflow progresses step-by-step:
- **Step 1**: AI asks for job description → you respond with details
- **Step 2**: AI asks for resume → you respond
- **Step 3**: Resume Agent analyzes and tailors (shows "DONE")
- **Step 4**: Sara interview session ready → shows **"Open" button**
- **Step 5**: Emily roleplay session ready → shows **"Open" button**
- **Step 6**: Quinn computes Q-Score → shows results
6. Click **Open** on Sara's card → new tab opens `http://localhost:3002/service-sessions/interview?session_id=<uuid>...`
7. On the new GrowQR interview launcher, click **Start interview** and allow microphone/camera access.
8. Click **Open** on Emily's card → new tab opens `http://localhost:3002/service-sessions/roleplay?session_id=<uuid>...`
9. On the new GrowQR roleplay launcher, click **Start roleplay** and allow microphone/camera access.
10. Completed steps are marked with **green checkmarks** in the chat.
### Alternative Workflow Trigger Queries
| Workflow | Trigger Query |
|---|---|
| Interview-to-Offer | `I have an interview at Google for SWE` |
| Career Switch | `I want to switch from marketing to product management` |
| Resume Boost | `My resume isn't getting any responses` |
| Job Search | `Find me backend engineering jobs` |
| Job Preparation | `Prepare me for a role at Stripe` |
Direct service demo pages are for low-level service debugging only. Product workflow links should use the new `growqr-app/frontend` launchers:
```text
http://localhost:3002/service-sessions/interview?session_id=<uuid>
http://localhost:3002/service-sessions/roleplay?session_id=<uuid>
```
## Health Checks
Use these after startup:
```bash
curl http://127.0.0.1:4000/healthz # Backend
curl http://127.0.0.1:8007/health # Interview
curl http://127.0.0.1:8008/health # Roleplay
curl http://127.0.0.1:8000/health # QScore
curl http://127.0.0.1:8002/health # Resume builder
curl http://127.0.0.1:8006/api/v1/health # Matchmaking
curl -I 'http://localhost:3002/service-sessions/interview?session_id=demo-interview'
curl -I 'http://localhost:3002/service-sessions/roleplay?session_id=demo-roleplay'
curl http://127.0.0.1:3001/api/v1/version # Gitea
docker ps # All containers
```
If the workflow actor fails with `no_capacity`, restart the backend with:
```bash
cd /Users/divyansh/Desktop/growQR/growqr-backend
RIVET_RUN_ENGINE=1 npm run dev
```
## Shutdown
Stop frontend and backend with `Ctrl+C` in their terminals.
**Also kill any lingering backend Node processes:**
```bash
pkill -f "tsx watch" 2>/dev/null
pkill -f "tsx.*src/index" 2>/dev/null
```
Stop compose services:
```bash
docker compose -f growqr-backend/docker-compose.yml down
docker compose -f interview-service/docker-compose.yml down
docker compose -f roleplay-service/docker-compose.yml down
docker compose -f qscore-service/docker-compose.yml down
docker compose -f matchmaking/docker-compose.yml down
docker compose -f growqr-app/resume-builder/docker-compose.yml down
docker rm -f growqr-resume-builder
```
Stop per-user OpenCode containers if still running:
```bash
docker ps --filter "name=growqr-opencode-user"
```
Then stop/remove the shown containers by name:
```bash
docker stop <growqr-opencode-container>
docker rm <growqr-opencode-container>
```
They can be recreated later by the backend provisioning flow. The local persistent data remains in `growqr-backend/.data/users`.
## Build Rule
Whenever code changes are made, run the relevant build before calling the demo ready:
```bash
cd /Users/divyansh/Desktop/growQR/growqr-backend && npm run build
cd /Users/divyansh/Desktop/growQR/workflows--dashboard && npm run build
cd /Users/divyansh/Desktop/growQR/growqr-app/frontend && npm run build
```
For Dockerized Python services, rebuild with their compose commands:
```bash
docker compose -f interview-service/docker-compose.yml up -d --build
docker compose -f roleplay-service/docker-compose.yml up -d --build
docker compose -f qscore-service/docker-compose.yml up -d --build
```
## Agent Architecture & Flow
The system has 5 real service-backed agents:
| Agent | Name | Service | Port | Provider | Creates |
|---|---|---|---|---|---|
| Resume Agent | Mira | resume-builder | 8002 | OpenCode (kimi-k2.6) | Resume analysis + tailoring |
| Interview Agent | Sara | interview-service + growqr-app frontend launcher | 8007 + 3002 | Gemini (audio) | Live interview sessions in new UI |
| Roleplay Agent | Emily | roleplay-service + growqr-app frontend launcher | 8008 + 3002 | Gemini (audio) | Roleplay scenario sessions in new UI |
| Q-Score Agent | Quinn | qscore-service | 8000 | Internal formula | Readiness scores |
| Job Search Agent | Scout | matchmaking | 8006 | Internal ranking engine | Ranked opportunity feed |
### Chat Flow (Conversational Step-by-Step)
1. User types message → Frontend sends to `POST http://127.0.0.1:4000/api/chat` (with Clerk auth)
2. Backend tries Rivet actor first, falls back to direct LLM with tools
3. LLM responds conversationally OR calls a tool (`start_interview_session`, `analyze_resume`, etc.)
4. If tool is called, backend executes it against the real microservice
5. Response includes `{ reply, sessions[], workflow }` — sessions contain real URLs
6. Frontend renders agent cards with photos, status badges, and **Open** buttons
### Trigger Queries (what to type)
| For this agent | Type this |
|---|---|
| Resume analysis | `analyze my resume for Google SWE` |
| Interview session | `launch a behavioral interview for Software Engineer at Google` |
| Roleplay session | `launch a roleplay for negotiating a Google offer` |
| Q-Score | `compute my Q-Score` |
| Full workflow | `I have an interview at Google for SWE` |
### Session URLs
When a session is created, the backend preserves the `session_id` from the service configure response and returns one of these product URLs:
- Interview: `http://localhost:3002/service-sessions/interview?session_id=<uuid>&role=<goal>&type=<type>`
- Roleplay: `http://localhost:3002/service-sessions/roleplay?session_id=<uuid>&goal=<goal>&type=<type>`
The old direct demo pages still exist for service debugging, but workflow chat should not link to them.
### Key Files
- System prompt: `growqr-backend/prompts/system.txt`
- Agent definitions: `growqr-backend/agents/*.md`
- Service probes: `growqr-backend/src/services/service-agents.ts`
- Chat route (with tool dispatch): `growqr-backend/src/routes/chat.ts`
- Frontend chat component: `workflows--dashboard/src/components/home-v2/GrowChat.tsx`
- New service launchers: `growqr-app/frontend/src/app/service-sessions/interview/page.tsx`, `growqr-app/frontend/src/app/service-sessions/roleplay/page.tsx`
- Product flow requirements: `.growqr_memory.md`
### Known Quirks
- **Roleplay configure is slow** (30-60s): The roleplay service calls an LLM to generate scenarios. Be patient.
- **Rivet `/actors` returns 400**: Cosmetic console warning from the Rivet frontend SDK. Does not affect chat — chat uses HTTP fallback.
- **Rivet proxy vs handler conflict**: When `RIVET_RUN_ENGINE=1`, the backend proxies `/api/rivet/*` to the engine at `localhost:6420` instead of using `registry.handler()`. This avoids the "Runtime already started as runner" error.
- **Multiple backend processes**: `tsx watch` sometimes spawns orphans. Always `pkill -f "tsx watch"` before restarting.