simpler arch around devcontainer
This commit is contained in:
1 parent
c81610e52c
commit
7fcad63d38
116 files changed
+1132
-423
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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` |
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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 "$@"
|
||||
Executable
+16
@@ -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,5 +1,4 @@
|
||||
**/.env*
|
||||
docker/Dockerfile*
|
||||
.claude/
|
||||
.devcontainer/
|
||||
!.devcontainer/entrypoint.sh
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -24,4 +24,5 @@ workspace/.ralph/ralph-*.log
|
||||
workspace/.ralph/.claude/
|
||||
|
||||
**/.next
|
||||
**/.ralph/archives/
|
||||
**/.ralph/archives/
|
||||
**/src/generated/
|
||||
@@ -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)
|
||||
---
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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"]
|
||||
@@ -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:
|
||||
@@ -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}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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", () => {
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
---
|
||||
paths:
|
||||
- "next-app/src/app/api/**/*"
|
||||
- "projects/next-app/src/app/api/**/*"
|
||||
---
|
||||
|
||||
# API Routes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
paths:
|
||||
- "next-app/src/components/**/*.tsx"
|
||||
- "projects/next-app/src/components/**/*.tsx"
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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'"
|
||||
```
|
||||
|
||||
|
||||
@@ -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>/`
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
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.
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"],
|
||||
};
|
||||
|
||||
+78
-40
@@ -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": {
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user