simpler arch around devcontainer

This commit is contained in:
ryaneggz committed 2026-04-09 20:40:17 -06:00
1 parent c81610e52c
commit 7fcad63d38
116 files changed
+1132 -423

No files matched your search

+38
View File
@@ -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
+318
View File
@@ -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
+241
View File
@@ -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 <path>] [--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 <path>` 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 <path>` 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**: "<plan title or source>"
- **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` |
+15 -9
View File
@@ -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
+16 -7
View File
@@ -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
+42 -14
View File
@@ -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.
+1 -1
View File
@@ -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
```
+41 -14
View File
@@ -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"]
+4 -4
View File
@@ -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": {
@@ -0,0 +1,5 @@
services:
sandbox:
environment:
- INSTALL_CLOUDFLARED=true
- INSTALL_BROWSER=true
File renamed without changes.
@@ -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:
+22 -5
View File
@@ -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:
+24 -3
View File
@@ -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 "$@"
+16
View File
@@ -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"
-1
View File
@@ -1,5 +1,4 @@
**/.env*
docker/Dockerfile*
.claude/
.devcontainer/
!.devcontainer/entrypoint.sh
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+23 -23
View File
@@ -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
+9 -9
View File
@@ -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
+2 -1
View File
@@ -24,4 +24,5 @@ workspace/.ralph/ralph-*.log
workspace/.ralph/.claude/
**/.next
**/.ralph/archives/
**/.ralph/archives/
**/src/generated/
+1 -1
View File
@@ -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"]
}
@@ -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."
}
]
}
@@ -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)
---
+25 -12
View File
@@ -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
+12 -10
View File
@@ -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 |
-33
View File
@@ -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"]
-14
View File
@@ -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:
-17
View File
@@ -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}
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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: <cron-expression> | <file-path> | [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.
#
+2 -2
View File
@@ -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"
@@ -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`);
});
});
@@ -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", () => {
+4 -4
View File
@@ -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`;
}
}
+18 -18
View File
@@ -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.");
+1 -1
View File
@@ -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" },
});
+1 -1
View File
@@ -1,6 +1,6 @@
---
paths:
- "next-app/src/app/api/**/*"
- "projects/next-app/src/app/api/**/*"
---
# API Routes
+1 -1
View File
@@ -1,6 +1,6 @@
---
paths:
- "next-app/src/components/**/*.tsx"
- "projects/next-app/src/components/**/*.tsx"
---
# Components
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+5 -5
View File
@@ -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
+3 -3
View File
@@ -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"
}
]
}
+1 -1
View File
@@ -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
```
+1 -1
View File
@@ -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'"
```
+5 -5
View File
@@ -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/<N>-<shortdesc>` 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/<feature>/prd.json` succeeds. If not, create the archive now: `mkdir -p .ralph/archives/$(date +%Y-%m-%d)/<feature> && mv prd.json progress.txt .ralph/archives/$(date +%Y-%m-%d)/<feature>/`
+2 -2
View File
@@ -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
---
+3 -1
View File
@@ -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.
+2 -2
View File
@@ -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
+1 -1
View File
@@ -3,7 +3,7 @@
# Format: <cron-expression> | <file-path> | [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.
#
+105
View File
@@ -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.
+15
View File
@@ -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.
File renamed without changes.
@@ -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
File renamed without changes.
File renamed without changes.
@@ -7,7 +7,9 @@ const withPWA = withPWAInit({
});
const nextConfig: NextConfig = {
turbopack: {},
turbopack: {
root: process.cwd(),
},
allowedDevOrigins: ["next-postgres-shadcn.ruska.dev"],
};
@@ -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"
}
@@ -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": {
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more