Skip to content

Commit 2fdbae6

Browse files
mariomeyerclaude
andcommitted
feat: restructure as multi-stack monorepo with shared CLI
Move Laravel stack files into stacks/laravel/, rename CLI to @reyemtech/stack-upgrade with multi-stack detection, multi-upgrade queue, and parallel container/pod launching. Add npm-publish job and per-stack matrix builds to CI. Rewrite README and CLAUDE.md for multi-stack architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8c02084 commit 2fdbae6

30 files changed

Lines changed: 1239 additions & 173 deletions

.dockerignore

Lines changed: 0 additions & 5 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ on:
1111

1212
env:
1313
REGISTRY: ghcr.io
14-
IMAGE_NAME: ${{ github.repository }}
1514

1615
jobs:
1716
release:
@@ -43,7 +42,29 @@ jobs:
4342
env:
4443
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4544

46-
# Build each platform natively (no QEMU emulation)
45+
npm-publish:
46+
needs: [release]
47+
if: needs.release.outputs.new_release_published == 'true'
48+
runs-on: ubuntu-latest
49+
permissions:
50+
contents: read
51+
steps:
52+
- uses: actions/checkout@v4
53+
54+
- uses: actions/setup-node@v4
55+
with:
56+
node-version: 22
57+
registry-url: https://registry.npmjs.org
58+
59+
- name: Publish CLI to npm
60+
working-directory: cli
61+
run: |
62+
npm version ${{ needs.release.outputs.new_release_version }} --no-git-tag-version
63+
npm publish --access public
64+
env:
65+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
66+
67+
# Build each platform natively per stack (no QEMU emulation)
4768
build:
4869
needs: [release]
4970
if: |
@@ -55,6 +76,7 @@ jobs:
5576
strategy:
5677
fail-fast: true
5778
matrix:
79+
stack: [laravel]
5880
include:
5981
- platform: linux/amd64
6082
runner: ubuntu-latest
@@ -79,11 +101,11 @@ jobs:
79101
id: build
80102
uses: docker/build-push-action@v6
81103
with:
82-
context: .
104+
context: stacks/${{ matrix.stack }}
83105
platforms: ${{ matrix.platform }}
84-
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
85-
cache-from: type=gha,scope=${{ matrix.platform }}
86-
cache-to: type=gha,scope=${{ matrix.platform }},mode=max
106+
outputs: type=image,name=${{ env.REGISTRY }}/reyemtech/${{ matrix.stack }}-upgrade-agent,push-by-digest=true,name-canonical=true,push=true
107+
cache-from: type=gha,scope=${{ matrix.stack }}-${{ matrix.platform }}
108+
cache-to: type=gha,scope=${{ matrix.stack }}-${{ matrix.platform }},mode=max
87109

88110
- name: Export digest
89111
run: |
@@ -94,18 +116,21 @@ jobs:
94116
- name: Upload digest
95117
uses: actions/upload-artifact@v4
96118
with:
97-
name: digests-${{ matrix.runner }}
119+
name: digests-${{ matrix.stack }}-${{ matrix.runner }}
98120
path: /tmp/digests/*
99121
if-no-files-found: error
100122
retention-days: 1
101123

102-
# Merge per-platform images into a single multi-arch manifest
124+
# Merge per-platform images into a single multi-arch manifest per stack
103125
publish:
104126
needs: [release, build]
105127
runs-on: ubuntu-latest
106128
permissions:
107129
contents: read
108130
packages: write
131+
strategy:
132+
matrix:
133+
stack: [laravel]
109134
steps:
110135
- uses: actions/checkout@v4
111136
with:
@@ -136,7 +161,7 @@ jobs:
136161
id: meta
137162
uses: docker/metadata-action@v5
138163
with:
139-
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
164+
images: ${{ env.REGISTRY }}/reyemtech/${{ matrix.stack }}-upgrade-agent
140165
tags: |
141166
type=raw,value=latest
142167
type=semver,pattern={{version}},value=${{ steps.version.outputs.tag }}
@@ -148,16 +173,16 @@ jobs:
148173
uses: actions/download-artifact@v4
149174
with:
150175
path: /tmp/digests
151-
pattern: digests-*
176+
pattern: digests-${{ matrix.stack }}-*
152177
merge-multiple: true
153178

154179
- name: Create manifest list and push
155180
working-directory: /tmp/digests
156181
run: |
157182
docker buildx imagetools create \
158183
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
159-
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
184+
$(printf '${{ env.REGISTRY }}/reyemtech/${{ matrix.stack }}-upgrade-agent@sha256:%s ' *)
160185
161186
- name: Inspect image
162187
run: |
163-
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
188+
docker buildx imagetools inspect ${{ env.REGISTRY }}/reyemtech/${{ matrix.stack }}-upgrade-agent:${{ steps.meta.outputs.version }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
output/
2+
node_modules/
23
.env
34
*.log

CLAUDE.md

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,48 @@
1-
# CLAUDE.md — Laravel Upgrade Agent (Dev)
1+
# CLAUDE.md — Stack Upgrade Agent (Dev)
22

3-
Instructions for developing the laravel-upgrade-agent itself (not for the agent running inside Docker).
3+
Instructions for developing the stack-upgrade project itself (not for the agent running inside Docker).
44

55
## What This Project Is
66

7-
A disposable Docker image that runs Claude Code autonomously to upgrade any Laravel app. It clones a repo, creates an `upgrade/laravel-{version}` branch, works through 7 phases, commits per phase, and pushes. Optionally creates a PR with a generated changelog.
7+
A multi-stack monorepo of disposable Docker images that run Claude Code autonomously to upgrade applications. Each stack (Laravel, React, Rails, etc.) lives in `stacks/{name}/` with its own Dockerfile, entrypoint, templates, and scripts. A shared CLI (`@reyemtech/stack-upgrade`) handles repo discovery, stack detection, and launching upgrades via Docker or Kubernetes.
8+
9+
Currently supports **Laravel**. Future stacks are added by creating a new `stacks/{name}/` directory and registering it in `cli/src/stacks.js`.
810

911
## Architecture
1012

1113
```
12-
entrypoint.sh # Clone repo, setup branch, install deps, recon, drop templates, launch ralph loop
13-
scripts/ralph-loop.sh # Restart loop — relaunches Claude Code if it exits before checklist complete
14-
scripts/recon.sh # Pre-run repo analysis — produces .upgrade/recon-report.md
15-
scripts/verify-fast.sh # Quick check: composer validate + route:list + tests
16-
scripts/verify-full.sh # Full check: above + migrate:fresh + npm build + audits
17-
scripts/stream-pretty.sh # Prettifies Claude Code stream-json output for logs
18-
kickoff-prompt.txt # Initial prompt sent to Claude Code inside the container
19-
Dockerfile # PHP 8.4, Node 22, Composer 2, gh CLI, Claude Code CLI, non-root user
20-
templates/
21-
CLAUDE.md # Dropped into .upgrade/ — agent instructions (NOT this file)
22-
plan.md # Dropped into .upgrade/ — 6-phase upgrade plan (uses envsubst)
23-
checklist.yaml # Dropped into .upgrade/ — phase tracking (uses envsubst)
24-
run-log.md # Dropped into .upgrade/ — agent decision log
25-
changelog.md # Dropped into .upgrade/ — agent-maintained changelog (used as PR body)
14+
cli/ # Shared CLI — @reyemtech/stack-upgrade
15+
src/
16+
index.js # Entry point — multi-upgrade loop
17+
stacks.js # Stack registry (detection, image, env keys)
18+
github.js # Repo discovery via gh CLI
19+
docker.js # Docker launcher (multi-container)
20+
kubectl.js # Kubernetes launcher (multi-pod)
21+
prompts.js # Interactive prompts
22+
pod-name.js # Container/pod name derivation
23+
config.js # ~/.stack-upgrade/config.json
24+
credentials.js # Claude credential detection
25+
package.json
26+
27+
stacks/laravel/ # Laravel upgrade stack
28+
Dockerfile # PHP 8.4, Node 22, Composer 2, gh CLI, Claude Code CLI
29+
entrypoint.sh # Clone repo, setup branch, install deps, recon, drop templates, launch ralph loop
30+
kickoff-prompt.txt # Initial prompt sent to Claude Code inside the container
31+
scripts/
32+
ralph-loop.sh # Restart loop — relaunches Claude Code if it exits before checklist complete
33+
recon.sh # Pre-run repo analysis — produces .upgrade/recon-report.md
34+
verify-fast.sh # Quick check: composer validate + route:list + tests
35+
verify-full.sh # Full check: above + migrate:fresh + npm build + audits
36+
stream-pretty.sh # Prettifies Claude Code stream-json output for logs
37+
templates/
38+
CLAUDE.md # Dropped into .upgrade/ — agent instructions (NOT this file)
39+
plan.md # Dropped into .upgrade/ — 7-phase upgrade plan (uses envsubst)
40+
checklist.yaml # Dropped into .upgrade/ — phase tracking (uses envsubst)
41+
run-log.md # Dropped into .upgrade/ — agent decision log
42+
changelog.md # Dropped into .upgrade/ — agent-maintained changelog (used as PR body)
43+
44+
.github/workflows/release.yml # CI/CD — semantic-release + npm publish + matrix Docker build per stack
45+
.releaserc.json # semantic-release config
2646
```
2747

2848
### Workspace Layout (inside container)
@@ -58,9 +78,9 @@ The agent's durable memory survives context compaction and session restarts:
5878

5979
| File | Purpose | Location |
6080
|------|---------|----------|
61-
| `plan.md` | Blueprint — phases, constraints, verification strategy | Template: `templates/plan.md` -> dropped into `.upgrade/` |
62-
| `checklist.yaml` | Executable work items with acceptance criteria and status | Template: `templates/checklist.yaml` -> dropped into `.upgrade/` |
63-
| `run-log.md` | Append-only ops log: decisions, evidence, failures, fixes | Template: `templates/run-log.md` -> dropped into `.upgrade/` |
81+
| `plan.md` | Blueprint — phases, constraints, verification strategy | Template: `stacks/laravel/templates/plan.md` -> dropped into `.upgrade/` |
82+
| `checklist.yaml` | Executable work items with acceptance criteria and status | Template: `stacks/laravel/templates/checklist.yaml` -> dropped into `.upgrade/` |
83+
| `run-log.md` | Append-only ops log: decisions, evidence, failures, fixes | Template: `stacks/laravel/templates/run-log.md` -> dropped into `.upgrade/` |
6484

6585
### Execution Loop (what the agent does per phase)
6686

@@ -80,7 +100,7 @@ The agent's durable memory survives context compaction and session restarts:
80100
- **Checkpoint constantly** — one commit per phase, reversible steps
81101
- **Loop breaker** — after 3 failed attempts on the same error, log failure, mark phase `failed`, move on (prevents infinite loops)
82102
- **Ralph loop** — restart harness that relaunches Claude Code if it exits before checklist is complete (handles context exhaustion gracefully)
83-
- **Recon before action**`scripts/recon.sh` maps the repo (package usage, component counts, test shape) before the agent starts
103+
- **Recon before action**`stacks/laravel/scripts/recon.sh` maps the repo (package usage, component counts, test shape) before the agent starts
84104
- **Self-evolve** — after every run, review `run-log.md` for patterns that should become template/constraint changes
85105

86106
### Steering Messages (for manual intervention during a run)
@@ -94,18 +114,18 @@ If you need to steer the agent mid-run (via Docker exec or modifying files):
94114
## Key Concepts
95115

96116
- **Templates** use `${TARGET_LARAVEL}` and `${UPGRADE_DATE}` — substituted by `envsubst` in entrypoint.sh
97-
- **Ralph loop** (`scripts/ralph-loop.sh`) restarts Claude Code up to `MAX_RESTARTS` times if checklist has incomplete tasks
98-
- **Branch handling** (`entrypoint.sh`): checks if remote branch exists first — if yes, checks out and rebases; if no, creates fresh. Supports `BRANCH_SUFFIX` for repeat runs.
117+
- **Ralph loop** (`stacks/laravel/scripts/ralph-loop.sh`) restarts Claude Code up to `MAX_RESTARTS` times if checklist has incomplete tasks
118+
- **Branch handling** (`stacks/laravel/entrypoint.sh`): checks if remote branch exists first — if yes, checks out and rebases; if no, creates fresh. Supports `BRANCH_SUFFIX` for repeat runs.
99119
- **One commit per phase** — no intermediate commits within a phase
100120
- **Push + PR** — push happens at end of ralph-loop.sh (controlled by `GIT_PUSH`). If `GH_TOKEN` is set, a PR is auto-created using `.upgrade/changelog.md` as the body.
101121
- **Structured exit** — exit 0 = all complete, exit 1 = incomplete. `/output/result.json` has outcome summary.
102122
- **Status monitoring**`/output/status.json` is updated throughout the run for external monitoring.
103123
- **Dependency snapshots** — before/after JSON snapshots of composer and npm packages saved to `/output/` for diff-based review.
104-
- **Recon**`scripts/recon.sh` produces `.upgrade/recon-report.md` with package usage analysis, component counts, test suite shape.
124+
- **Recon**`stacks/laravel/scripts/recon.sh` produces `.upgrade/recon-report.md` with package usage analysis, component counts, test suite shape.
105125
- **Upgrade guide** — official Laravel upgrade docs fetched and saved to `.upgrade/laravel-upgrade-guide.html`.
106126
- **No CLAUDE.md overwrite** — upgrade instructions go to `.upgrade/CLAUDE.md`; the target repo's own `CLAUDE.md` is preserved.
107127

108-
## Upgrade Philosophy (templates/CLAUDE.md + templates/plan.md)
128+
## Upgrade Philosophy (stacks/laravel/templates/CLAUDE.md + stacks/laravel/templates/plan.md)
109129

110130
These files define what the agent does inside the container:
111131

@@ -114,6 +134,20 @@ These files define what the agent does inside the container:
114134
- **Skip unused packages** — if not imported/used anywhere, remove instead of upgrading.
115135
- **7 phases:** Core Framework > First-Party > Filament+Livewire > Third-Party Composer > NPM+Frontend > Config Drift+README > PHP Version
116136

137+
## CLI
138+
139+
The CLI (`cli/`) is published as `@reyemtech/stack-upgrade` (binary: `stack-upgrade`). It:
140+
141+
1. Auto-detects Claude credentials (OAuth or API key) from multiple sources
142+
2. Discovers repos via GitHub CLI, detects stack type from `composer.json`
143+
3. Supports queuing multiple upgrades to run in parallel
144+
4. Launches via Docker (local) or Kubernetes (remote cluster)
145+
5. Persists preferences to `~/.stack-upgrade/config.json`
146+
147+
### Stack Registry (`cli/src/stacks.js`)
148+
149+
Each stack is registered with: name, Docker image, detection function, version prompt label, branch prefix, and env key. Adding a new stack means adding an entry here.
150+
117151
## Environment Variables
118152

119153
| Variable | Required | Default | Description |
@@ -155,8 +189,8 @@ After a run, `/output/` contains:
155189
## Dev Workflow
156190

157191
```bash
158-
# Build
159-
docker build -t laravel-upgrade-agent .
192+
# Build Laravel stack
193+
docker build -t laravel-upgrade-agent stacks/laravel/
160194

161195
# Test run (no push)
162196
docker run --rm \
@@ -168,44 +202,20 @@ docker run --rm \
168202
-v ./output:/output \
169203
laravel-upgrade-agent
170204

171-
# Test run with branch suffix and auto PR
172-
docker run --rm \
173-
-e REPO_URL=git@github.com:org/repo.git \
174-
-e TARGET_LARAVEL=12 \
175-
-e BRANCH_SUFFIX=$(date +%Y-%m-%d) \
176-
-e GH_TOKEN=$GH_TOKEN \
177-
-e CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN \
178-
-e GIT_SSH_KEY_B64=$(base64 < ~/.ssh/deploy_key) \
179-
-v ./output:/output \
180-
laravel-upgrade-agent
205+
# Test CLI
206+
cd cli && npm install && npm link && stack-upgrade
181207

182208
# Check output
183209
cat output/result.json
184210
cat output/changelog.md
185-
cat output/run-log.md
186-
cat output/checklist.yaml
187-
cat output/commits.log
188-
189-
# Compare dependency changes
190-
diff <(jq -r '.installed[] | .name' output/before-composer.json | sort) \
191-
<(jq -r '.installed[] | .name' output/after-composer.json | sort)
192211
```
193212

194-
## Recent Changes
195-
196-
- **`.upgrade/` folder** — all upgrade artifacts now live in `.upgrade/` instead of the workspace root; target repo's `CLAUDE.md` is preserved
197-
- **Recon phase**`scripts/recon.sh` maps package usage, component counts, and test shape before the agent starts
198-
- **Changelog** — agent maintains `.upgrade/changelog.md` per phase; used as PR body
199-
- **Upgrade guide fetch** — official Laravel upgrade docs fetched to `.upgrade/laravel-upgrade-guide.html`
200-
- **Dependency snapshots** — before/after JSON snapshots for diff-based review
201-
- **Structured exit** — exit 0/1 with `/output/result.json` summary
202-
- **Status monitoring**`/output/status.json` updated throughout for external monitoring
203-
- **Auto PR**`gh pr create` when `GH_TOKEN` is set
204-
- **Branch suffix**`BRANCH_SUFFIX` env var for repeat runs without collision
205-
- **Branch reuse** — entrypoint.sh checks for existing remote branch and rebases instead of blindly creating
206-
- **Upgrade philosophy** — "never modify business logic" replaced with "never change application behaviour" so major package upgrades proceed with required code changes
207-
- **Unused package removal** — agent removes packages not imported anywhere instead of upgrading them
208-
- **Flux/flux-pro awareness** — Phase 3 checks for livewire/flux and livewire/flux-pro
213+
## Adding a New Stack
214+
215+
1. Create `stacks/{name}/` with `Dockerfile`, `entrypoint.sh`, `templates/`, and `scripts/`
216+
2. Add the stack to `cli/src/stacks.js` with detection logic, image name, and env key
217+
3. Add the stack to the `matrix.stack` array in `.github/workflows/release.yml`
218+
4. Image will be published as `ghcr.io/reyemtech/{name}-upgrade-agent`
209219

210220
## Commit Convention
211221

@@ -224,21 +234,23 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f
224234
| `refactor` | Code restructuring, no behavior change | No release |
225235
| `test` | Adding or fixing tests | No release |
226236

227-
**Scopes** (optional but encouraged): `entrypoint`, `ralph`, `recon`, `templates`, `dockerfile`, `ci`
237+
**Scopes** (optional but encouraged): `entrypoint`, `ralph`, `recon`, `templates`, `dockerfile`, `ci`, `cli`, `stacks`
228238

229239
**Examples:**
230240
```
231241
fix(ralph): handle missing checklist.yaml on restart
242+
feat(cli): add multi-upgrade queue support
232243
feat(entrypoint): add BRANCH_SUFFIX env var for repeat runs
233-
feat!: move all artifacts to .upgrade/ folder
244+
feat!: restructure as multi-stack monorepo
234245
docs: update README with new output artifacts
235246
chore(dockerfile): bump Node to 22.x
236-
ci: add semantic-release workflow
247+
ci: add npm-publish job and matrix build per stack
237248
```
238249

239250
## File Editing Rules
240251

241-
- `templates/` files are what the agent sees inside the container — changes here affect agent behaviour
242-
- `entrypoint.sh` and `scripts/` are the orchestration layer — changes here affect the run lifecycle
243-
- `Dockerfile` rarely needs changes unless adding system deps or upgrading Claude Code
252+
- `stacks/laravel/templates/` files are what the agent sees inside the container — changes here affect agent behaviour
253+
- `stacks/laravel/entrypoint.sh` and `stacks/laravel/scripts/` are the orchestration layer — changes here affect the run lifecycle
254+
- `stacks/laravel/Dockerfile` rarely needs changes unless adding system deps or upgrading Claude Code
255+
- `cli/src/stacks.js` is the stack registry — add new stacks here
244256
- Always test changes with `GIT_PUSH=false` first

0 commit comments

Comments
 (0)