diff --git a/.claude/plans/shiny-tinkering-hickey.md b/.claude/plans/shiny-tinkering-hickey.md new file mode 100644 index 0000000..1116cd6 --- /dev/null +++ b/.claude/plans/shiny-tinkering-hickey.md @@ -0,0 +1,38 @@ +# Plan: Mount Docker Socket + Prompt for Overlays on Provision + +## Context +The Docker socket isn't mounted because `docker-compose.docker.yml` is missing from `.openharness/config.json`. The provision skill should also prompt the user about which overlays to enable so this doesn't happen silently. + +## Changes + +### 1. Add docker overlay to config +**File:** `.openharness/config.json` + +Add `".devcontainer/docker-compose.docker.yml"` to `composeOverrides` array. + +### 2. Recreate sandbox to pick up the socket mount +```bash +bash .devcontainer/init-env.sh +docker compose --env-file .devcontainer/.env \ + -f .devcontainer/docker-compose.yml \ + -f .devcontainer/docker-compose.postgres.yml \ + -f .devcontainer/docker-compose.cloudflared.yml \ + -f .devcontainer/docker-compose.docker.yml \ + up -d +``` + +### 3. Update provision skill to prompt for overlays +**File:** `.claude/skills/provision/SKILL.md` + +Before detecting overlays, add a step that: +- Lists all available `docker-compose.*.yml` files in `.devcontainer/` +- Shows which are currently enabled in `config.json` +- Asks the user if they want to enable/disable any before proceeding +- Updates `config.json` accordingly + +### 4. Save feedback memory +Record that the user expects overlay selection prompting during provision. + +## Verification +- `docker exec next-postgres-shadcn docker ps` works inside the container +- Provision skill includes overlay prompt step diff --git a/.claude/plans/snappy-marinating-peacock.md b/.claude/plans/snappy-marinating-peacock.md new file mode 100644 index 0000000..f8331be --- /dev/null +++ b/.claude/plans/snappy-marinating-peacock.md @@ -0,0 +1,318 @@ +# Consolidate `docker/` into `.devcontainer/` + +## Context + +The repo has two parallel Docker setups: `docker/` (production sandbox with layered compose overlays) and `.devcontainer/` (VS Code dev container with SSH + extra tooling but missing PostgreSQL/cloudflared). This creates maintenance burden and divergence. The devcontainer should become the single source of truth for containerization. + +**Key user requirements:** +- Bake PostgreSQL into the base compose file (no separate nextjs overlay) +- Keep cloudflared + docker-in-docker as optional overlays +- Install cloudflared and agent-browser at **build time** (not conditional runtime install) +- Use **named Docker volumes** for auth persistence (.claude, .cloudflared, .ssh, .config/gh) so restarts don't require re-auth +- Mount path: `/home/sandbox/project` (entire repo, not just workspace/) +- Update CI workflows to build from `.devcontainer/Dockerfile` +- Delete `docker/` entirely + +--- + +## Step 1: Consolidated `.devcontainer/Dockerfile` + +Merge both Dockerfiles. Add from `docker/Dockerfile` what's missing: + +- **Packages**: add `cron`, `gosu` to apt-get line +- **Cloudflared**: install at build time (apt repo, same as current entrypoint logic but in a RUN layer) +- **Agent-browser + Chromium**: install at build time (`npm install -g agent-browser && agent-browser install --with-deps`) +- **pi-coding-agent**: add `@mariozechner/pi-coding-agent` to npm global install +- **Shell aliases**: add `claude`, `codex`, `pi` aliases to sandbox `.bashrc` +- **COPY install/**: `COPY --chown=sandbox:sandbox install/ /home/sandbox/install/` + `chmod +x` +- **COPY .openharness/**: `COPY --chown=sandbox:sandbox .openharness/ /home/sandbox/.openharness/` +- **COPY workspace/**: `COPY --chown=sandbox:sandbox workspace/ /home/sandbox/project/workspace/` (for CI builds; bind mount overrides at runtime) +- **ENV TZ**: `ENV TZ=America/Denver` + +Keep everything already in `.devcontainer/Dockerfile`: SSH server, GitHub CLI, Docker CLI+Compose, Bun, uv, ripgrep, tmux, nano, bash-completion, git safe.directory, VS Code labels. + +--- + +## Step 2: Consolidated `.devcontainer/docker-compose.yml` + +Rewrite to merge base + nextjs overlay + current devcontainer compose. Named volumes for all auth: + +```yaml +services: + postgres: + image: postgres:16-alpine + container_name: ${NAME:-next-postgres-shadcn}-postgres + environment: + POSTGRES_USER: sandbox + POSTGRES_PASSWORD: sandbox + POSTGRES_DB: sandbox + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U sandbox"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - devnet + + sandbox: + container_name: ${NAME:-next-postgres-shadcn} + build: + context: .. + dockerfile: .devcontainer/Dockerfile + depends_on: + postgres: + condition: service_healthy + ports: + - "2222:22" + - "${PORT:-3000}:3000" + volumes: + - ..:/home/sandbox/project + - ../.openharness:/home/sandbox/.openharness + - claude-auth:/home/sandbox/.claude + - cloudflared-auth:/home/sandbox/.cloudflared + - gh-config:/home/sandbox/.config/gh + - ssh-keys:/home/sandbox/.ssh + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + - TZ=${TZ:-America/Denver} + - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true + - DATABASE_URL=postgresql://sandbox:sandbox@postgres:5432/sandbox + - PGHOST=postgres + - PGUSER=sandbox + - PGPASSWORD=sandbox + - PGDATABASE=sandbox + - HEARTBEAT_ACTIVE_START=${HEARTBEAT_ACTIVE_START:-} + - HEARTBEAT_ACTIVE_END=${HEARTBEAT_ACTIVE_END:-} + - HEARTBEAT_AGENT=${HEARTBEAT_AGENT:-claude} + stdin_open: true + tty: true + entrypoint: /usr/local/bin/entrypoint.sh + command: /usr/sbin/sshd -D + restart: unless-stopped + networks: + - devnet + +networks: + devnet: + name: ${NAME:-next-postgres-shadcn}-devnet + driver: bridge + +volumes: + pgdata: + claude-auth: + cloudflared-auth: + gh-config: + ssh-keys: +``` + +**Key changes from current devcontainer compose:** +- Added postgres service with health check +- Replaced `~/.claude` host bind mount with `claude-auth` named volume (persists across restarts) +- Added named volumes for `.cloudflared`, `.config/gh`, `.ssh` (previously only in cloudflared overlay) +- Removed `/var/run/docker.sock` from base (moved to docker overlay) +- Added port 3000, DATABASE_URL, PG* env vars, HEARTBEAT_* env vars +- Added devnet network + +--- + +## Step 3: Cloudflared Overlay `.devcontainer/docker-compose.cloudflared.yml` + +Now only needs env vars (no volumes — those are in base, no install — that's in Dockerfile): + +```yaml +services: + sandbox: + environment: + - INSTALL_CLOUDFLARED=true + - INSTALL_BROWSER=true +``` + +The entrypoint reads these env vars to decide whether to **start** cloudflared/browser services (they're already installed). This is a much cleaner overlay. + +--- + +## Step 4: Docker-in-Docker Overlay `.devcontainer/docker-compose.docker.yml` + +Copy from `docker/docker-compose.docker.yml` unchanged: + +```yaml +services: + sandbox: + volumes: + - /var/run/docker.sock:/var/run/docker.sock + extra_hosts: + - "host.docker.internal:host-gateway" +``` + +--- + +## Step 5: Simplified `.devcontainer/entrypoint.sh` + +Merge `install/entrypoint.sh` features but **remove conditional install logic** (tools are baked in now): + +1. Docker GID sync (existing) +2. Fix .claude ownership (existing) +3. SSH host key generation (existing) +4. Start cron daemon (from install/entrypoint.sh) +5. Heartbeat sync — path updated to `/home/sandbox/project/workspace/heartbeats.conf` +6. Run `workspace/startup.sh` as sandbox user via `gosu` — path updated to `/home/sandbox/project/workspace/startup.sh` +7. `exec "$@"` (pass through to sshd) + +No more cloudflared/agent-browser install blocks in the entrypoint. + +--- + +## Step 6: Update `.devcontainer/devcontainer.json` + +Add port 3000 to forwarded ports: +```json +"forwardPorts": [2222, 3000] +``` + +--- + +## Step 7: Update `.openharness/config.json` + +```json +{ + "composeOverrides": [".devcontainer/docker-compose.cloudflared.yml"] +} +``` + +Remove nextjs overlay (baked in). Update cloudflared path. Nextjs overlay no longer exists. + +--- + +## Step 8: Update CI Workflows + +- `.github/workflows/build.yml` line 48: `docker/Dockerfile` -> `.devcontainer/Dockerfile` +- `.github/workflows/release.yml` line 97: `docker/Dockerfile` -> `.devcontainer/Dockerfile` + +--- + +## Step 9: Update `packages/sandbox/src/lib/config.ts` + +- Line 48: existence check `"docker", "Dockerfile"` -> `".devcontainer", "Dockerfile"` +- Line 59: `docker/Dockerfile` -> `.devcontainer/Dockerfile` +- Line 63: `docker/docker-compose.yml` -> `.devcontainer/docker-compose.yml` +- Line 67: `docker/docker-compose.docker.yml` -> `.devcontainer/docker-compose.docker.yml` + +--- + +## Step 10: Update `packages/sandbox/src/__tests__/config.test.ts` + +Lines 89-91: Update expected paths from `docker/` to `.devcontainer/`. + +--- + +## Step 11: Update mount paths (`~/workspace` -> `~/project/workspace`) + +The old setup mounted only `workspace/` at `~/workspace`. The new setup mounts the full repo at `~/project`, so workspace is at `~/project/workspace`. + +| File | Lines | Change | +|------|-------|--------| +| `install/entrypoint.sh` | 26, 58 | `/home/sandbox/workspace/` -> `/home/sandbox/project/workspace/` | +| `install/heartbeat.sh` | 13, 425 | `${HOME}/workspace` -> `${HOME}/project/workspace` | +| `install/setup.sh` | 291, 338 | `$SANDBOX_HOME/workspace` -> `$SANDBOX_HOME/project/workspace` | +| `workspace/startup.sh` | 5 | `$HOME/workspace/next-app` -> `$HOME/project/workspace/next-app` | +| `workspace/TOOLS.md` | 7 | `/home/sandbox/workspace` -> `/home/sandbox/project/workspace` | +| `packages/sandbox/src/tools/ralph.ts` | 51,53,67,69,79,81,95,97,111,113,126,128,142,144,158,160,170,172 | `~/workspace` -> `~/project/workspace`, `workdir: "/home/sandbox/workspace"` -> `workdir: "/home/sandbox/project/workspace"` | +| `packages/sandbox/src/tools/shell.ts` | 20 | `workdir: "/home/sandbox/workspace"` -> `workdir: "/home/sandbox/project/workspace"` | +| `packages/sandbox/src/__tests__/docker.test.ts` | 102, 104 | `/home/sandbox/workspace` -> `/home/sandbox/project/workspace` | +| `workspace/.claude/settings.local.json` | 8, 19, 28 | `~/workspace/` -> `~/project/workspace/` | +| `workspace/.claude/skills/implement/SKILL.md` | 206 | `/home/sandbox/workspace` -> `/home/sandbox/project/workspace` | +| `.github/ISSUE_TEMPLATE/agent.md` | 56 | `~/workspace` -> `~/project/workspace` | +| `workspace/heartbeats.conf` | 6 | `~/workspace/` -> `~/project/workspace/` | + +--- + +## Step 12: Update Skills + +### `.claude/skills/provision/SKILL.md` +- Line 27: `docker/docker-compose.yml` -> `.devcontainer/docker-compose.yml` +- Line 59: Remove cloudflared/agent-browser install note (baked into image now) +- Line 95: `~/workspace/next-app` -> `~/project/workspace/next-app` + +### `.claude/skills/destroy/SKILL.md` +- Line 27: `docker/docker-compose.yml` -> `.devcontainer/docker-compose.yml` +- Line 69: `docker-sandbox` image name may need updating +- Line 80: `docker_` volume prefix -> `devcontainer_` (or check actual prefix) + +### `.claude/skills/diagnose/SKILL.md` +- Lines 21, 34, 35, 37, 38, 43: `~/workspace/next-app` -> `~/project/workspace/next-app` + +--- + +## Step 13: Update Documentation + +### `CLAUDE.md` +- Infrastructure table: remove `docker/` row, update `.devcontainer/` to "Dockerfile, compose files (base + cloudflared/docker overlays)" +- Quick Reference: update any workspace path references + +### `.dockerignore` +- Remove `docker/Dockerfile*` line (docker/ will be deleted) +- Keep `.devcontainer/` excludes with re-includes for entrypoint.sh and Dockerfile + +--- + +## Step 14: Delete `docker/` + +Remove all 5 files: +- `docker/Dockerfile` +- `docker/docker-compose.yml` +- `docker/docker-compose.nextjs.yml` +- `docker/docker-compose.cloudflared.yml` +- `docker/docker-compose.docker.yml` + +--- + +## Files Modified (complete list) + +| # | File | Action | +|---|------|--------| +| 1 | `.devcontainer/Dockerfile` | Edit (merge production features) | +| 2 | `.devcontainer/docker-compose.yml` | Rewrite (merge base + nextjs + auth volumes) | +| 3 | `.devcontainer/docker-compose.cloudflared.yml` | Create (env vars only) | +| 4 | `.devcontainer/docker-compose.docker.yml` | Create (docker.sock overlay) | +| 5 | `.devcontainer/entrypoint.sh` | Edit (merge install/entrypoint.sh, remove conditional installs) | +| 6 | `.devcontainer/devcontainer.json` | Edit (add port 3000) | +| 7 | `.openharness/config.json` | Edit (update overlay paths) | +| 8 | `.github/workflows/build.yml` | Edit (Dockerfile path) | +| 9 | `.github/workflows/release.yml` | Edit (Dockerfile path) | +| 10 | `packages/sandbox/src/lib/config.ts` | Edit (docker/ -> .devcontainer/) | +| 11 | `packages/sandbox/src/__tests__/config.test.ts` | Edit (path assertions) | +| 12 | `packages/sandbox/src/__tests__/docker.test.ts` | Edit (workspace path) | +| 13 | `packages/sandbox/src/tools/ralph.ts` | Edit (workspace paths) | +| 14 | `packages/sandbox/src/tools/shell.ts` | Edit (workspace path) | +| 15 | `install/entrypoint.sh` | Edit (workspace paths) | +| 16 | `install/heartbeat.sh` | Edit (workspace paths) | +| 17 | `install/setup.sh` | Edit (workspace paths) | +| 18 | `workspace/startup.sh` | Edit (workspace path) | +| 19 | `workspace/TOOLS.md` | Edit (workspace path) | +| 20 | `workspace/heartbeats.conf` | Edit (workspace path) | +| 21 | `workspace/.claude/settings.local.json` | Edit (hook paths) | +| 22 | `workspace/.claude/skills/implement/SKILL.md` | Edit (workspace path) | +| 23 | `.claude/skills/provision/SKILL.md` | Edit (compose path + workspace path) | +| 24 | `.claude/skills/destroy/SKILL.md` | Edit (compose path + volume prefix) | +| 25 | `.claude/skills/diagnose/SKILL.md` | Edit (workspace paths) | +| 26 | `CLAUDE.md` | Edit (remove docker/ references) | +| 27 | `.dockerignore` | Edit (remove docker/Dockerfile* line) | +| 28 | `.github/ISSUE_TEMPLATE/agent.md` | Edit (workspace path) | +| 29 | `docker/` (5 files) | Delete | + +--- + +## Verification + +1. **Build the image**: `docker compose -f .devcontainer/docker-compose.yml build` +2. **Start the stack**: `NAME=next-postgres-shadcn docker compose -f .devcontainer/docker-compose.yml -f .devcontainer/docker-compose.cloudflared.yml up -d` +3. **Verify containers**: `docker ps` — both sandbox and postgres should be running +4. **Verify cloudflared installed**: `docker exec next-postgres-shadcn cloudflared --version` +5. **Verify agent-browser installed**: `docker exec next-postgres-shadcn which agent-browser` +6. **Verify auth persists**: restart containers, confirm `.claude` and `.cloudflared` volumes survive +7. **Run tests**: `docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/project/workspace/next-app && npm run test:setup'` +8. **Run package tests**: `cd packages/sandbox && npm test` (config.ts path assertions) +9. **VS Code attach**: open in VS Code with Dev Containers extension, verify it connects diff --git a/.claude/skills/delegate/SKILL.md b/.claude/skills/delegate/SKILL.md new file mode 100644 index 0000000..739d453 --- /dev/null +++ b/.claude/skills/delegate/SKILL.md @@ -0,0 +1,241 @@ +--- +name: delegate +description: | + Parallel execution coordinator. Decomposes a plan into discrete tasks, + identifies parallelism via dependency analysis, and spawns worker sub-agents + in waves to maximize throughput without sacrificing quality. Collects results, + validates completeness, and reports a structured summary. + TRIGGER when: asked to delegate work, execute a plan, parallelize tasks, + "run this plan", "delegate this", or after /prd or plan creation. +argument-hint: "[--plan ] [--dry-run]" +--- + +# Delegate + +Parallel execution coordinator. Read a plan or conversation context, decompose it into +a dependency-ordered task graph, and spawn worker sub-agents in parallel waves. Each wave +completes before the next begins. Results are collected, validated, and reported. + +**Core principle: maximize parallelism while respecting dependencies absolutely.** + +## Decision Flow + +```mermaid +flowchart TD + A["Resolve input: $ARGUMENTS or conversation context"] --> B{Plan found?} + B -->|No| FAIL["Report: no plan found"] + FAIL --> MEM_FAIL[Memory Protocol] + + B -->|Yes| C["Step 2: Deep-think task decomposition"] + C --> D["Step 3: Build dependency graph"] + D --> E["Step 4: Create tasks + compute waves"] + E --> F{--dry-run?} + F -->|Yes| DRY["Report: task graph + wave plan"] + DRY --> MEM_DRY[Memory Protocol] + + F -->|No| G["Step 5: Execute Wave N"] + G --> G1["Worker A"] + G --> G2["Worker B"] + G --> G3["Worker C"] + G1 & G2 & G3 --> H{All passed?} + H -->|No| I["Mark dependents BLOCKED, continue independent"] + I --> J{More waves?} + H -->|Yes| J + J -->|Yes| G + J -->|No| K["Step 6: Validate"] + K --> L["Step 7: Report"] + L --> MEM_OP[Memory Protocol] +``` + +## Instructions + +### 1. Resolve input + +Arguments received: `$ARGUMENTS` + +- If `--plan ` is provided, read that file +- If no arguments, use the current conversation context (the plan should be visible + from a prior `/prd`, plan discussion, or issue triage output) +- If `--dry-run` is present, set DRY_RUN=true + +If no plan is found in either source, report: +> No plan found. Provide a plan file path with `--plan ` or discuss the plan first, then run `/delegate`. + +Run Memory Protocol and stop. + +### 2. Decompose into tasks (use extended thinking) + +Analyze the plan deeply and produce a structured task list. For each task, determine: + +| Field | Description | +|-------|-------------| +| **ID** | Sequential: T1, T2, T3, ... | +| **Title** | Short imperative description | +| **Description** | What the worker agent needs to do (2-3 sentences, include file paths) | +| **Depends On** | Task IDs this requires first, or "none" | +| **Files** | Key files the worker will read or modify | +| **Model** | haiku (config/docs) / sonnet (standard) / opus (complex architecture) | +| **Acceptance** | How to verify the task is done (objectively checkable) | + +**Decomposition rules:** +- Each task must be completable by a single sub-agent in one session +- Prefer more smaller tasks over fewer larger ones +- Schema/infrastructure before backend, backend before frontend +- Tasks that touch different files with no shared state CAN be parallel +- Tasks that modify the same file or depend on another's output MUST be sequential +- Every task must have at least one verifiable acceptance criterion + +### 3. Build dependency graph and compute waves + +Arrange tasks into parallel execution waves using topological ordering: + +1. **Wave 1**: All tasks with `Depends On: none` -- run first, in parallel +2. **Wave 2**: All tasks whose dependencies are entirely within Wave 1 +3. **Wave N**: All tasks whose dependencies are entirely within Waves 1..N-1 + +Output the wave plan: + +| Wave | Tasks | Parallelism | Complexity | +|------|-------|-------------|------------| +| 1 | T1, T2, T3 | 3 agents | S + S + M | +| 2 | T4, T5 | 2 agents | M + S | +| 3 | T6 | 1 agent | L | + +**Validation:** +- No circular dependencies (if found, report error and stop) +- Max 5 concurrent agents per wave (split larger waves into sub-waves) + +### 4. Create tasks and track dependencies + +Use `TaskCreate` for each task. Then use `TaskUpdate` with `addBlockedBy` to wire dependencies. + +If `--dry-run`, output the full task graph and wave plan, then skip to **Step 8**. + +### 5. Execute waves + +For each wave, starting from Wave 1: + +**a) Spawn worker agents in ONE message (parallel)** + +Launch N `Agent` tool calls **in a single message** for parallel execution. Each worker receives: +- Task ID, title, description, files, and acceptance criteria +- Summaries of completed prior-wave results (not full output) +- Instruction: report what was done, what files changed, whether acceptance criteria are met + +Worker configuration: +- **Model**: as specified in the task decomposition (haiku/sonnet/opus) +- **run_in_background**: true (for waves with 2+ tasks) + +**b) Collect results** + +After all agents in the wave complete, update each task via `TaskUpdate`: + +| Task | Status | Summary | Files Changed | +|------|--------|---------|---------------| +| T1 | completed | Created schema migration | prisma/schema.prisma | +| T2 | completed | Added API route | src/app/api/... | +| T3 | FAIL | Type error in ... | -- | + +**c) Handle failures** + +If any task fails: +- Log the failure with details +- Check if tasks in subsequent waves depend on the failed task +- Mark dependent tasks as BLOCKED (do not execute them) +- Continue with non-dependent tasks in the next wave + +**d) Advance to next wave** + +Pass completed task summaries as context to the next wave's workers. Repeat until all waves complete or all remaining tasks are blocked. + +### 6. Validate + +After all waves complete: + +1. Review acceptance criteria for every completed task +2. If the plan involved code changes, run verification: + +```bash +cd ~/harness/workspace/projects/next-app && npm run type-check 2>&1 || true +cd ~/harness/workspace/projects/next-app && npm run lint 2>&1 || true +cd ~/harness/workspace/projects/next-app && npm test 2>&1 || true +``` + +3. If validation fails, note which tasks likely caused the failure + +### 7. Report + +Output a structured summary: + +``` +## Delegation Report + +### Task Summary +| Task | Wave | Status | Summary | +|------|------|--------|---------| +| T1 | 1 | DONE | ... | +| T2 | 1 | DONE | ... | +| T3 | 1 | FAIL | ... | +| T4 | 2 | BLOCKED| Depends on T3 | + +### Execution Stats +- Total tasks: N +- Completed: N +- Failed: N +- Blocked: N +- Waves executed: N +- Max parallelism: N agents + +### Validation +- Type check: PASS/FAIL +- Lint: PASS/FAIL +- Tests: PASS/FAIL + +### Issues Requiring Attention +- [list any failures, blocked tasks, or validation errors] +``` + +### 8. Memory Improvement Protocol + +Run at the end of **every** execution -- op, dry-run, or error. + +**a) Log** -- append to `memory/YYYY-MM-DD.md`: + +```markdown +## Delegate -- HH:MM UTC +- **Result**: OP | DRY-RUN | PARTIAL | FAIL +- **Plan**: "" +- **Action**: [N tasks across M waves, P parallel max; X completed, Y failed, Z blocked] +- **Duration**: ~Xs +- **Observation**: [one sentence] +``` + +**b) Qualify** -- ask: +- Did dependency analysis reveal unexpected coupling? +- Did a worker fail on a task that seemed straightforward? +- Was parallelism limited by dependencies more than expected? + +**c) Improve** -- if actionable, append to `MEMORY.md > Lessons Learned` + +## Reference + +### Wave Execution Rules + +| Rule | Value | +|------|-------| +| Max concurrent agents per wave | 5 (split larger waves) | +| Failure handling | Mark dependent tasks BLOCKED, continue independent ones | +| Context passing | Prior wave summaries, not full output | +| Model selection | haiku: config/docs, sonnet: standard, opus: complex | + +### Key Resources + +| Resource | Path | +|----------|------| +| Agent: Implementer | `.claude/agents/implementer.md` | +| Agent: Critic | `.claude/agents/critic.md` | +| Agent: PM | `.claude/agents/pm.md` | +| Agent: Council | `.claude/agents/council.md` | +| Identity | `IDENTITY.md` | +| Memory | `MEMORY.md` | +| Daily Logs | `memory/YYYY-MM-DD.md` | diff --git a/.claude/skills/destroy/SKILL.md b/.claude/skills/destroy/SKILL.md index 8e96e5c..9ff82e4 100644 --- a/.claude/skills/destroy/SKILL.md +++ b/.claude/skills/destroy/SKILL.md @@ -24,7 +24,11 @@ Arguments received: `$ARGUMENTS` ### Step 2 — Detect compose overlays ```bash -COMPOSE_FILES="-f docker/docker-compose.yml" +# Resolve SANDBOX_NAME from git remote or folder name +bash .devcontainer/init-env.sh +source .devcontainer/.env + +COMPOSE_FILES="-f .devcontainer/docker-compose.yml" CONFIG=".openharness/config.json" if [ -f "$CONFIG" ]; then @@ -36,15 +40,17 @@ if [ -f "$CONFIG" ]; then fi ``` +Use `$SANDBOX_NAME` (resolved above) in all subsequent `docker` commands. + ### Step 3 — Confirm with user **Before executing**, show what will be destroyed and ask for confirmation: ``` This will destroy: - - Containers: next-postgres-shadcn, next-postgres-shadcn-postgres + - Containers: $SANDBOX_NAME, $SANDBOX_NAME-postgres - Volumes: pgdata, cloudflared, gh-config, ssh-keys (if --volumes) - - Image: docker-sandbox (if --image) + - Image: $SANDBOX_NAME-sandbox (if --image) Proceed? [y/N] ``` @@ -56,9 +62,9 @@ Do NOT proceed without explicit user confirmation. ```bash # Stop and remove containers (+ volumes if flagged) if [ "$VOLUMES" = true ]; then - NAME=next-postgres-shadcn docker compose $COMPOSE_FILES down -v + docker compose --env-file .devcontainer/.env $COMPOSE_FILES down -v else - NAME=next-postgres-shadcn docker compose $COMPOSE_FILES down + docker compose --env-file .devcontainer/.env $COMPOSE_FILES down fi ``` @@ -66,7 +72,7 @@ fi ```bash if [ "$IMAGE" = true ]; then - docker rmi docker-sandbox 2>/dev/null || true + docker rmi "${SANDBOX_NAME}-sandbox" 2>/dev/null || true fi ``` @@ -74,16 +80,16 @@ fi ```bash # No containers remain -docker ps -a --filter "name=next-postgres-shadcn" --format "{{.Names}}" | grep . && echo "WARNING: containers still exist" || echo "Containers: clean" +docker ps -a --filter "name=$SANDBOX_NAME" --format "{{.Names}}" | grep . && echo "WARNING: containers still exist" || echo "Containers: clean" # No volumes remain (if --volumes) -docker volume ls --filter "name=docker_" --format "{{.Name}}" | grep . && echo "WARNING: volumes still exist" || echo "Volumes: clean" +docker volume ls --filter "name=${SANDBOX_NAME}_" --format "{{.Name}}" | grep . && echo "WARNING: volumes still exist" || echo "Volumes: clean" ``` ### Step 7 — Report ``` -Sandbox 'next-postgres-shadcn' destroyed. +Sandbox '$SANDBOX_NAME' destroyed. Containers: removed Volumes: removed / kept diff --git a/.claude/skills/diagnose/SKILL.md b/.claude/skills/diagnose/SKILL.md index d45a6f5..bb538f6 100644 --- a/.claude/skills/diagnose/SKILL.md +++ b/.claude/skills/diagnose/SKILL.md @@ -15,10 +15,19 @@ inside the container, then auto-remediates any failures and re-runs until green. ## Instructions +### Step 0 — Resolve sandbox name + +```bash +bash .devcontainer/init-env.sh +source .devcontainer/.env +``` + +Use `$SANDBOX_NAME` in all subsequent `docker` commands. + ### Step 1 — Run the diagnose tests ```bash -docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && npm run test:setup' +docker exec -u sandbox $SANDBOX_NAME bash -c 'cd ~/harness/workspace/projects/next-app && npm run test:setup' ``` Capture the output. If all 8 tests pass, skip to **Step 5**. @@ -31,16 +40,16 @@ For each failing test, apply the matching fix **in this order** (order matters |---|---| | `has DATABASE_URL set` | Container missing compose overlay. Cannot auto-fix — report to user. | | `has Node.js >= 22` | Wrong container image. Cannot auto-fix — report to user. | -| `has node_modules installed` or `package-lock.json in sync` | `docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && npm install'` | -| `has Prisma client generated` | `docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && npx prisma generate'` | -| `can connect via TCP` (PostgreSQL) | Check: `docker ps --filter name=next-postgres-shadcn-postgres`. If down, report to user. | -| `responds on port 3000` (Next.js) | Check log: `docker exec next-postgres-shadcn bash -c 'tail -20 /tmp/next-dev.log'`. Then restart: `docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && nohup npm run dev > /tmp/next-dev.log 2>&1 &'`. Wait 15s. | -| `public URL responds` (Cloudflare) | Check log: `docker exec next-postgres-shadcn bash -c 'tail -20 /tmp/cloudflared.log'`. Then restart: `docker exec -u sandbox next-postgres-shadcn bash -c 'TUNNEL_TOKEN=$(grep TUNNEL_TOKEN ~/workspace/startup.sh \| head -1 \| cut -d"\"" -f2); kill $(pidof cloudflared) 2>/dev/null; sleep 1; nohup cloudflared tunnel --url http://localhost:3000 run --token "$TUNNEL_TOKEN" > /tmp/cloudflared.log 2>&1 &'`. Wait 5s. If cloudflared not installed, report to user. | +| `has node_modules installed` or `package-lock.json in sync` | `docker exec -u sandbox $SANDBOX_NAME bash -c 'cd ~/harness/workspace/projects/next-app && npm install'` | +| `has Prisma client generated` | `docker exec -u sandbox $SANDBOX_NAME bash -c 'cd ~/harness/workspace/projects/next-app && npx prisma generate'` | +| `can connect via TCP` (PostgreSQL) | Check: `docker ps --filter name=$SANDBOX_NAME-postgres`. If down, report to user. | +| `responds on port 3000` (Next.js) | Check log: `docker exec $SANDBOX_NAME bash -c 'tail -20 /tmp/next-dev.log'`. Then restart: `docker exec -u sandbox $SANDBOX_NAME bash -c 'cd ~/harness/workspace/projects/next-app && nohup npm run dev > /tmp/next-dev.log 2>&1 &'`. Wait 15s. | +| `public URL responds` (Cloudflare) | Check log: `docker exec $SANDBOX_NAME bash -c 'tail -20 /tmp/cloudflared.log'`. Then restart: `docker exec -u sandbox $SANDBOX_NAME bash -c 'TUNNEL_TOKEN=$(grep TUNNEL_TOKEN ~/harness/workspace/startup.sh \| head -1 \| cut -d"\"" -f2); kill $(pidof cloudflared) 2>/dev/null; sleep 1; nohup cloudflared tunnel --url http://localhost:3000 run --token "$TUNNEL_TOKEN" > /tmp/cloudflared.log 2>&1 &'`. Wait 5s. If cloudflared not installed, report to user. | ### Step 3 — Re-run tests ```bash -docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && npm run test:setup' +docker exec -u sandbox $SANDBOX_NAME bash -c 'cd ~/harness/workspace/projects/next-app && npm run test:setup' ``` ### Step 4 — If still failing diff --git a/.claude/skills/provision/SKILL.md b/.claude/skills/provision/SKILL.md index 4f7d942..3cdd894 100644 --- a/.claude/skills/provision/SKILL.md +++ b/.claude/skills/provision/SKILL.md @@ -16,15 +16,43 @@ Provision (or rebuild) the next-postgres-shadcn sandbox. One skill, zero manual Arguments received: `$ARGUMENTS` -- **NAME**: `next-postgres-shadcn` - **REBUILD**: `true` if `--rebuild` flag is present, otherwise `false` -## 2. Detect Compose Overlays +## 2. Resolve Name and Confirm Overlays -Read `.openharness/config.json` for the full compose file list: +### 2a. Resolve sandbox name ```bash -COMPOSE_FILES="-f docker/docker-compose.yml" +bash .devcontainer/init-env.sh +source .devcontainer/.env +echo "SANDBOX_NAME=$SANDBOX_NAME" +``` + +Use `$SANDBOX_NAME` in all subsequent `docker` commands. + +### 2b. Prompt user for compose overlays + +List all available overlay files (everything matching `.devcontainer/docker-compose.*.yml`) +and show which are currently enabled in `.openharness/config.json`. + +Present a checklist to the user **before proceeding**: + +``` +Available compose overlays: + [x] docker-compose.postgres.yml — PostgreSQL 16 + devnet + [x] docker-compose.cloudflared.yml — Cloudflare tunnel env vars + [x] docker-compose.docker.yml — Docker socket mount (DinD) + [ ] (any new overlays found) + +Enable/disable any overlays? +``` + +If the user changes selections, update `.openharness/config.json` accordingly. + +### 2c. Build compose file list + +```bash +COMPOSE_FILES="-f .devcontainer/docker-compose.yml" CONFIG=".openharness/config.json" if [ -f "$CONFIG" ]; then @@ -43,20 +71,20 @@ echo "Compose files: $COMPOSE_FILES" **Only if `--rebuild` was passed.** Skip this step on initial provision. ```bash -NAME=next-postgres-shadcn docker compose $COMPOSE_FILES down -v 2>&1 +docker compose --env-file .devcontainer/.env $COMPOSE_FILES down -v 2>&1 ``` ## 4. Build and Start ```bash -NAME=next-postgres-shadcn docker compose $COMPOSE_FILES up -d --build +docker compose --env-file .devcontainer/.env $COMPOSE_FILES up -d --build ``` This will: - Build the Docker image (Node.js 22, agent CLIs, procps) - Start PostgreSQL 16 (waits for healthcheck) - Start the sandbox container -- `entrypoint.sh` runs as root: installs cloudflared + agent-browser if env vars request it +- `entrypoint.sh` runs as root: starts cron, syncs heartbeats - `startup.sh` runs as sandbox: npm install, prisma generate, prisma migrate deploy, starts Next.js dev server + cloudflared tunnel, health-checks port 3000 ## 5. Wait for Startup @@ -65,13 +93,13 @@ Poll logs until `startup.sh` reports completion (up to 3 minutes): ```bash for i in $(seq 1 36); do - if docker logs next-postgres-shadcn 2>&1 | grep -q "Startup complete"; then + if docker logs "$SANDBOX_NAME" 2>&1 | grep -q "Startup complete"; then echo "Startup complete" break fi if [ "$i" -eq 36 ]; then echo "WARNING: Startup did not complete within 3 minutes" - docker logs next-postgres-shadcn 2>&1 | grep '\[startup\]\|\[entrypoint\]' | tail -10 + docker logs "$SANDBOX_NAME" 2>&1 | grep '\[startup\]\|\[entrypoint\]' | tail -10 fi sleep 5 done @@ -82,17 +110,17 @@ done ### 6a. Container Health ```bash -docker ps --filter "name=next-postgres-shadcn" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +docker ps --filter "name=$SANDBOX_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" ``` -Both `next-postgres-shadcn` and `next-postgres-shadcn-postgres` should be running. +Both `$SANDBOX_NAME` and `$SANDBOX_NAME-postgres` should be running. ### 6b. Run test:setup This runs 8 TypeScript tests (vitest) that validate the full stack: ```bash -docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && npm run test:setup' +docker exec -u sandbox "$SANDBOX_NAME" bash -c 'cd ~/harness/workspace/projects/next-app && npm run test:setup' ``` **All 8 tests must pass:** @@ -113,7 +141,7 @@ docker exec -u sandbox next-postgres-shadcn bash -c 'cd ~/workspace/next-app && Check logs and remediate: - `/tmp/next-dev.log` — Next.js dev server - `/tmp/cloudflared.log` — Cloudflare tunnel -- `docker logs next-postgres-shadcn` — entrypoint + startup +- `docker logs $SANDBOX_NAME` — entrypoint + startup Re-run `npm run test:setup` after fixing. Do not loop more than once. @@ -123,7 +151,7 @@ The sandbox generates an ED25519 keypair on first boot (persisted in the `ssh-ke Read the public key so the user can add it to GitHub / GitLab: ```bash -docker exec -u sandbox next-postgres-shadcn cat ~/.ssh/id_ed25519.pub +docker exec -u sandbox "$SANDBOX_NAME" cat ~/.ssh/id_ed25519.pub ``` Save this value for the report. diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 11b46ad..56c066b 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -47,7 +47,7 @@ git branch --show-current git status --porcelain # Run lint + type check + tests locally -cd workspace/next-app +cd workspace/projects/next-app npm run lint && npm run format:check && npm run type-check && npm test ``` diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f51c5ea..09e1b71 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,10 @@ FROM debian:bookworm-slim +ENV TZ=America/Denver + # ─── System packages ──────────────────────────────────────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ - openssh-server ca-certificates curl wget sudo git jq gnupg \ + openssh-server ca-certificates cron curl wget sudo gosu git jq gnupg \ lsb-release nano ripgrep tmux unzip bash-completion procps less \ && rm -rf /var/lib/apt/lists/* @@ -28,6 +30,14 @@ RUN install -m 0755 -d /etc/apt/keyrings \ && apt-get update && apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \ && rm -rf /var/lib/apt/lists/* +# ─── Cloudflared ─────────────────────────────────────────────────── +RUN curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \ + -o /usr/share/keyrings/cloudflare-main.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main" \ + > /etc/apt/sources.list.d/cloudflared.list \ + && apt-get update && apt-get install -y --no-install-recommends cloudflared \ + && rm -rf /var/lib/apt/lists/* + # ─── Bun ──────────────────────────────────────────────────────────── RUN BUN_INSTALL=/usr/local curl -fsSL https://bun.sh/install | bash @@ -36,35 +46,52 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1 && cp /root/.local/bin/uv /usr/local/bin/uv \ && cp /root/.local/bin/uvx /usr/local/bin/uvx -# ─── Claude Code CLI ──────────────────────────────────────────────── -RUN npm install -g @anthropic-ai/claude-code +# ─── Agent CLIs ───────────────────────────────────────────────────── +RUN npm install -g @anthropic-ai/claude-code @openai/codex @mariozechner/pi-coding-agent -# ─── OpenAI Codex CLI ────────────────────────────────────────────── -RUN npm install -g @openai/codex +# ─── Agent Browser + Chromium ────────────────────────────────────── +RUN npm install -g agent-browser && agent-browser install --with-deps -# ─── Orchestrator user ───────────────────────────────────────────── -RUN useradd -m -s /bin/bash orchestrator \ - && echo "orchestrator:test1234" | chpasswd \ - && echo "orchestrator ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/orchestrator \ - && chmod 0440 /etc/sudoers.d/orchestrator \ - && groupadd -f docker && usermod -aG docker orchestrator +# ─── Sandbox user ────────────────────────────────────────────────── +RUN useradd -m -s /bin/bash sandbox \ + && echo "sandbox:test1234" | chpasswd \ + && echo "sandbox ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/sandbox \ + && chmod 0440 /etc/sudoers.d/sandbox \ + && groupadd -f docker && usermod -aG docker sandbox + +# Shell aliases for agent CLIs +RUN echo "alias claude='claude --dangerously-skip-permissions'" >> /home/sandbox/.bashrc \ + && echo "alias codex='codex --full-auto'" >> /home/sandbox/.bashrc \ + && echo "alias pi='pi'" >> /home/sandbox/.bashrc # Git safe.directory for bind-mounted project -RUN su - orchestrator -c "git config --global --add safe.directory /home/orchestrator/project" +RUN su - sandbox -c "git config --global --add safe.directory /home/sandbox/harness" + +# Default working directory for interactive shells (SSH + terminal attach) +RUN echo 'cd ~/harness 2>/dev/null' >> /home/sandbox/.bashrc # Image-level devcontainer config — VS Code reads this on "Attach to Running Container" -RUN mkdir -p /.devcontainer && echo '{"workspaceFolder":"/home/orchestrator/project","remoteUser":"orchestrator"}' > /.devcontainer/devcontainer.json +RUN mkdir -p /.devcontainer && echo '{"workspaceFolder":"/home/sandbox/harness","remoteUser":"sandbox"}' > /.devcontainer/devcontainer.json + +# Labels for VS Code "Attach to Running Container" +LABEL devcontainer.metadata='[{"remoteUser":"sandbox","workspaceFolder":"/home/sandbox/harness"}]' # ─── SSH server ───────────────────────────────────────────────────── RUN mkdir -p /run/sshd \ && sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config \ && sed -i 's/#PermitUserEnvironment no/PermitUserEnvironment yes/' /etc/ssh/sshd_config +# ─── Copy install scripts + workspace (for CI; bind mount overrides at runtime) +COPY --chown=sandbox:sandbox install/ /home/sandbox/install/ +RUN chmod +x /home/sandbox/install/*.sh +COPY --chown=sandbox:sandbox .openharness/ /home/sandbox/.openharness/ +COPY --chown=sandbox:sandbox workspace/ /home/sandbox/harness/workspace/ + # ─── Entrypoint ──────────────────────────────────────────────────── COPY .devcontainer/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh -WORKDIR /home/orchestrator/project +WORKDIR /home/sandbox/harness EXPOSE 22 ENTRYPOINT ["entrypoint.sh"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1341ef1..6f2f55e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,9 @@ { - "name": "Open Harness Orchestrator", + "name": "Open Harness Sandbox", "dockerComposeFile": "docker-compose.yml", - "service": "orchestrator", - "workspaceFolder": "/home/orchestrator/project", - "remoteUser": "orchestrator", + "service": "sandbox", + "workspaceFolder": "/home/sandbox/harness", + "remoteUser": "sandbox", "forwardPorts": [2222], "customizations": { "vscode": { diff --git a/.devcontainer/docker-compose.cloudflared.yml b/.devcontainer/docker-compose.cloudflared.yml new file mode 100644 index 0000000..fb21a3f --- /dev/null +++ b/.devcontainer/docker-compose.cloudflared.yml @@ -0,0 +1,5 @@ +services: + sandbox: + environment: + - INSTALL_CLOUDFLARED=true + - INSTALL_BROWSER=true diff --git a/docker/docker-compose.docker.yml b/.devcontainer/docker-compose.docker.yml similarity index 100% rename from docker/docker-compose.docker.yml rename to .devcontainer/docker-compose.docker.yml diff --git a/docker/docker-compose.nextjs.yml b/.devcontainer/docker-compose.postgres.yml similarity index 88% rename from docker/docker-compose.nextjs.yml rename to .devcontainer/docker-compose.postgres.yml index 1e79ecb..7527411 100644 --- a/docker/docker-compose.nextjs.yml +++ b/.devcontainer/docker-compose.postgres.yml @@ -1,7 +1,7 @@ services: postgres: image: postgres:16-alpine - container_name: ${NAME}-postgres + container_name: ${SANDBOX_NAME:-sandbox}-postgres environment: POSTGRES_USER: sandbox POSTGRES_PASSWORD: sandbox @@ -20,20 +20,20 @@ services: depends_on: postgres: condition: service_healthy + ports: + - "${PORT:-3000}:3000" environment: - DATABASE_URL=postgresql://sandbox:sandbox@postgres:5432/sandbox - PGHOST=postgres - PGUSER=sandbox - PGPASSWORD=sandbox - PGDATABASE=sandbox - ports: - - "${PORT:-3000}:3000" networks: - devnet networks: devnet: - name: ${NAME}-devnet + name: ${SANDBOX_NAME:-sandbox}-devnet driver: bridge volumes: diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index b8fcbcd..d28f54c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,19 +1,36 @@ +name: ${SANDBOX_NAME:-sandbox} + services: - orchestrator: - container_name: orchestrator + sandbox: + container_name: ${SANDBOX_NAME:-sandbox} build: context: .. dockerfile: .devcontainer/Dockerfile ports: - "2222:22" volumes: - - ..:/home/orchestrator/project - - /var/run/docker.sock:/var/run/docker.sock - - ~/.claude:/home/orchestrator/.claude + - ..:/home/sandbox/harness + - ../.openharness:/home/sandbox/.openharness + - claude-auth:/home/sandbox/.claude + - cloudflared-auth:/home/sandbox/.cloudflared + - gh-config:/home/sandbox/.config/gh + - ssh-keys:/home/sandbox/.ssh extra_hosts: - "host.docker.internal:host-gateway" environment: + - TZ=${TZ:-America/Denver} - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true + - HEARTBEAT_ACTIVE_START=${HEARTBEAT_ACTIVE_START:-} + - HEARTBEAT_ACTIVE_END=${HEARTBEAT_ACTIVE_END:-} + - HEARTBEAT_AGENT=${HEARTBEAT_AGENT:-claude} + stdin_open: true + tty: true entrypoint: /usr/local/bin/entrypoint.sh command: /usr/sbin/sshd -D restart: unless-stopped + +volumes: + claude-auth: + cloudflared-auth: + gh-config: + ssh-keys: diff --git a/.devcontainer/entrypoint.sh b/.devcontainer/entrypoint.sh index 7376115..c950423 100644 --- a/.devcontainer/entrypoint.sh +++ b/.devcontainer/entrypoint.sh @@ -2,7 +2,7 @@ set -e # Match the container's docker group GID to the host socket's GID -# so the orchestrator user can use Docker without sudo. +# so the sandbox user can use Docker without sudo. SOCK=/var/run/docker.sock if [ -S "$SOCK" ]; then HOST_GID=$(stat -c '%g' "$SOCK") @@ -13,8 +13,29 @@ if [ -S "$SOCK" ]; then fi # Fix ownership of mounted .claude auth directory -if [ -d /home/orchestrator/.claude ]; then - chown -R orchestrator:orchestrator /home/orchestrator/.claude 2>/dev/null || true +if [ -d /home/sandbox/.claude ]; then + chown -R sandbox:sandbox /home/sandbox/.claude 2>/dev/null || true +fi + +# Generate SSH host keys if missing (needed for sshd to accept connections) +if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then + ssh-keygen -A +fi + +# Start cron daemon (needed for heartbeat scheduling) +if command -v cron &>/dev/null; then + service cron start 2>/dev/null || true +fi + +# Auto-sync heartbeat schedules from persistent config +if [ -f "/home/sandbox/harness/workspace/heartbeats.conf" ]; then + gosu sandbox /home/sandbox/install/heartbeat.sh sync 2>/dev/null || true +fi + +# Run workspace startup (dev server + tunnel) as sandbox user +STARTUP="/home/sandbox/harness/workspace/startup.sh" +if [ -f "$STARTUP" ]; then + gosu sandbox bash "$STARTUP" 2>&1 | sed 's/^/ /' || true fi exec "$@" diff --git a/.devcontainer/init-env.sh b/.devcontainer/init-env.sh new file mode 100755 index 0000000..b864e0c --- /dev/null +++ b/.devcontainer/init-env.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Resolve SANDBOX_NAME from git remote (repo name) or directory name. +# Writes .devcontainer/.env so docker compose picks it up. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +if git -C "$REPO_ROOT" remote get-url origin &>/dev/null; then + SANDBOX_NAME="$(basename -s .git "$(git -C "$REPO_ROOT" remote get-url origin)")" +else + SANDBOX_NAME="$(basename "$REPO_ROOT")" +fi + +echo "SANDBOX_NAME=$SANDBOX_NAME" > "$SCRIPT_DIR/.env" +echo "Resolved SANDBOX_NAME=$SANDBOX_NAME" diff --git a/.dockerignore b/.dockerignore index e300766..beb45eb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,4 @@ **/.env* -docker/Dockerfile* .claude/ .devcontainer/ !.devcontainer/entrypoint.sh diff --git a/.github/ISSUE_TEMPLATE/agent.md b/.github/ISSUE_TEMPLATE/agent.md index 36093d4..6b9570b 100644 --- a/.github/ISSUE_TEMPLATE/agent.md +++ b/.github/ISSUE_TEMPLATE/agent.md @@ -53,5 +53,5 @@ claude ### 3. Verify - [ ] Container is running (`make list`) -- [ ] Agent can access workspace (`ls ~/workspace`) +- [ ] Agent can access workspace (`ls ~/harness/workspace`) - [ ] SOUL.md and MEMORY.md are present diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72db7da..792535a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,6 @@ jobs: LATEST=ghcr.io/ryaneggz/open-harness:latest echo "Building: $IMAGE and $LATEST" - docker build -f docker/Dockerfile -t $IMAGE -t $LATEST . + docker build -f .devcontainer/Dockerfile -t $IMAGE -t $LATEST . docker push $IMAGE docker push $LATEST diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c9d175..fbf4398 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,17 +6,17 @@ on: - development - agent/next-postgres-shadcn paths: - - "workspace/next-app/src/**" - - "workspace/next-app/prisma/**" - - "workspace/next-app/public/**" - - "workspace/next-app/package.json" - - "workspace/next-app/package-lock.json" - - "workspace/next-app/tsconfig.json" - - "workspace/next-app/next.config.*" - - "workspace/next-app/tailwind.config.*" - - "workspace/next-app/postcss.config.*" - - "workspace/next-app/vitest.config.*" - - "workspace/next-app/playwright.config.*" + - "workspace/projects/next-app/src/**" + - "workspace/projects/next-app/prisma/**" + - "workspace/projects/next-app/public/**" + - "workspace/projects/next-app/package.json" + - "workspace/projects/next-app/package-lock.json" + - "workspace/projects/next-app/tsconfig.json" + - "workspace/projects/next-app/next.config.*" + - "workspace/projects/next-app/tailwind.config.*" + - "workspace/projects/next-app/postcss.config.*" + - "workspace/projects/next-app/vitest.config.*" + - "workspace/projects/next-app/playwright.config.*" - ".github/workflows/ci.yml" permissions: @@ -57,47 +57,47 @@ jobs: - name: Cache node_modules uses: actions/cache@v4 with: - path: workspace/next-app/node_modules - key: node-modules-${{ hashFiles('workspace/next-app/package-lock.json') }} + path: workspace/projects/next-app/node_modules + key: node-modules-${{ hashFiles('workspace/projects/next-app/package-lock.json') }} restore-keys: | node-modules- - name: Install dependencies - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm ci - name: Generate Prisma client - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npx prisma generate - name: Run migrations - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npx prisma migrate deploy - name: Lint - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run lint - name: Format check - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run format:check - name: Type check - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run type-check - name: Build - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run build - name: Test - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm test - name: Install Playwright browsers - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npx playwright install --with-deps chromium - name: E2E tests - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run test:e2e diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af202b6..194e585 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,35 +48,35 @@ jobs: node-version: "22" - name: Install dependencies - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm ci - name: Lint - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run lint - name: Format check - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run format:check - name: Type check - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run type-check - name: Generate Prisma client - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npx prisma generate - name: Run migrations - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npx prisma migrate deploy - name: Build - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm run build - name: Test - working-directory: workspace/next-app + working-directory: workspace/projects/next-app run: npm test - name: Set up Docker Buildx @@ -94,7 +94,7 @@ jobs: IMAGE=ghcr.io/ryaneggz/next-postgres-shadcn:${{ steps.parse.outputs.version }} LATEST=ghcr.io/ryaneggz/next-postgres-shadcn:latest - docker build -f docker/Dockerfile -t $IMAGE -t $LATEST . + docker build -f .devcontainer/Dockerfile -t $IMAGE -t $LATEST . docker push $IMAGE docker push $LATEST diff --git a/.gitignore b/.gitignore index 21f0b22..e27ca31 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ workspace/.ralph/ralph-*.log workspace/.ralph/.claude/ **/.next -**/.ralph/archives/ \ No newline at end of file +**/.ralph/archives/ +**/src/generated/ \ No newline at end of file diff --git a/.openharness/config.json b/.openharness/config.json index d457fd3..e61111e 100644 --- a/.openharness/config.json +++ b/.openharness/config.json @@ -1,3 +1,3 @@ { - "composeOverrides": ["docker/docker-compose.nextjs.yml", "docker/docker-compose.cloudflared.yml"] + "composeOverrides": [".devcontainer/docker-compose.postgres.yml", ".devcontainer/docker-compose.cloudflared.yml", ".devcontainer/docker-compose.docker.yml"] } diff --git a/.ralph/archive/2026-04-09-api-health/prd.json b/.ralph/archive/2026-04-09-api-health/prd.json deleted file mode 100644 index dc82c7d..0000000 --- a/.ralph/archive/2026-04-09-api-health/prd.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "project": "next-postgres-shadcn", - "branchName": "feat/4-api-health", - "description": "Add GET /api/health endpoint for runtime monitoring — returns 200 with JSON status including database connectivity, Next.js status, and process uptime. Returns 503 when PostgreSQL is unreachable.", - "userStories": [ - { - "id": "US-001", - "title": "Create /api/health route handler with basic response", - "description": "As a DevOps engineer, I need a GET /api/health endpoint that returns a JSON health check response so I can monitor the application's runtime status.", - "acceptanceCriteria": [ - "Create src/lib/prisma.ts with a singleton PrismaClient instance (import from @/generated/prisma)", - "Create src/app/api/health/route.ts with a named GET export", - "GET /api/health returns 200 with JSON: { status: 'ok', timestamp: ISO-8601 string, uptime: number (seconds since process start), services: { nextjs: 'ok' } }", - "Response Content-Type is application/json", - "Endpoint responds in < 500ms", - "Typecheck passes (npm run type-check)" - ], - "priority": 1, - "passes": true, - "notes": "Create src/lib/prisma.ts as a shared singleton — US-002 will use it for the database check. Prisma schema is empty but the generated client still provides $queryRaw and $connect. Use process.uptime() for the uptime field." - }, - { - "id": "US-002", - "title": "Add PostgreSQL health check via Prisma", - "description": "As a DevOps engineer, I need the health endpoint to verify database connectivity so I can detect PostgreSQL outages.", - "acceptanceCriteria": [ - "Health endpoint runs SELECT 1 via Prisma ($queryRawUnsafe or $queryRaw) to verify database connectivity", - "On success: services.database = 'ok', overall status = 'ok', HTTP 200", - "On failure: services.database = 'error', overall status = 'degraded', HTTP 503", - "Database check has a timeout so it does not hang indefinitely (< 500ms total response)", - "Typecheck passes (npm run type-check)" - ], - "priority": 2, - "passes": true, - "notes": "Import the PrismaClient singleton from @/lib/prisma. Use Prisma.$queryRawUnsafe('SELECT 1') wrapped in try/catch. Use AbortSignal.timeout or Promise.race for the timeout guard." - }, - { - "id": "US-003", - "title": "Add unit tests for the health endpoint", - "description": "As a developer, I need automated tests for the health endpoint to catch regressions.", - "acceptanceCriteria": [ - "Create src/app/api/health/__tests__/route.test.ts", - "Test: GET returns 200 with status 'ok' when database is reachable", - "Test: Response includes timestamp (valid ISO-8601), uptime (number >= 0), and services object", - "Test: GET returns 503 with status 'degraded' when database query throws", - "Test: services.database is 'error' when database is unreachable", - "Mock Prisma client to simulate both success and failure cases", - "All tests pass (npm test)", - "Typecheck passes (npm run type-check)" - ], - "priority": 3, - "passes": true, - "notes": "Mock @/lib/prisma using vi.mock. Use vi.fn() to control $queryRawUnsafe behavior per test. Invoke the GET handler directly — no HTTP server needed." - }, - { - "id": "US-FINAL", - "title": "Archive Ralph run, submit draft PR, and verify CI green", - "description": "Archive the Ralph run, submit all work as a draft PR with review docs, and confirm CI passes.", - "acceptanceCriteria": [ - "All previous stories have passes: true", - "Archive .ralph/prd.json and .ralph/progress.txt to .ralph/archive/2026-04-09-api-health/", - "Create feature branch: feat/4-api-health from agent/next-postgres-shadcn", - "Push all commits to the feature branch", - "Create draft PR to development with body containing: Closes #4 on its own line, ## Roadmap Context (Rank: 1, Category: product, Phase: now, Complexity: S, Signal: infrastructure), ## What, ## Why, ## How, ## Manual Review Steps, ## Acceptance Criteria", - "PR body MUST include 'Closes #4' so the issue auto-closes on merge", - "PR body MUST include Roadmap Context section matching the pinned roadmap", - "PR title: feat(#4): add /api/health endpoint for runtime monitoring", - "Run /ci-status after push — poll until CI completes", - "If CI fails: read failure logs, fix the issue, push again, re-poll until GREEN", - "This story is NOT complete until CI pipeline is GREEN" - ], - "priority": 999, - "passes": false, - "notes": "ALWAYS the last story. Archive MUST happen before PR. CI MUST be green before passes: true. PR body MUST have Closes #4 and Roadmap Context." - } - ] -} diff --git a/.ralph/archive/2026-04-09-api-health/progress.txt b/.ralph/archive/2026-04-09-api-health/progress.txt deleted file mode 100644 index f17a063..0000000 --- a/.ralph/archive/2026-04-09-api-health/progress.txt +++ /dev/null @@ -1,56 +0,0 @@ -# Ralph Progress Log -Started: Wed Apr 8 20:54:00 MDT 2026 ---- - -## Codebase Patterns -- Next.js app lives in `workspace/next-app/` inside the monorepo root (not at repo root `src/`) -- Prisma 7 requires `@prisma/adapter-pg` — cannot instantiate `PrismaClient()` without an adapter -- Generated Prisma client is in `src/generated/prisma/` but gitignored — run `npx prisma generate` before type-checking -- Import PrismaClient from `@/generated/prisma/client` (not `@/generated/prisma`) -- Quality checks: `npm run type-check`, `npm run lint`, `npm test` from `workspace/next-app/` -- Pre-commit hooks run lint-staged + build + tests for `cli/` and `packages/sandbox/` workspaces too -- Run `npx tsc --noEmit` from `workspace/next-app/` (nested), NOT from git root — path aliases and types only resolve from nested tsconfig -- Root `package.json` is monorepo config — install Next.js app deps in `workspace/next-app/package.json` -- Run `npx prisma generate` from `workspace/next-app/` where prisma.config.ts lives - -## 2026-04-08 21:01 UTC - US-001 -- Created `src/lib/prisma.ts` — PrismaClient singleton using `@prisma/adapter-pg` -- Created `src/app/api/health/route.ts` — GET handler returning `{ status, timestamp, uptime, services: { nextjs } }` -- Installed `@prisma/adapter-pg` dependency -- Files changed: `workspace/next-app/src/lib/prisma.ts`, `workspace/next-app/src/app/api/health/route.ts`, `workspace/next-app/package.json`, `workspace/next-app/package-lock.json` -- **Learnings for future iterations:** - - Repo is a monorepo — root `package.json` has workspaces `["cli", "packages/sandbox"]`, Next.js app is at `workspace/next-app/` - - Prisma 7 changed: `new PrismaClient()` no longer works — requires `{ adapter: new PrismaPg({ connectionString }) }` - - `src/generated/prisma/` is gitignored, must regenerate with `npx prisma generate` in a fresh checkout - - Pre-commit hooks validate ALL workspaces, not just workspace/next-app ---- - -## 2026-04-09 03:06 UTC - US-002 -- Added PostgreSQL health check via `prisma.$queryRawUnsafe("SELECT 1")` with Promise.race timeout guard (4s) -- On success: `services.database = "ok"`, status `"ok"`, HTTP 200 -- On failure: `services.database = "error"`, status `"degraded"`, HTTP 503 -- Files changed: `workspace/next-app/src/app/api/health/route.ts` -- **Learnings for future iterations:** - - Run `npx tsc --noEmit` from `workspace/next-app/` (nested dir), NOT from the git root — the `@/` alias and `next` types only resolve correctly from the nested tsconfig - - The git root is `/home/sandbox/workspace/next-app/` but the Next.js source lives at `workspace/next-app/src/` within it — all source file paths are relative to git root - - Root `package.json` is monorepo config only — do NOT install Next.js app deps there; they belong in `workspace/next-app/package.json` - - `npx prisma generate` must be run from `workspace/next-app/` where prisma.config.ts lives -- `workspace/` is gitignored — use `git add -f` to stage files under that path -- Mock Prisma with `vi.mock("@/lib/prisma", ...)` at module level; vitest hoists it -- Import Next.js route handlers directly for unit tests — no HTTP server needed ---- - -## 2026-04-09 03:09 UTC - US-003 -- Created `src/app/api/health/__tests__/route.test.ts` with 4 unit tests -- Test: GET returns 200 with status 'ok' when database is reachable -- Test: Response includes valid ISO-8601 timestamp, numeric uptime >= 0, and services object -- Test: GET returns 503 with status 'degraded' when database query throws -- Test: services.database is 'error' when database is unreachable -- Mocked `@/lib/prisma` using `vi.mock` to control `$queryRawUnsafe` behavior per test -- Files changed: `workspace/next-app/src/app/api/health/__tests__/route.test.ts` -- **Learnings for future iterations:** - - Use `vi.mock("@/lib/prisma", ...)` at module level to mock the Prisma singleton — vitest hoists it automatically - - Import the route handler directly (`import { GET } from "../route"`) — no HTTP server needed - - `workspace/` is gitignored — must use `git add -f` to stage files under that path - - Pre-commit hooks run cli/ and packages/sandbox/ workspaces too (build + test for each) ---- diff --git a/AGENTS.md b/AGENTS.md index 2330e26..a02db77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ A Next.js + TypeScript + PostgreSQL + shadcn/ui development workspace running in ## Working Directory -Your code lives in `workspace/next-app/`. Run all npm commands from there. +Your code lives in `workspace/projects/next-app/`. Run all npm commands from there. ## Identity & Context @@ -34,8 +34,8 @@ Your code lives in `workspace/next-app/`. Run all npm commands from there. | Dev server | `npm run dev` (port 3000, binds `0.0.0.0`) | | Public URL | `https://next-postgres-shadcn.ruska.dev` | | Database | PostgreSQL 16 — `sandbox:sandbox@postgres:5432/sandbox` | -| Prisma schema | `workspace/next-app/prisma/schema.prisma` | -| UI components | `workspace/next-app/src/components/ui/` (shadcn) | +| Prisma schema | `workspace/projects/next-app/prisma/schema.prisma` | +| UI components | `workspace/projects/next-app/src/components/ui/` (shadcn) | | Tests | `npm test` (Vitest) / `npm run test:e2e` (Playwright) | | Tunnel start | `cloudflared tunnel --config ~/.cloudflared/config-next-postgres-shadcn.yml run next-postgres-shadcn` | @@ -43,11 +43,12 @@ Your code lives in `workspace/next-app/`. Run all npm commands from there. ``` workspace/ - next-app/ # Next.js project - src/app/ # App Router routes - src/components/ # React components (ui/ for shadcn) - src/lib/ # Utilities - prisma/ # Database schema & migrations + projects/ + next-app/ # Next.js project + src/app/ # App Router routes + src/components/ # React components (ui/ for shadcn) + src/lib/ # Utilities + prisma/ # Database schema & migrations .claude/skills/ # Slash command skills (see below) .claude/agents/ # Sub-agent definitions (implementer, critic, pm, council) .claude/rules/ # Coding standards + workflow rules @@ -65,7 +66,17 @@ workspace/ ## Skills -Available as slash commands inside the workspace: +### Host-level skills (`.claude/skills/`) + +| Skill | Trigger | Purpose | +|-------|---------|---------| +| `/delegate` | Decomposing a plan into parallel tasks | Decompose plan into tasks, spawn parallel worker agents in waves | +| `/diagnose` | After crashes, failed builds, broken stack | Diagnose and fix the full stack (container, deps, Prisma, DB, dev server, tunnel) | +| `/release` | Cutting a new version | Cut a CalVer release (branch, tag, push, CI builds + pushes to GHCR) | +| `/destroy` | Tearing down the sandbox | Tear down sandbox (stop containers, remove volumes) | +| `/provision` | Provisioning or rebuilding the sandbox | Build image, start services, validate with test:setup | + +### Workspace-level skills (`workspace/.claude/skills/`) | Skill | Trigger | Purpose | |-------|---------|---------| @@ -73,18 +84,20 @@ Available as slash commands inside the workspace: | `/agent-browser` | QA, verifying UI, taking screenshots | Navigate, interact with, and screenshot the app via headless browser | | `/prd` | Planning a feature, writing requirements | Generate a Product Requirements Document | | `/ralph` | Converting a PRD for autonomous loop | Convert PRD to `.ralph/prd.json` format | -| `/issue-triage` | Heartbeat (hourly) or manual triage | Check for unassigned issues, spawn sub-agents, council review, draft PR | +| `/issue-triage` | Heartbeat (hourly) or manual triage | Triage unassigned GitHub issues with parallel sub-agents + council, draft PR | | `/quality-gate` | Before consequential actions | Template: validate decisions against thresholds | | `/strategy-review` | Periodic self-assessment | Template: measure decision quality over time | +| `/backlog-rank` | Prioritizing open issues | Rank open issues by PM criteria, update pinned backlog tracking issue | +| `/strategic-proposal` | Building or refreshing the product roadmap | Spawn 5 experts + AI council, produce signal-validated product roadmap | +| `/implement` | Picking up the next roadmap item | Pick top validated roadmap item, run Ralph loop in tmux, submit draft PR | ## Infrastructure (do not modify) | Directory | Purpose | |-----------|---------| -| `docker/` | Dockerfile + compose files (base + nextjs overlay for PostgreSQL + port 3000) | +| `.devcontainer/` | Dockerfile, compose files (base + cloudflared/docker overlays), entrypoint | | `install/` | Provisioning scripts (setup.sh, heartbeat.sh, cloudflared-tunnel.sh) | | `.openharness/` | Compose overrides config (`config.json`) | -| `.devcontainer/` | Optional VS Code Dev Container | ## Git Workflow diff --git a/README.md b/README.md index 62fb315..a4045f0 100644 --- a/README.md +++ b/README.md @@ -78,14 +78,15 @@ ssh orchestrator@localhost -p 2222 # password: test1234 ├── cli/ # openharness CLI ├── packages/sandbox/ # @openharness/sandbox tools └── workspace/ # Agent harness (persisted via bind mount) - ├── next-app/ # Next.js project - │ ├── src/app/ # App Router routes (/, /roadmap) - │ ├── src/components/ # React components (ui/, landing/, roadmap/) - │ ├── src/data/ # Typed data (roadmap.ts) - │ ├── src/lib/ # Utilities + guard helpers - │ ├── src/test/ # Vitest tests (35 tests) - │ ├── prisma/ # Database schema & migrations - │ └── public/ # Static assets + PWA manifest + ├── projects/ + │ └── next-app/ # Next.js project + │ ├── src/app/ # App Router routes (/, /roadmap) + │ ├── src/components/ # React components (ui/, landing/, roadmap/) + │ ├── src/data/ # Typed data (roadmap.ts) + │ ├── src/lib/ # Utilities + guard helpers + │ ├── src/test/ # Vitest tests (35 tests) + │ ├── prisma/ # Database schema & migrations + │ └── public/ # Static assets + PWA manifest ├── .claude/ # Agent config │ ├── skills/ # 12 skills (slash commands) │ ├── agents/ # 11 sub-agents (experts, council, critic) @@ -122,7 +123,7 @@ gh auth login ### ⚙️ Environment Variables -Set in `next-app/.env` or container env: +Set in `projects/next-app/.env` or container env: | Variable | Default | Description | |----------|---------|-------------| @@ -147,7 +148,7 @@ Set in `next-app/.env` or container env: ### 🚀 Start Developing ```bash -cd workspace/next-app +cd workspace/projects/next-app npm run dev # Dev server on port 3000 cloudflared tunnel --config ~/.cloudflared/config-next-postgres-shadcn.yml run next-postgres-shadcn # Expose publicly (optional) claude # Start AI agent @@ -199,6 +200,7 @@ Host-level skills run from the orchestrator (outside the container): | `/provision` | Build + start the sandbox with all compose overlays, wait for startup, validate with `test:setup` | | `/provision --rebuild` | Full teardown (volumes included) then provision from scratch | | `/diagnose` | Run 8 health checks (env, deps, Prisma, DB, dev server, tunnel, public URL), auto-fix failures | +| `/delegate` | Decompose plan into tasks, spawn parallel worker agents in waves | | `/release` | Cut a CalVer release — create `release/YYYY.M.D-N` branch, tag, push to trigger CI + GHCR build | | `/release --dry-run` | Pre-flight checks only — show version and test results without releasing | | `/destroy` | Tear down containers + volumes, optionally prune Docker image | diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 92fdb2f..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM debian:bookworm-slim - -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates cron curl wget sudo gosu gnupg jq procps \ - && rm -rf /var/lib/apt/lists/* - -ENV TZ=America/Denver - -# Node.js 22.x -RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* - -# Agent CLIs -RUN npm install -g @anthropic-ai/claude-code @openai/codex @mariozechner/pi-coding-agent - -RUN useradd -m -s /bin/bash sandbox \ - && echo "sandbox ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/sandbox \ - && groupadd -f docker && usermod -aG docker sandbox \ - && echo "alias claude='claude --dangerously-skip-permissions'" >> /home/sandbox/.bashrc \ - && echo "alias codex='codex --full-auto'" >> /home/sandbox/.bashrc \ - && echo "alias pi='pi'" >> /home/sandbox/.bashrc - -COPY install/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh -COPY --chown=sandbox:sandbox install/ /home/sandbox/install/ -RUN chmod +x /home/sandbox/install/*.sh -COPY --chown=sandbox:sandbox .openharness/ /home/sandbox/.openharness/ -COPY --chown=sandbox:sandbox workspace/ /home/sandbox/workspace/ - -WORKDIR /home/sandbox/workspace -ENTRYPOINT ["entrypoint.sh"] -CMD ["bash"] diff --git a/docker/docker-compose.cloudflared.yml b/docker/docker-compose.cloudflared.yml deleted file mode 100644 index ec15f30..0000000 --- a/docker/docker-compose.cloudflared.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - sandbox: - environment: - - INSTALL_CLOUDFLARED=true - - INSTALL_BROWSER=true - volumes: - - cloudflared:/home/sandbox/.cloudflared - - gh-config:/home/sandbox/.config/gh - - ssh-keys:/home/sandbox/.ssh - -volumes: - cloudflared: - gh-config: - ssh-keys: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index ce267a4..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -services: - sandbox: - container_name: ${NAME} - build: - context: .. - dockerfile: docker/Dockerfile - volumes: - - ../workspace:/home/sandbox/workspace - - ../.openharness:/home/sandbox/.openharness - - ${HOME}/.claude:/home/sandbox/.claude - stdin_open: true - tty: true - environment: - - TZ=${TZ:-America/Denver} - - HEARTBEAT_ACTIVE_START=${HEARTBEAT_ACTIVE_START:-} - - HEARTBEAT_ACTIVE_END=${HEARTBEAT_ACTIVE_END:-} - - HEARTBEAT_AGENT=${HEARTBEAT_AGENT:-claude} diff --git a/install/entrypoint.sh b/install/entrypoint.sh index 1c0df2f..960c5d2 100755 --- a/install/entrypoint.sh +++ b/install/entrypoint.sh @@ -23,7 +23,7 @@ if command -v cron &>/dev/null; then fi # Auto-sync heartbeat schedules from persistent config -if [ -f "/home/sandbox/workspace/heartbeats.conf" ]; then +if [ -f "/home/sandbox/harness/workspace/heartbeats.conf" ]; then gosu sandbox /home/sandbox/install/heartbeat.sh sync 2>/dev/null || true fi @@ -55,7 +55,7 @@ if [ "${INSTALL_BROWSER:-false}" = "true" ] && ! command -v agent-browser &>/dev fi # Run workspace startup (dev server + tunnel) as sandbox user -STARTUP="/home/sandbox/workspace/startup.sh" +STARTUP="/home/sandbox/harness/workspace/startup.sh" if [ -f "$STARTUP" ]; then gosu sandbox bash "$STARTUP" 2>&1 | sed 's/^/ /' || true fi diff --git a/install/heartbeat.sh b/install/heartbeat.sh index aa11f1b..aedf344 100755 --- a/install/heartbeat.sh +++ b/install/heartbeat.sh @@ -9,7 +9,7 @@ set -euo pipefail HEARTBEAT_DIR="${HOME}/.heartbeat" LOG_FILE="${HEARTBEAT_DIR}/heartbeat.log" ENV_FILE="${HEARTBEAT_DIR}/env.sh" -WORKSPACE="${HOME}/workspace" +WORKSPACE="${HOME}/harness/workspace" CONFIG_FILE="${WORKSPACE}/heartbeats.conf" LEGACY_FILE="${WORKSPACE}/HEARTBEAT.md" # backward compat for users who haven't migrated SOUL_FILE="${SOUL_FILE:-${WORKSPACE}/SOUL.md}" @@ -422,7 +422,7 @@ cmd_migrate() { # Format: | | [agent] | [active_start-active_end] # # - cron-expression: Standard 5-field cron (min hour dom mon dow) -# - file-path: Relative to ~/workspace/ +# - file-path: Relative to ~/harness/workspace/ # - agent: (optional) Override HEARTBEAT_AGENT env var. Default: ${HEARTBEAT_AGENT} # - active_start-active_end: (optional) Hours (0-23). Only run during this window. # diff --git a/install/setup.sh b/install/setup.sh index 0824e24..157515d 100644 --- a/install/setup.sh +++ b/install/setup.sh @@ -288,7 +288,7 @@ ok "Done" banner "Setup complete" printf "\n" printf " ${CYAN}Sandbox user${NC}: $SANDBOX_USER\n" -printf " ${CYAN}Workspace${NC}: $SANDBOX_HOME/workspace\n" +printf " ${CYAN}Workspace${NC}: $SANDBOX_HOME/harness/workspace\n" printf "\n" printf " ${CYAN}Installed tools${NC}\n" printf " ──────────────────────────────────────\n" @@ -335,7 +335,7 @@ fi printf "\n" # ─── 18. Run workspace startup script ─────────────────────────── -STARTUP="$SANDBOX_HOME/workspace/startup.sh" +STARTUP="$SANDBOX_HOME/harness/workspace/startup.sh" if [ -f "$STARTUP" ]; then banner "Running workspace startup" su - "$SANDBOX_USER" -c "bash $STARTUP" diff --git a/packages/sandbox/src/__tests__/config.test.ts b/packages/sandbox/src/__tests__/config.test.ts index 0d5dc8d..a7904f3 100644 --- a/packages/sandbox/src/__tests__/config.test.ts +++ b/packages/sandbox/src/__tests__/config.test.ts @@ -86,8 +86,8 @@ describe("SandboxConfig", () => { it("derives dockerfile and compose paths from projectRoot", () => { const config = new SandboxConfig({ name: "test" }); const root = config.projectRoot; - expect(config.dockerfilePath).toBe(`${root}/docker/Dockerfile`); - expect(config.composeFile).toBe(`${root}/docker/docker-compose.yml`); - expect(config.composeDockerFile).toBe(`${root}/docker/docker-compose.docker.yml`); + expect(config.dockerfilePath).toBe(`${root}/.devcontainer/Dockerfile`); + expect(config.composeFile).toBe(`${root}/.devcontainer/docker-compose.yml`); + expect(config.composeDockerFile).toBe(`${root}/.devcontainer/docker-compose.docker.yml`); }); }); diff --git a/packages/sandbox/src/__tests__/docker.test.ts b/packages/sandbox/src/__tests__/docker.test.ts index b39b12b..9160c09 100644 --- a/packages/sandbox/src/__tests__/docker.test.ts +++ b/packages/sandbox/src/__tests__/docker.test.ts @@ -99,9 +99,9 @@ describe("docker command builders", () => { }); it("adds -w workdir flag", () => { - const cmd = execCmd("my-sandbox", ["ls"], { workdir: "/home/sandbox/workspace" }); + const cmd = execCmd("my-sandbox", ["ls"], { workdir: "/home/sandbox/harness/workspace" }); expect(cmd).toContain("-w"); - expect(cmd).toContain("/home/sandbox/workspace"); + expect(cmd).toContain("/home/sandbox/harness/workspace"); }); it("adds -e env flags", () => { diff --git a/packages/sandbox/src/lib/config.ts b/packages/sandbox/src/lib/config.ts index d603796..74c07d0 100644 --- a/packages/sandbox/src/lib/config.ts +++ b/packages/sandbox/src/lib/config.ts @@ -45,7 +45,7 @@ export class SandboxConfig { get projectRoot(): string { const worktree = this.worktreePath; - if (existsSync(resolve(process.cwd(), worktree, "docker", "Dockerfile"))) { + if (existsSync(resolve(process.cwd(), worktree, ".devcontainer", "Dockerfile"))) { return worktree; } return "."; @@ -56,14 +56,14 @@ export class SandboxConfig { } get dockerfilePath(): string { - return `${this.projectRoot}/docker/Dockerfile`; + return `${this.projectRoot}/.devcontainer/Dockerfile`; } get composeFile(): string { - return `${this.projectRoot}/docker/docker-compose.yml`; + return `${this.projectRoot}/.devcontainer/docker-compose.yml`; } get composeDockerFile(): string { - return `${this.projectRoot}/docker/docker-compose.docker.yml`; + return `${this.projectRoot}/.devcontainer/docker-compose.docker.yml`; } } diff --git a/packages/sandbox/src/tools/ralph.ts b/packages/sandbox/src/tools/ralph.ts index b69bbab..68d1fd5 100644 --- a/packages/sandbox/src/tools/ralph.ts +++ b/packages/sandbox/src/tools/ralph.ts @@ -48,9 +48,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - 'cd ~/workspace && claude --dangerously-skip-permissions -p "Read the plan files in .claude/plans/ and generate a PRD using the /prd skill. Save to tasks/"', + 'cd ~/harness/workspace && claude --dangerously-skip-permissions -p "Read the plan files in .claude/plans/ and generate a PRD using the /prd skill. Save to tasks/"', ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(cmd); steps.push("PRD generated. Check tasks/ for the output."); @@ -64,9 +64,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - 'cd ~/workspace && claude --dangerously-skip-permissions -p "Convert the latest PRD in tasks/ to .ralph/prd.json using the /ralph skill"', + 'cd ~/harness/workspace && claude --dangerously-skip-permissions -p "Convert the latest PRD in tasks/ to .ralph/prd.json using the /ralph skill"', ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(convertCmd); @@ -76,9 +76,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace && git add tasks/ .ralph/prd.json .ralph/progress.txt 2>/dev/null; git commit -m "task: add PRD and prd.json for ralph" && git push -u origin HEAD && gh pr create --draft --base development --title "feat: $(jq -r .description .ralph/prd.json | head -c 60)" --body "PRD and prd.json ready for Ralph execution" 2>&1 || echo "PR may already exist"`, + `cd ~/harness/workspace && git add tasks/ .ralph/prd.json .ralph/progress.txt 2>/dev/null; git commit -m "task: add PRD and prd.json for ralph" && git push -u origin HEAD && gh pr create --draft --base development --title "feat: $(jq -r .description .ralph/prd.json | head -c 60)" --body "PRD and prd.json ready for Ralph execution" 2>&1 || echo "PR may already exist"`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(prCmd); steps.push("Draft PR created. Ready for ralph run."); @@ -92,9 +92,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `tmux new-session -d -s ralph "cd ~/workspace && .ralph/ralph.sh --tool claude ${iterations}"`, + `tmux new-session -d -s ralph "cd ~/harness/workspace && .ralph/ralph.sh --tool claude ${iterations}"`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(cmd); steps.push("Ralph is running in tmux session 'ralph'."); @@ -108,9 +108,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace && echo "=== Stories ===" && jq ".userStories[] | {id, title, passes}" .ralph/prd.json 2>/dev/null || echo "No prd.json found" && echo "" && echo "=== Progress (last 20 lines) ===" && tail -20 .ralph/progress.txt 2>/dev/null || echo "No progress.txt found" && echo "" && echo "=== tmux ===" && tmux has-session -t ralph 2>/dev/null && echo "Ralph tmux session: RUNNING" || echo "Ralph tmux session: NOT RUNNING"`, + `cd ~/harness/workspace && echo "=== Stories ===" && jq ".userStories[] | {id, title, passes}" .ralph/prd.json 2>/dev/null || echo "No prd.json found" && echo "" && echo "=== Progress (last 20 lines) ===" && tail -20 .ralph/progress.txt 2>/dev/null || echo "No progress.txt found" && echo "" && echo "=== tmux ===" && tmux has-session -t ralph 2>/dev/null && echo "Ralph tmux session: RUNNING" || echo "Ralph tmux session: NOT RUNNING"`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(cmd); break; @@ -123,9 +123,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace && claude --dangerously-skip-permissions -p "Read .ralph/progress.txt and .ralph/prd.json. Focus on: what would make the NEXT session better? Update MEMORY.md with actionable patterns, approaches to reuse, mistakes to avoid, and codebase insights. Append a session log to memory/$(date +%Y-%m-%d).md. Commit memory updates."`, + `cd ~/harness/workspace && claude --dangerously-skip-permissions -p "Read .ralph/progress.txt and .ralph/prd.json. Focus on: what would make the NEXT session better? Update MEMORY.md with actionable patterns, approaches to reuse, mistakes to avoid, and codebase insights. Append a session log to memory/$(date +%Y-%m-%d).md. Commit memory updates."`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(cmd); steps.push("Memory updated with session reflections."); @@ -139,9 +139,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace/next-app && npm run lint:fix 2>&1; npm run format 2>&1; npm run type-check 2>&1; npm test 2>&1; cd ~/workspace && git add -A && git diff --cached --quiet || git commit -m "task: cleanup before PR submission"`, + `cd ~/harness/workspace/projects/next-app && npm run lint:fix 2>&1; npm run format 2>&1; npm run type-check 2>&1; npm test 2>&1; cd ~/harness/workspace && git add -A && git diff --cached --quiet || git commit -m "task: cleanup before PR submission"`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(cmd); steps.push("Cleanup complete."); @@ -155,9 +155,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace && BRANCH=$(jq -r .branchName .ralph/prd.json 2>/dev/null | sed 's|ralph/||') && mkdir -p .ralph/archive/$BRANCH && cp .ralph/prd.json .ralph/progress.txt .ralph/archive/$BRANCH/ 2>/dev/null && git add .ralph/archive/ && git commit -m "task: archive ralph run for $BRANCH" 2>/dev/null || true`, + `cd ~/harness/workspace && BRANCH=$(jq -r .branchName .ralph/prd.json 2>/dev/null | sed 's|ralph/||') && mkdir -p .ralph/archive/$BRANCH && cp .ralph/prd.json .ralph/progress.txt .ralph/archive/$BRANCH/ 2>/dev/null && git add .ralph/archive/ && git commit -m "task: archive ralph run for $BRANCH" 2>/dev/null || true`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(archiveCmd); @@ -167,9 +167,9 @@ export const ralphTool: ToolDefinition = { [ "bash", "-c", - `cd ~/workspace && ALL_PASS=$(jq "[.userStories[].passes] | all" .ralph/prd.json 2>/dev/null) && if [ "$ALL_PASS" = "true" ]; then git push && gh pr ready && echo "PR is now ready for review!"; else echo "Not all stories pass yet. Run 'openharness ralph status ${name}' to check."; exit 1; fi`, + `cd ~/harness/workspace && ALL_PASS=$(jq "[.userStories[].passes] | all" .ralph/prd.json 2>/dev/null) && if [ "$ALL_PASS" = "true" ]; then git push && gh pr ready && echo "PR is now ready for review!"; else echo "Not all stories pass yet. Run 'openharness ralph status ${name}' to check."; exit 1; fi`, ], - { user: "sandbox", workdir: "/home/sandbox/workspace" }, + { user: "sandbox", workdir: "/home/sandbox/harness/workspace" }, ); run(prCmd); steps.push("PR taken out of draft."); diff --git a/packages/sandbox/src/tools/shell.ts b/packages/sandbox/src/tools/shell.ts index 3cc21f0..69bd63e 100644 --- a/packages/sandbox/src/tools/shell.ts +++ b/packages/sandbox/src/tools/shell.ts @@ -17,7 +17,7 @@ export const shellTool: ToolDefinition = { const cmd = execCmd(name, ["bash", "--login"], { user: "sandbox", interactive: true, - workdir: "/home/sandbox/workspace", + workdir: "/home/sandbox/harness/workspace", env: { HOME: "/home/sandbox" }, }); diff --git a/workspace/.claude/rules/api.md b/workspace/.claude/rules/api.md index 24a3990..9d98426 100644 --- a/workspace/.claude/rules/api.md +++ b/workspace/.claude/rules/api.md @@ -1,6 +1,6 @@ --- paths: - - "next-app/src/app/api/**/*" + - "projects/next-app/src/app/api/**/*" --- # API Routes diff --git a/workspace/.claude/rules/components.md b/workspace/.claude/rules/components.md index 59707a8..d1b3fbf 100644 --- a/workspace/.claude/rules/components.md +++ b/workspace/.claude/rules/components.md @@ -1,6 +1,6 @@ --- paths: - - "next-app/src/components/**/*.tsx" + - "projects/next-app/src/components/**/*.tsx" --- # Components diff --git a/workspace/.claude/rules/nextjs.md b/workspace/.claude/rules/nextjs.md index 6fd4de1..47a09a3 100644 --- a/workspace/.claude/rules/nextjs.md +++ b/workspace/.claude/rules/nextjs.md @@ -1,7 +1,7 @@ --- paths: - - "next-app/**/*.{ts,tsx}" - - "next-app/next.config.ts" + - "projects/next-app/**/*.{ts,tsx}" + - "projects/next-app/next.config.ts" --- # Next.js + Vercel Best Practices diff --git a/workspace/.claude/rules/prisma.md b/workspace/.claude/rules/prisma.md index 0df8a05..15b92d6 100644 --- a/workspace/.claude/rules/prisma.md +++ b/workspace/.claude/rules/prisma.md @@ -1,7 +1,7 @@ --- paths: - - "next-app/prisma/**/*" - - "next-app/prisma.config.ts" + - "projects/next-app/prisma/**/*" + - "projects/next-app/prisma.config.ts" --- # Prisma & Database diff --git a/workspace/.claude/rules/styles.md b/workspace/.claude/rules/styles.md index 7de3058..0a6a2b0 100644 --- a/workspace/.claude/rules/styles.md +++ b/workspace/.claude/rules/styles.md @@ -1,7 +1,7 @@ --- paths: - - "next-app/src/**/*.css" - - "next-app/postcss.config.mjs" + - "projects/next-app/src/**/*.css" + - "projects/next-app/postcss.config.mjs" --- # Styles & Theming diff --git a/workspace/.claude/rules/testing.md b/workspace/.claude/rules/testing.md index 62facb5..f262cfc 100644 --- a/workspace/.claude/rules/testing.md +++ b/workspace/.claude/rules/testing.md @@ -1,10 +1,10 @@ --- paths: - - "next-app/src/**/*.test.{ts,tsx}" - - "next-app/src/**/*.spec.{ts,tsx}" - - "next-app/src/e2e/**/*" - - "next-app/vitest.config.ts" - - "next-app/playwright.config.ts" + - "projects/next-app/src/**/*.test.{ts,tsx}" + - "projects/next-app/src/**/*.spec.{ts,tsx}" + - "projects/next-app/src/e2e/**/*" + - "projects/next-app/vitest.config.ts" + - "projects/next-app/playwright.config.ts" --- # Testing diff --git a/workspace/.claude/settings.local.json b/workspace/.claude/settings.local.json index 73400bf..0537772 100644 --- a/workspace/.claude/settings.local.json +++ b/workspace/.claude/settings.local.json @@ -5,7 +5,7 @@ "hooks": [ { "type": "command", - "command": "bash ~/workspace/.claude/hooks/notify_slack.sh" + "command": "bash $CLAUDE_CODE_PROJECT_DIR/.claude/hooks/notify_slack.sh" } ] } @@ -16,7 +16,7 @@ "hooks": [ { "type": "command", - "command": "bash ~/workspace/.claude/hooks/notify_slack.sh" + "command": "bash $CLAUDE_CODE_PROJECT_DIR/.claude/hooks/notify_slack.sh" } ] }, @@ -25,7 +25,7 @@ "hooks": [ { "type": "command", - "command": "bash ~/workspace/.claude/hooks/notify_slack.sh" + "command": "bash $CLAUDE_CODE_PROJECT_DIR/.claude/hooks/notify_slack.sh" } ] } diff --git a/workspace/.claude/skills/ci-status/SKILL.md b/workspace/.claude/skills/ci-status/SKILL.md index 0259634..d1b18f6 100644 --- a/workspace/.claude/skills/ci-status/SKILL.md +++ b/workspace/.claude/skills/ci-status/SKILL.md @@ -86,6 +86,6 @@ This project's CI (`CI: next-postgres-shadcn`) runs these steps in order: Before pushing, you can run the same checks locally to catch issues early: ```bash -cd workspace/next-app +cd workspace/projects/next-app npm run lint && npm run format:check && npm run type-check && npx prisma generate && npm run build && npm test ``` diff --git a/workspace/.claude/skills/implement/SKILL.md b/workspace/.claude/skills/implement/SKILL.md index 2068976..d482067 100644 --- a/workspace/.claude/skills/implement/SKILL.md +++ b/workspace/.claude/skills/implement/SKILL.md @@ -203,7 +203,7 @@ Set its priority to `max(existing priorities) + 100` to ensure it's always last. Run this from within the container (via `docker exec`), using `gosu sandbox` to drop from root to the sandbox user — Claude Code refuses `--dangerously-skip-permissions` when running as root: ```bash -tmux new-session -d -s ralph -c /home/sandbox/workspace \ +tmux new-session -d -s ralph -c /home/sandbox/harness/workspace \ "gosu sandbox bash -c 'cd .ralph && HOME=/home/sandbox ./ralph.sh --tool claude 15 2>&1 | tee ralph-\$(date +%Y%m%d-%H%M).log'" ``` diff --git a/workspace/.ralph/AGENTS.md b/workspace/.ralph/AGENTS.md index fbc7ba4..4ff76a5 100644 --- a/workspace/.ralph/AGENTS.md +++ b/workspace/.ralph/AGENTS.md @@ -107,16 +107,16 @@ If there are still stories with `passes: false`, end your response normally (ano - You are working in an EXISTING git checkout. NEVER run `git clone`. NEVER run `git init`. - To create a feature branch: `git checkout -b feat/-` from the current branch -- NEVER change the working directory above `workspace/next-app/` for git operations -- If you see files like `cli/`, `docker/`, `install/`, `packages/` appearing in `workspace/next-app/`, STOP — something has gone wrong with your git operations -- All source code changes happen under `workspace/next-app/src/` — never write files outside this path -- The git root is the monorepo root, NOT `workspace/next-app/` — be aware of this when running git commands +- NEVER change the working directory above `workspace/projects/next-app/` for git operations +- If you see files like `cli/`, `docker/`, `install/`, `packages/` appearing in `workspace/projects/next-app/`, STOP — something has gone wrong with your git operations +- All source code changes happen under `workspace/projects/next-app/src/` — never write files outside this path +- The git root is the monorepo root, NOT `workspace/projects/next-app/` — be aware of this when running git commands ## Pre-Submit Validation Before marking US-FINAL as `passes: true`, you MUST verify ALL of these: -1. `npm run build` succeeds from `workspace/next-app/` (full Next.js build, not just type-check) +1. `npm run build` succeeds from `workspace/projects/next-app/` (full Next.js build, not just type-check) 2. Dev server is running: `curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/` returns `200`. If not running, start with `npm run dev &` and wait for ready. 3. Public URL: `curl -s -o /dev/null -w '%{http_code}' https://next-postgres-shadcn.ruska.dev/` returns `200`. If tunnel is down, this is a BLOCKER — do not mark passes: true. 4. Archive exists: `ls .ralph/archives/YYYY-MM-DD//prd.json` succeeds. If not, create the archive now: `mkdir -p .ralph/archives/$(date +%Y-%m-%d)/ && mv prd.json progress.txt .ralph/archives/$(date +%Y-%m-%d)//` diff --git a/workspace/.ralph/progress.txt b/workspace/.ralph/progress.txt index ce34904..1be1578 100644 --- a/workspace/.ralph/progress.txt +++ b/workspace/.ralph/progress.txt @@ -9,8 +9,8 @@ Started: Wed Apr 8 22:33:27 MDT 2026 - **Learnings for future iterations:** - Next.js Metadata API merges page-level metadata with layout metadata — only override what's different - OG image URL must be absolute (include full domain) for social platforms to fetch it - - The git root is the monorepo root, not workspace/next-app/ — npm commands run from nested dir - - Dependencies must be installed in workspace/next-app/ (nested) not the monorepo root + - The git root is the monorepo root, not workspace/projects/next-app/ — npm commands run from nested dir + - Dependencies must be installed in workspace/projects/next-app/ (nested) not the monorepo root - Chrome/agent-browser not available in this sandbox — use curl to verify HTML output instead --- diff --git a/workspace/AGENTS.md b/workspace/AGENTS.md index 445aeef..8f817b4 100644 --- a/workspace/AGENTS.md +++ b/workspace/AGENTS.md @@ -4,7 +4,7 @@ 1. Read IDENTITY.md, USER.md, and MEMORY.md at session start 2. Work within `workspace/` — it persists across container restarts -3. The Next.js project lives in `next-app/` — run all npm commands from there +3. The Next.js project lives in `projects/next-app/` — run all npm commands from there 4. Do not modify `~/install/` — those are provisioning scripts 5. Coding standards live in `.claude/rules/` — they load automatically 6. After every `git push`, run `/ci-status` to confirm CI is green @@ -72,6 +72,7 @@ Available as slash commands (`.claude/skills/`): | `/diagnose` | Diagnose and fix the full stack — run tests, auto-remediate, re-verify (`npm run test:setup`) | | `/release` | Cut a CalVer release — branch `release/YYYY.M.D-N`, tag, push, CI builds + pushes to GHCR | | `/destroy` | Tear down sandbox — stop containers, remove volumes, optionally prune image | +| `/delegate` | Decompose plan into tasks, spawn parallel worker agents in waves | | `/agent-browser` | QA features, take screenshots, debug UI at `next-postgres-shadcn.ruska.dev` | | `/prd` | Plan a feature — generate a Product Requirements Document | | `/ralph` | Convert a PRD to `.ralph/prd.json` for the autonomous agent loop | @@ -80,6 +81,7 @@ Available as slash commands (`.claude/skills/`): | `/backlog-rank` | Rank open issues by PM criteria, update pinned backlog | | `/strategic-proposal` | Spawn 5 experts + AI council, produce signal-validated product roadmap | | `/implement` | Pick top validated roadmap item, run Ralph loop in tmux, submit draft PR | +| `/issue-triage` | Triage unassigned GitHub issues with parallel sub-agents + council | **Important:** After every `git push`, run `/ci-status` to confirm CI is green. Work is not done until CI passes. diff --git a/workspace/TOOLS.md b/workspace/TOOLS.md index c95dc75..65c695c 100644 --- a/workspace/TOOLS.md +++ b/workspace/TOOLS.md @@ -4,7 +4,7 @@ - **OS**: Debian Bookworm (slim) - **User**: `sandbox` (passwordless sudo) -- **Working directory**: `/home/sandbox/workspace` (persisted via bind mount) +- **Working directory**: `/home/sandbox/harness/workspace` (persisted via bind mount) - **Docker**: CLI + Compose available; host Docker socket mounted for container management - **Node.js**: 22.x required (enforced via `.nvmrc` and `engines`) - **Permissions**: `--dangerously-skip-permissions` is the default for Claude Code (aliased in `.bashrc`) @@ -55,7 +55,7 @@ ## Common Workflows ```bash -cd next-app +cd projects/next-app npm run dev # Dev server (port 3000, 0.0.0.0) npx shadcn@latest add button # Add shadcn component diff --git a/workspace/heartbeats.conf b/workspace/heartbeats.conf index 9c097c1..1bc118d 100644 --- a/workspace/heartbeats.conf +++ b/workspace/heartbeats.conf @@ -3,7 +3,7 @@ # Format: | | [agent] | [active_start-active_end] # # - cron-expression: Standard 5-field cron (min hour dom mon dow) -# - file-path: Relative to ~/workspace/ +# - file-path: Relative to ~/harness/workspace/ # - agent: (optional) Override HEARTBEAT_AGENT env var. Default: claude # - active_start-active_end: (optional) Hours (0-23). Only run during this window. # diff --git a/workspace/memory/2026-04-09.md b/workspace/memory/2026-04-09.md new file mode 100644 index 0000000..550fc35 --- /dev/null +++ b/workspace/memory/2026-04-09.md @@ -0,0 +1,105 @@ +# 2026-04-09 Build Health Check + +**Time:** ~09:30 UTC +**Result:** ALL PASS + +| Check | Status | +|-------|--------| +| `npm run build` | Pass — Next.js 16.2.2 (Turbopack), compiled in 1370ms | +| `npm test` | Pass — 6 test files, 52 tests, 1.28s | +| `npm run type-check` | Pass — no errors | +| `npm run lint` | Pass — no errors | +| PostgreSQL | Pass — confirmed via `/api/health` endpoint (psql CLI not installed) | + +Note: `psql` CLI is not available in this environment, but DB connectivity confirmed through the running app's health endpoint (`{"status":"ok","services":{"database":"ok"}}`). + +## Build Health — 10:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed +- **Duration**: ~30s +- **Observation**: All 52 tests passing, build compiles in ~1.3s — project is stable + +## Build Health — 11:00 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB confirmed via /api/health +- **Duration**: ~15s +- **Observation**: Stable — 52/52 tests, clean build (1.3s), no type or lint errors + +## Build Health — 12:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql/no DATABASE_URL) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (1.4s), no type or lint errors + +## Build Health — 13:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql CLI) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (1.3s), no type or lint errors + +## Build Health — 15:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no .env / no DATABASE_URL set) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (2.1s), no type or lint errors + +## Build Health — 16:00 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB confirmed via /api/health +- **Duration**: ~15s +- **Observation**: Stable — 52/52 tests, clean build (1.7s), no type or lint errors + +## Build Health — 16:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql CLI, no DATABASE_URL) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (1.4s), no type or lint errors + +## Build Health — 17:00 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql CLI) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (2.3s), no type or lint errors + +## Build Health — 17:30 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql CLI) +- **Duration**: ~25s +- **Observation**: Stable — 52/52 tests, clean build (1.9s), no type or lint errors + +## Build Health — 18:00 UTC +- **Result**: OP +- **Item**: none +- **Action**: Ran full build health check — build, test, type-check, lint all passed; DB skip (no psql CLI) +- **Duration**: ~20s +- **Observation**: Stable — 52/52 tests, clean build (1.9s), no type or lint errors + +## Implement Heartbeat — 18:30 UTC +- **Result**: NO-OP +- **Item**: none +- **Action**: Checked roadmap — #4 closed, #5 has open PR #8. No validated items to implement. +- **Duration**: ~10s +- **Observation**: Roadmap exhausted; new items needed from /strategic-proposal before implement heartbeat can act. + +## Implement Heartbeat — 19:00 UTC +- **Result**: NO-OP +- **Item**: none +- **Action**: Checked roadmap and environment — #4 closed, #5 has open PR #8. Git worktree broken, gh CLI not authed. No validated items to implement. +- **Duration**: ~10s +- **Observation**: Same as prior run — roadmap exhausted, environment also degraded (broken worktree ref). + +## Issue Triage — 19:30 UTC +- **Result**: NO-OP +- **Item**: none +- **Action**: Cannot query GitHub issues — `gh` CLI not authenticated (no GH_TOKEN, no `gh auth login`) +- **Duration**: ~5s +- **Observation**: Recurring blocker — gh CLI auth missing in this sandbox session. Same issue noted in implement heartbeat at 19:00. diff --git a/workspace/memory/2026-04-10.md b/workspace/memory/2026-04-10.md new file mode 100644 index 0000000..680d177 --- /dev/null +++ b/workspace/memory/2026-04-10.md @@ -0,0 +1,15 @@ +# 2026-04-10 + +## Issue Triage — 01:01 UTC +- **Result**: NO-OP +- **Item**: none +- **Action**: Cannot query GitHub issues — `gh` CLI not authenticated (no GH_TOKEN, no `gh auth login`). No git repo initialized in workspace either. +- **Duration**: ~10s +- **Observation**: Recurring blocker — same as 2026-04-09 19:30 UTC. GitHub auth must be configured before issue-triage can operate. + +## Issue Triage — 2026-04-10 (heartbeat) +- **Result**: SKIP +- **Item**: none +- **Action**: `gh` CLI still not authenticated — no GH_TOKEN env var, no `gh auth login`. Cannot query issues. +- **Duration**: ~5s +- **Observation**: Third consecutive run blocked by missing GitHub auth. This is the persistent blocker for all GitHub-dependent heartbeats. diff --git a/workspace/next-app/.husky/pre-commit b/workspace/projects/next-app/.husky/pre-commit similarity index 100% rename from workspace/next-app/.husky/pre-commit rename to workspace/projects/next-app/.husky/pre-commit diff --git a/workspace/next-app/.prettierrc b/workspace/projects/next-app/.prettierrc similarity index 100% rename from workspace/next-app/.prettierrc rename to workspace/projects/next-app/.prettierrc diff --git a/workspace/next-app/README.md b/workspace/projects/next-app/README.md similarity index 98% rename from workspace/next-app/README.md rename to workspace/projects/next-app/README.md index 6cea98a..5e7c6f6 100644 --- a/workspace/next-app/README.md +++ b/workspace/projects/next-app/README.md @@ -39,7 +39,7 @@ The agent will: ## Development ```bash -cd workspace/next-app +cd workspace/projects/next-app npm run dev # Dev server on 0.0.0.0:3000 npm test # Vitest (unit/integration) npm run test:e2e # Playwright E2E diff --git a/workspace/next-app/components.json b/workspace/projects/next-app/components.json similarity index 100% rename from workspace/next-app/components.json rename to workspace/projects/next-app/components.json diff --git a/workspace/next-app/eslint.config.mjs b/workspace/projects/next-app/eslint.config.mjs similarity index 100% rename from workspace/next-app/eslint.config.mjs rename to workspace/projects/next-app/eslint.config.mjs diff --git a/workspace/next-app/next-env.d.ts b/workspace/projects/next-app/next-env.d.ts similarity index 100% rename from workspace/next-app/next-env.d.ts rename to workspace/projects/next-app/next-env.d.ts diff --git a/workspace/next-app/next-pwa.d.ts b/workspace/projects/next-app/next-pwa.d.ts similarity index 100% rename from workspace/next-app/next-pwa.d.ts rename to workspace/projects/next-app/next-pwa.d.ts diff --git a/workspace/next-app/next.config.ts b/workspace/projects/next-app/next.config.ts similarity index 87% rename from workspace/next-app/next.config.ts rename to workspace/projects/next-app/next.config.ts index 000ad4d..900a354 100644 --- a/workspace/next-app/next.config.ts +++ b/workspace/projects/next-app/next.config.ts @@ -7,7 +7,9 @@ const withPWA = withPWAInit({ }); const nextConfig: NextConfig = { - turbopack: {}, + turbopack: { + root: process.cwd(), + }, allowedDevOrigins: ["next-postgres-shadcn.ruska.dev"], }; diff --git a/workspace/next-app/package-lock.json b/workspace/projects/next-app/package-lock.json similarity index 99% rename from workspace/next-app/package-lock.json rename to workspace/projects/next-app/package-lock.json index fe51234..e29deb8 100644 --- a/workspace/next-app/package-lock.json +++ b/workspace/projects/next-app/package-lock.json @@ -136,7 +136,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1785,7 +1784,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -1834,7 +1832,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -2024,8 +2021,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -3251,7 +3247,6 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -3369,7 +3364,6 @@ "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright": "1.59.1" }, @@ -3962,6 +3956,29 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", @@ -4658,7 +4675,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/chai": { "version": "5.2.3", @@ -4683,6 +4701,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4693,6 +4712,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -4758,7 +4778,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4769,7 +4788,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4846,7 +4864,6 @@ "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", @@ -5510,6 +5527,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -5519,25 +5537,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5548,13 +5570,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5567,6 +5591,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -5576,6 +5601,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -5584,13 +5610,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5607,6 +5635,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -5620,6 +5649,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5632,6 +5662,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5646,6 +5677,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -5655,13 +5687,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/accepts": { "version": "2.0.0", @@ -5681,7 +5715,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5694,6 +5727,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" }, @@ -5725,7 +5759,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6280,7 +6313,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -6500,6 +6532,7 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -7291,7 +7324,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dotenv": { "version": "16.6.1", @@ -7679,7 +7713,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7881,7 +7914,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -8148,6 +8180,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -8214,7 +8247,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8906,7 +8938,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/globals": { "version": "14.0.0", @@ -9111,7 +9144,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -10549,6 +10581,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" }, @@ -10760,6 +10793,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11101,7 +11135,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/next": { "version": "16.2.2", @@ -11822,7 +11857,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -12238,6 +12272,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12253,6 +12288,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -12281,7 +12317,6 @@ "integrity": "sha512-HlgwRBt1uEFB9LStHL4HLYDvoi4BNu1rYA0hPG0zCAEyK9SaZBqp7E5Rjpc3Qh8Lex/ye/svoHZ0OWoFNhWxuQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.7.0", "@prisma/dev": "0.24.3", @@ -12488,7 +12523,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12498,7 +12532,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12511,7 +12544,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/readdirp": { "version": "4.1.2", @@ -12834,7 +12868,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -14046,7 +14079,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14417,7 +14449,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14712,7 +14743,6 @@ "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -14915,6 +14945,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -14996,6 +15027,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -15022,6 +15054,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -15039,6 +15072,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -15051,6 +15085,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -15064,6 +15099,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -15072,13 +15108,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -15088,6 +15126,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15100,6 +15139,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -15354,7 +15394,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15883,7 +15922,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/workspace/next-app/package.json b/workspace/projects/next-app/package.json similarity index 94% rename from workspace/next-app/package.json rename to workspace/projects/next-app/package.json index e5c6b54..985004c 100644 --- a/workspace/next-app/package.json +++ b/workspace/projects/next-app/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "private": true, "scripts": { + "predev": "prisma generate", "dev": "next dev -H 0.0.0.0", + "prebuild": "prisma generate", "build": "next build", "start": "next start", "lint": "eslint . --ext .ts,.tsx", @@ -15,6 +17,7 @@ "test:watch": "vitest", "test:setup": "vitest run --config vitest.setup.config.ts", "test:e2e": "playwright test", + "prisma:generate": "prisma generate", "prepare": "husky" }, "lint-staged": { diff --git a/workspace/next-app/playwright.config.ts b/workspace/projects/next-app/playwright.config.ts similarity index 100% rename from workspace/next-app/playwright.config.ts rename to workspace/projects/next-app/playwright.config.ts diff --git a/workspace/next-app/postcss.config.mjs b/workspace/projects/next-app/postcss.config.mjs similarity index 100% rename from workspace/next-app/postcss.config.mjs rename to workspace/projects/next-app/postcss.config.mjs diff --git a/workspace/next-app/prisma.config.ts b/workspace/projects/next-app/prisma.config.ts similarity index 100% rename from workspace/next-app/prisma.config.ts rename to workspace/projects/next-app/prisma.config.ts diff --git a/workspace/next-app/prisma/schema.prisma b/workspace/projects/next-app/prisma/schema.prisma similarity index 100% rename from workspace/next-app/prisma/schema.prisma rename to workspace/projects/next-app/prisma/schema.prisma diff --git a/workspace/next-app/public/file.svg b/workspace/projects/next-app/public/file.svg similarity index 100% rename from workspace/next-app/public/file.svg rename to workspace/projects/next-app/public/file.svg diff --git a/workspace/next-app/public/globe.svg b/workspace/projects/next-app/public/globe.svg similarity index 100% rename from workspace/next-app/public/globe.svg rename to workspace/projects/next-app/public/globe.svg diff --git a/workspace/next-app/public/icon-192x192.svg b/workspace/projects/next-app/public/icon-192x192.svg similarity index 100% rename from workspace/next-app/public/icon-192x192.svg rename to workspace/projects/next-app/public/icon-192x192.svg diff --git a/workspace/next-app/public/icon-512x512.svg b/workspace/projects/next-app/public/icon-512x512.svg similarity index 100% rename from workspace/next-app/public/icon-512x512.svg rename to workspace/projects/next-app/public/icon-512x512.svg diff --git a/workspace/next-app/public/manifest.json b/workspace/projects/next-app/public/manifest.json similarity index 100% rename from workspace/next-app/public/manifest.json rename to workspace/projects/next-app/public/manifest.json diff --git a/workspace/next-app/public/next.svg b/workspace/projects/next-app/public/next.svg similarity index 100% rename from workspace/next-app/public/next.svg rename to workspace/projects/next-app/public/next.svg diff --git a/workspace/next-app/public/og-image.png b/workspace/projects/next-app/public/og-image.png similarity index 100% rename from workspace/next-app/public/og-image.png rename to workspace/projects/next-app/public/og-image.png diff --git a/workspace/next-app/public/vercel.svg b/workspace/projects/next-app/public/vercel.svg similarity index 100% rename from workspace/next-app/public/vercel.svg rename to workspace/projects/next-app/public/vercel.svg diff --git a/workspace/next-app/public/window.svg b/workspace/projects/next-app/public/window.svg similarity index 100% rename from workspace/next-app/public/window.svg rename to workspace/projects/next-app/public/window.svg diff --git a/workspace/next-app/src/app/api/health/__tests__/route.test.ts b/workspace/projects/next-app/src/app/api/health/__tests__/route.test.ts similarity index 100% rename from workspace/next-app/src/app/api/health/__tests__/route.test.ts rename to workspace/projects/next-app/src/app/api/health/__tests__/route.test.ts diff --git a/workspace/next-app/src/app/api/health/route.ts b/workspace/projects/next-app/src/app/api/health/route.ts similarity index 100% rename from workspace/next-app/src/app/api/health/route.ts rename to workspace/projects/next-app/src/app/api/health/route.ts diff --git a/workspace/next-app/src/app/favicon.ico b/workspace/projects/next-app/src/app/favicon.ico similarity index 100% rename from workspace/next-app/src/app/favicon.ico rename to workspace/projects/next-app/src/app/favicon.ico diff --git a/workspace/next-app/src/app/globals.css b/workspace/projects/next-app/src/app/globals.css similarity index 100% rename from workspace/next-app/src/app/globals.css rename to workspace/projects/next-app/src/app/globals.css diff --git a/workspace/next-app/src/app/icon.svg b/workspace/projects/next-app/src/app/icon.svg similarity index 100% rename from workspace/next-app/src/app/icon.svg rename to workspace/projects/next-app/src/app/icon.svg diff --git a/workspace/next-app/src/app/layout.tsx b/workspace/projects/next-app/src/app/layout.tsx similarity index 100% rename from workspace/next-app/src/app/layout.tsx rename to workspace/projects/next-app/src/app/layout.tsx diff --git a/workspace/next-app/src/app/page.tsx b/workspace/projects/next-app/src/app/page.tsx similarity index 100% rename from workspace/next-app/src/app/page.tsx rename to workspace/projects/next-app/src/app/page.tsx diff --git a/workspace/next-app/src/app/roadmap/page.tsx b/workspace/projects/next-app/src/app/roadmap/page.tsx similarity index 100% rename from workspace/next-app/src/app/roadmap/page.tsx rename to workspace/projects/next-app/src/app/roadmap/page.tsx diff --git a/workspace/next-app/src/components/landing/copy-button.tsx b/workspace/projects/next-app/src/components/landing/copy-button.tsx similarity index 100% rename from workspace/next-app/src/components/landing/copy-button.tsx rename to workspace/projects/next-app/src/components/landing/copy-button.tsx diff --git a/workspace/next-app/src/components/landing/faq.tsx b/workspace/projects/next-app/src/components/landing/faq.tsx similarity index 100% rename from workspace/next-app/src/components/landing/faq.tsx rename to workspace/projects/next-app/src/components/landing/faq.tsx diff --git a/workspace/next-app/src/components/landing/features.tsx b/workspace/projects/next-app/src/components/landing/features.tsx similarity index 100% rename from workspace/next-app/src/components/landing/features.tsx rename to workspace/projects/next-app/src/components/landing/features.tsx diff --git a/workspace/next-app/src/components/landing/footer.tsx b/workspace/projects/next-app/src/components/landing/footer.tsx similarity index 100% rename from workspace/next-app/src/components/landing/footer.tsx rename to workspace/projects/next-app/src/components/landing/footer.tsx diff --git a/workspace/next-app/src/components/landing/hero.tsx b/workspace/projects/next-app/src/components/landing/hero.tsx similarity index 100% rename from workspace/next-app/src/components/landing/hero.tsx rename to workspace/projects/next-app/src/components/landing/hero.tsx diff --git a/workspace/next-app/src/components/landing/navbar.tsx b/workspace/projects/next-app/src/components/landing/navbar.tsx similarity index 100% rename from workspace/next-app/src/components/landing/navbar.tsx rename to workspace/projects/next-app/src/components/landing/navbar.tsx diff --git a/workspace/next-app/src/components/landing/quick-start.tsx b/workspace/projects/next-app/src/components/landing/quick-start.tsx similarity index 100% rename from workspace/next-app/src/components/landing/quick-start.tsx rename to workspace/projects/next-app/src/components/landing/quick-start.tsx diff --git a/workspace/next-app/src/components/landing/tech-stack.tsx b/workspace/projects/next-app/src/components/landing/tech-stack.tsx similarity index 100% rename from workspace/next-app/src/components/landing/tech-stack.tsx rename to workspace/projects/next-app/src/components/landing/tech-stack.tsx diff --git a/workspace/next-app/src/components/landing/theme-toggle.tsx b/workspace/projects/next-app/src/components/landing/theme-toggle.tsx similarity index 100% rename from workspace/next-app/src/components/landing/theme-toggle.tsx rename to workspace/projects/next-app/src/components/landing/theme-toggle.tsx diff --git a/workspace/next-app/src/components/roadmap/roadmap-card.tsx b/workspace/projects/next-app/src/components/roadmap/roadmap-card.tsx similarity index 100% rename from workspace/next-app/src/components/roadmap/roadmap-card.tsx rename to workspace/projects/next-app/src/components/roadmap/roadmap-card.tsx diff --git a/workspace/next-app/src/components/theme-provider.tsx b/workspace/projects/next-app/src/components/theme-provider.tsx similarity index 100% rename from workspace/next-app/src/components/theme-provider.tsx rename to workspace/projects/next-app/src/components/theme-provider.tsx diff --git a/workspace/next-app/src/components/ui/accordion.tsx b/workspace/projects/next-app/src/components/ui/accordion.tsx similarity index 100% rename from workspace/next-app/src/components/ui/accordion.tsx rename to workspace/projects/next-app/src/components/ui/accordion.tsx diff --git a/workspace/next-app/src/components/ui/badge.tsx b/workspace/projects/next-app/src/components/ui/badge.tsx similarity index 100% rename from workspace/next-app/src/components/ui/badge.tsx rename to workspace/projects/next-app/src/components/ui/badge.tsx diff --git a/workspace/next-app/src/components/ui/button.tsx b/workspace/projects/next-app/src/components/ui/button.tsx similarity index 100% rename from workspace/next-app/src/components/ui/button.tsx rename to workspace/projects/next-app/src/components/ui/button.tsx diff --git a/workspace/next-app/src/components/ui/card.tsx b/workspace/projects/next-app/src/components/ui/card.tsx similarity index 100% rename from workspace/next-app/src/components/ui/card.tsx rename to workspace/projects/next-app/src/components/ui/card.tsx diff --git a/workspace/next-app/src/components/ui/separator.tsx b/workspace/projects/next-app/src/components/ui/separator.tsx similarity index 100% rename from workspace/next-app/src/components/ui/separator.tsx rename to workspace/projects/next-app/src/components/ui/separator.tsx diff --git a/workspace/next-app/src/data/roadmap.ts b/workspace/projects/next-app/src/data/roadmap.ts similarity index 100% rename from workspace/next-app/src/data/roadmap.ts rename to workspace/projects/next-app/src/data/roadmap.ts diff --git a/workspace/next-app/src/e2e/home.spec.ts b/workspace/projects/next-app/src/e2e/home.spec.ts similarity index 100% rename from workspace/next-app/src/e2e/home.spec.ts rename to workspace/projects/next-app/src/e2e/home.spec.ts diff --git a/workspace/next-app/src/lib/implement-guards.ts b/workspace/projects/next-app/src/lib/implement-guards.ts similarity index 100% rename from workspace/next-app/src/lib/implement-guards.ts rename to workspace/projects/next-app/src/lib/implement-guards.ts diff --git a/workspace/next-app/src/lib/prisma.ts b/workspace/projects/next-app/src/lib/prisma.ts similarity index 100% rename from workspace/next-app/src/lib/prisma.ts rename to workspace/projects/next-app/src/lib/prisma.ts diff --git a/workspace/next-app/src/lib/utils.ts b/workspace/projects/next-app/src/lib/utils.ts similarity index 100% rename from workspace/next-app/src/lib/utils.ts rename to workspace/projects/next-app/src/lib/utils.ts diff --git a/workspace/next-app/src/test/implement-guards.test.ts b/workspace/projects/next-app/src/test/implement-guards.test.ts similarity index 100% rename from workspace/next-app/src/test/implement-guards.test.ts rename to workspace/projects/next-app/src/test/implement-guards.test.ts diff --git a/workspace/next-app/src/test/metadata.test.ts b/workspace/projects/next-app/src/test/metadata.test.ts similarity index 100% rename from workspace/next-app/src/test/metadata.test.ts rename to workspace/projects/next-app/src/test/metadata.test.ts diff --git a/workspace/next-app/src/test/ralph-prd.test.ts b/workspace/projects/next-app/src/test/ralph-prd.test.ts similarity index 100% rename from workspace/next-app/src/test/ralph-prd.test.ts rename to workspace/projects/next-app/src/test/ralph-prd.test.ts diff --git a/workspace/next-app/src/test/roadmap-data.test.ts b/workspace/projects/next-app/src/test/roadmap-data.test.ts similarity index 100% rename from workspace/next-app/src/test/roadmap-data.test.ts rename to workspace/projects/next-app/src/test/roadmap-data.test.ts diff --git a/workspace/next-app/src/test/roadmap-page.test.tsx b/workspace/projects/next-app/src/test/roadmap-page.test.tsx similarity index 100% rename from workspace/next-app/src/test/roadmap-page.test.tsx rename to workspace/projects/next-app/src/test/roadmap-page.test.tsx diff --git a/workspace/next-app/src/test/setup-check.test.ts b/workspace/projects/next-app/src/test/setup-check.test.ts similarity index 100% rename from workspace/next-app/src/test/setup-check.test.ts rename to workspace/projects/next-app/src/test/setup-check.test.ts diff --git a/workspace/next-app/src/test/setup.ts b/workspace/projects/next-app/src/test/setup.ts similarity index 100% rename from workspace/next-app/src/test/setup.ts rename to workspace/projects/next-app/src/test/setup.ts diff --git a/workspace/next-app/tsconfig.json b/workspace/projects/next-app/tsconfig.json similarity index 100% rename from workspace/next-app/tsconfig.json rename to workspace/projects/next-app/tsconfig.json diff --git a/workspace/next-app/vitest.config.ts b/workspace/projects/next-app/vitest.config.ts similarity index 100% rename from workspace/next-app/vitest.config.ts rename to workspace/projects/next-app/vitest.config.ts diff --git a/workspace/next-app/vitest.setup.config.ts b/workspace/projects/next-app/vitest.setup.config.ts similarity index 100% rename from workspace/next-app/vitest.setup.config.ts rename to workspace/projects/next-app/vitest.setup.config.ts diff --git a/workspace/startup.sh b/workspace/startup.sh index f6ae69a..8c708e0 100644 --- a/workspace/startup.sh +++ b/workspace/startup.sh @@ -3,7 +3,7 @@ # and at end of setup.sh on fresh provision set -euo pipefail -APP_DIR="$HOME/workspace/next-app" +APP_DIR="$HOME/harness/workspace/projects/next-app" LOG_PREFIX="[startup]" MAX_RETRIES=30 RETRY_INTERVAL=2