# Product Consolidation Plan

> **Status:** PLANNING — awaiting approval before execution
> **Created:** 2026-02-08
> **Author:** ARIA + Raven

---

## Problem

Products are split across two directories:
- `/home/aria/products/` — product artifacts (discovery, design, build metadata, sell)
- `/home/aria/projects/` — app codebases

This is fragmented. No unified backup. If the VPS dies, recovery requires manually piecing things together.

## Goal

1. **Single `products/` folder** — each product is a git repo containing all artifacts + app code
2. **Workspace as recovery manifest** — enough info to rebuild everything from scratch
3. **`projects/` only for platform infra** — Symph Agency UI, dashboard, staging, starter template

---

## Target Directory Structure

```
/home/aria/products/<slug>/              ← git repo (pushed to symphco/product-<slug>)
  ├── discovery/                         ← briefs, competitors, target-market, prd, etc.
  ├── design/                            ← brand guidelines, screens, mockups
  ├── build/                             ← build-details.yaml, build-plan, progress
  ├── sell/                              ← deals.yaml, activities
  ├── market/                            ← GTM, landing page artifacts
  ├── apps/                              ← app codebases
  │   └── main/                          ← git submodule → symphco/<app-repo>
  ├── landings/                          ← landing pages (tracked directly, not submodules)
  └── .git

/home/aria/projects/                     ← platform infra ONLY
  ├── symph-agency/                      ← Symph Agency UI (port 4000)
  ├── symph-agency-staging/              ← Blue-green deploy staging dir
  ├── aria-dashboard/                    ← ARIA Dashboard (port 3001)
  ├── agency-app-starter/                ← Template repo
  ├── .caddy-ports.json                  ← REMOVED (moved to workspace)
  ├── .aria-logs/                        ← Ephemeral, recreated on boot
  └── .aria-pids.json                    ← Ephemeral, recreated on boot

/home/aria/.openclaw/workspace/          ← git repo (symphco/aria)
  ├── agency/
  │   ├── products -> /home/aria/products   ← symlink (unchanged)
  │   ├── infra/
  │   │   └── caddy-ports.json           ← MOVED here (backed up in git)
  │   ├── products-registry.yaml         ← NEW: master manifest for recovery
  │   └── ...
  └── ...
```

---

## Git Submodules (How It Works)

Each product repo contains app codebases as **git submodules**. This means:
- The product repo tracks a pointer to a specific commit of each app repo
- `git clone --recurse-submodules` pulls everything
- App repos remain independent (own branches, PRs, CI)
- Product repo is the "umbrella" that ties artifacts + apps together

```bash
# Adding an app as submodule (one-time)
cd /home/aria/products/<slug>
git submodule add git@github.com:symphco/<app-repo>.git apps/main

# Cloning a product with all apps (recovery)
git clone --recurse-submodules git@github.com:symphco/product-<slug>.git

# Updating submodule pointer after app changes
cd /home/aria/products/<slug>
git add apps/main
git commit -m "update app to latest"
git push
```

---

## Products Registry (`agency/products-registry.yaml`)

Master manifest for disaster recovery. Contains everything needed to rebuild.

```yaml
# /home/aria/.openclaw/workspace/agency/products-registry.yaml
products:
  ai-teacher-assistant:
    repo: git@github.com:symphco/product-ai-teacher-assistant.git
    firebase_project: symph-ai-teacher-assistant
    apps:
      main:
        repo: git@github.com:symphco/ai-teacher-assistant.git
        port: 3003
        domain: ai-teacher-assistant.dev.apps.symph.co
        pm2_name: ai-teacher-assistant
    landings:
      main:
        port: 3100
        domain: ai-teacher-assistant.landing.symph.co

  sthics:
    repo: git@github.com:symphco/product-sthics.git
    firebase_project: symph-sthics
    apps:
      main:
        repo: git@github.com:symphco/sthics.git
        port: 3011
        domain: sthics.dev.apps.symph.co
        pm2_name: sthics

  event-creation-event-suppliers:
    repo: git@github.com:symphco/product-event-creation-event-suppliers.git
    firebase_project: symph-gala-grid
    apps:
      main:
        repo: git@github.com:symphco/gala-grid.git
        port: 3009
        domain: gala-grid.dev.apps.symph.co
        pm2_name: gala-grid

  finance-operating-system:
    repo: git@github.com:symphco/product-finance-operating-system.git
    firebase_project: symph-finance-os
    apps:
      main:
        repo: git@github.com:symphco/finance-operating-system.git
        port: 3007
        domain: finance-os.dev.apps.symph.co
        pm2_name: finance-os

  motor-vehicle-maintenance:
    repo: git@github.com:symphco/product-motor-vehicle-maintenance.git
    firebase_project: symph-garagepro
    apps:
      main:
        repo: git@github.com:symphco/motor-vehicle-maintenance.git
        port: 3010
        domain: garagepro.dev.apps.symph.co
        pm2_name: garagepro

  mental-wellness-social-connection-habit-building:
    repo: git@github.com:symphco/product-mental-wellness.git
    firebase_project: symph-complimentary
    apps:
      main:
        repo: git@github.com:symphco/complimentary.git
        port: 3005
        domain: complimentary.dev.apps.symph.co
        pm2_name: complimentary

  dailymood-journal:
    repo: git@github.com:symphco/product-dailymood-journal.git
    firebase_project: symph-dailymood-journal
    apps:
      main:
        repo: git@github.com:symphco/dailymood-journal.git
        port: 3004
        domain: dailymood-journal.dev.apps.symph.co
        pm2_name: dailymood-journal

  travel-planning-trip-management-consumer-travel-tech:
    repo: git@github.com:symphco/product-travel-planning.git
    firebase_project: null
    apps:
      main:
        repo: git@github.com:symphco/wanderkit.git
        port: 3006
        domain: wanderkit.dev.apps.symph.co
        pm2_name: wanderkit

  visual-api-explorer:
    repo: git@github.com:symphco/product-visual-api-explorer.git
    firebase_project: null
    apps:
      main:
        repo: git@github.com:symphco/visual-api-explorer.git
        port: 3008
        domain: visual-api-explorer.dev.apps.symph.co
        pm2_name: visual-api-explorer

  # Products without apps (discovery/design only)
  # These still get product repos for backup
  agricultural-marketplace:
    repo: git@github.com:symphco/product-agricultural-marketplace.git
    apps: {}
  kyb-compliance-onboarding-platform:
    repo: git@github.com:symphco/product-kyb-compliance.git
    apps: {}
  # ... (all other products follow same pattern)

# Infrastructure (NOT products — stays in /home/aria/projects/)
infra:
  symph-agency:
    repo: git@github.com:symphco/symph-agency-ui.git
    port: 4000
    domain: symphony.symph.co
  aria-dashboard:
    repo: null  # local only
    port: 3001
    domain: dashboard.dev.apps.symph.co
  agency-app-starter:
    repo: git@github.com:symphco/agency-app-starter.git
```

---

## What Moves Where

### Apps moving from `projects/` → `products/<slug>/apps/main/`

| Current Location | Target Location | GitHub Repo | PM2 Name |
|---|---|---|---|
| `projects/ai-teacher-assistant` | `products/ai-teacher-assistant/apps/main` | `symphco/ai-teacher-assistant` | ai-teacher-assistant |
| `projects/ai-teacher-assistant-landing` | `products/ai-teacher-assistant/landings/main` | (tracked directly in product repo) | — |
| `projects/ai-teacher-assistant-promo` | `products/ai-teacher-assistant/landings/promo` | (tracked directly in product repo) | — |
| `projects/dailymood-journal` | `products/dailymood-journal/apps/main` | `symphco/dailymood-journal` | dailymood-journal |
| `projects/event-creation-event-suppliers` | `products/event-creation-event-suppliers/apps/main` | `symphco/gala-grid` | gala-grid |
| `projects/finance-operating-system` | `products/finance-operating-system/apps/main` | `symphco/finance-operating-system` | finance-os |
| `projects/mental-wellness-social-connection-habit-building` | `products/mental-wellness.../apps/main` | `symphco/complimentary` | complimentary |
| `projects/motor-vehicle-maintenance` | `products/motor-vehicle-maintenance/apps/main` | `symphco/motor-vehicle-maintenance` | garagepro |
| `projects/sthics` | `products/sthics/apps/main` | `symphco/sthics` | sthics |
| `projects/travel-planning-trip-management-consumer-travel-tech` | `products/travel-planning.../apps/main` | `symphco/wanderkit` | wanderkit |
| `projects/visual-api-explorer` | `products/visual-api-explorer/apps/main` | `symphco/visual-api-explorer` | — |
| `projects/wanderkit` | (duplicate — same as travel-planning) | `symphco/wanderkit` | wanderkit |
| `projects/complimentary` | (duplicate — same as mental-wellness) | `symphco/complimentary` | — |
| `projects/storygen` | **DECISION NEEDED** — archive or create product? | no remote | — |

### Stays in `projects/`

| Directory | Reason |
|---|---|
| `symph-agency` | Platform UI, not a product |
| `symph-agency-staging` | Blue-green deploy staging |
| `aria-dashboard` | Infra dashboard |
| `agency-app-starter` | Template repo |
| `.aria-logs/` | Ephemeral logs |

### Duplicates to resolve

| Duplicate | Real Product | Action |
|---|---|---|
| `projects/complimentary` | `mental-wellness-social-connection-habit-building` | Remove after confirming same repo |
| `projects/wanderkit` | `travel-planning-trip-management-consumer-travel-tech` | Remove after move |

---

## Infra State Files

| File | Current Location | New Location | Backed up? |
|---|---|---|---|
| `caddy-ports.json` | `/home/aria/projects/.caddy-ports.json` | `~/.openclaw/workspace/agency/infra/caddy-ports.json` | ✅ git |
| `.aria-logs/` | `/home/aria/projects/.aria-logs/` | stays (ephemeral) | ❌ no need |
| `.aria-pids.json` | `/home/aria/projects/.aria-pids.json` | stays (ephemeral) | ❌ no need |

---

## Execution Steps (In Order)

### Phase 1: Preparation

1. **Stop all PM2 app processes** (except symph-agency)
   ```bash
   pm2 stop gala-grid sthics garagepro
   ```

2. **Create `agency/infra/` dir in workspace**
   ```bash
   mkdir -p ~/.openclaw/workspace/agency/infra
   ```

3. **Move `caddy-ports.json` to workspace**
   ```bash
   cp /home/aria/projects/.caddy-ports.json ~/.openclaw/workspace/agency/infra/caddy-ports.json
   rm /home/aria/projects/.caddy-ports.json
   ```

4. **Create `bootstrap.sh`** in `agency/infra/bootstrap.sh` (see Recovery section below for full script)

5. **Create `products-registry.yaml`** in `agency/products-registry.yaml` with all product + infra entries

6. **Update all code that reads `.caddy-ports.json`** to use new path
   - `caddy-manager` skill — update `CADDY_PORTS_PATH` constant
   - Symph Agency UI API routes — grep for `.caddy-ports.json` and update
   - `TOOLS.md` — update External State Files table
   - Any build scripts that reference the old path

7. **Update all code that references `/home/aria/projects/<app>`** to use `/home/aria/products/<slug>/apps/main/`
   - Symph Agency UI: `src/lib/workspace.ts`, build APIs, code APIs, app management APIs
   - Build templates / PIPELINES.md
   - `build-details.yaml` files (add/update `cwd` field)

### Phase 2: Create Product Repos (one at a time)

For each product with a build phase:

```bash
SLUG="ai-teacher-assistant"

# 1. Create GitHub repo for product
gh repo create symphco/product-${SLUG} --private --description "Product: AI Teacher Assistant"

# 2. Init git in product folder
cd /home/aria/products/${SLUG}
git init
echo "apps/" > .gitignore   # ignore apps dir (submodules tracked separately)
echo "node_modules/" >> .gitignore
echo ".next/" >> .gitignore

# 3. Create apps/ directory
mkdir -p apps

# 4. Move codebase from projects/ to apps/main/
mv /home/aria/projects/ai-teacher-assistant apps/main

# 5. Add as submodule (if it has a GitHub remote)
#    First remove the moved dir, then add as submodule
rm -rf apps/main
git submodule add git@github.com:symphco/ai-teacher-assistant.git apps/main

# 6. Add all product artifacts
git add .
git commit -m "Initial product repo: AI Teacher Assistant"
git remote add origin git@github.com:symphco/product-${SLUG}.git
git push -u origin main
```

**For landing pages** (no GitHub remote, tracked directly):
```bash
# Move into landings/ folder — tracked directly in product repo (not submodules)
mkdir -p landings
mv /home/aria/projects/ai-teacher-assistant-landing landings/main
mv /home/aria/projects/ai-teacher-assistant-promo landings/promo
# Remove their .git dirs since they'll be part of the product repo
rm -rf landings/main/.git landings/promo/.git
```

### Phase 3: Update PM2

For each moved app:
```bash
# Delete old PM2 entry and recreate with new cwd
pm2 delete gala-grid
cd /home/aria/products/event-creation-event-suppliers/apps/main
pm2 start npm --name "gala-grid" -- start -- -p 3009
pm2 save
```

### Phase 4: Update Symph Agency UI

Search and replace hardcoded paths in the codebase:
```bash
# Find all references to /home/aria/projects/ in symph-agency
grep -r "/home/aria/projects/" /home/aria/projects/symph-agency/src/
```

Update to read app paths from `build-details.yaml` or derive from product slug:
```
/home/aria/products/<slug>/apps/main/
```

### Phase 5: Update Workspace References

1. **Update `TOOLS.md`** — change Projects section to reflect new paths
2. **Update `MEMORY.md`** — update Infrastructure table and Active Apps
3. **Create `products-registry.yaml`** — the recovery manifest
4. **Update caddy-manager skill** — new `caddy-ports.json` path
5. **Update build phase code** — `build-details.yaml` should include new `cwd` path

### Phase 6: Products Without Apps

For products that only have discovery/design (no build):
```bash
SLUG="agricultural-marketplace"
cd /home/aria/products/${SLUG}
git init
gh repo create symphco/product-${SLUG} --private
git add .
git commit -m "Initial product repo: ${SLUG}"
git remote add origin git@github.com:symphco/product-${SLUG}.git
git push -u origin main
```

### Phase 7: Verify Everything

- [ ] All PM2 processes running from new paths
- [ ] All Caddy domains resolve correctly
- [ ] Symph Agency UI shows all products
- [ ] Build phase controls work (start/stop/view)
- [ ] Sell phase loads deals
- [ ] Design phase loads screens
- [ ] `git submodule status` clean in all product repos
- [ ] Workspace committed and pushed

### Phase 8: Cleanup

```bash
# Remove emptied directories from projects/
# ONLY after full verification
rm -rf /home/aria/projects/ai-teacher-assistant
rm -rf /home/aria/projects/complimentary
# ... etc

# Remove old caddy-ports.json
rm /home/aria/projects/.caddy-ports.json
```

---

## Recovery Procedure (Disaster Recovery)

If VPS is lost, recovery from a fresh machine using `bootstrap.sh`:

### `bootstrap.sh` Location

```
~/.openclaw/workspace/agency/infra/bootstrap.sh
```

### What It Does

1. **Clone workspace** (if not already present)
2. **Parse `products-registry.yaml`** to get all product repos + app submodules
3. **Clone all product repos** with `--recurse-submodules` into `/home/aria/products/`
4. **Create symlinks** (`agency/products` → `/home/aria/products`, `agency/jobs` → `/home/aria/agency-jobs`)
5. **Restore `caddy-ports.json`** — regenerate from registry data (ports + domains)
6. **Generate Caddy site configs** from registry (one Caddyfile block per app domain → localhost:port)
7. **Install + build all apps** (`npm install && npm run build` in each app dir)
8. **Register PM2 processes** from registry (name, cwd, port)
9. **Clone infra repos** (`symph-agency`, `aria-dashboard`, `agency-app-starter`) into `/home/aria/projects/`
10. **Start platform services** (Symph Agency on port 4000, etc.)

### Usage

```bash
# Full recovery from scratch (after OS + Node + PM2 + Caddy installed)
git clone git@github.com:symphco/aria.git ~/.openclaw/workspace
cd ~/.openclaw/workspace/agency/infra
bash bootstrap.sh --full

# Just re-clone missing products (incremental)
bash bootstrap.sh --products

# Just rebuild PM2 + Caddy from registry (no git cloning)
bash bootstrap.sh --services
```

### Script Skeleton

```bash
#!/usr/bin/env bash
set -euo pipefail

WORKSPACE="$HOME/.openclaw/workspace"
PRODUCTS_DIR="$HOME/products"
PROJECTS_DIR="$HOME/projects"
REGISTRY="$WORKSPACE/agency/products-registry.yaml"
CADDY_PORTS="$WORKSPACE/agency/infra/caddy-ports.json"

# Requires: node, npm, pm2, yq, gh, git

MODE="${1:---full}"

clone_products() {
  echo "📦 Cloning product repos..."
  mkdir -p "$PRODUCTS_DIR"
  
  # Parse registry and clone each product
  for slug in $(yq '.products | keys | .[]' "$REGISTRY"); do
    repo=$(yq ".products[\"$slug\"].repo" "$REGISTRY")
    if [ "$repo" = "null" ] || [ -z "$repo" ]; then continue; fi
    
    target="$PRODUCTS_DIR/$slug"
    if [ -d "$target" ]; then
      echo "  ⏭️  $slug (exists, pulling)"
      git -C "$target" pull --recurse-submodules
    else
      echo "  📥 $slug"
      git clone --recurse-submodules "$repo" "$target"
    fi
  done
}

clone_infra() {
  echo "🏗️  Cloning infra repos..."
  mkdir -p "$PROJECTS_DIR"
  
  for name in $(yq '.infra | keys | .[]' "$REGISTRY"); do
    repo=$(yq ".infra[\"$name\"].repo" "$REGISTRY")
    if [ "$repo" = "null" ] || [ -z "$repo" ]; then continue; fi
    
    target="$PROJECTS_DIR/$name"
    if [ ! -d "$target" ]; then
      git clone "$repo" "$target"
    fi
  done
}

setup_symlinks() {
  echo "🔗 Creating symlinks..."
  mkdir -p "$WORKSPACE/agency"
  mkdir -p "$HOME/agency-jobs"
  
  ln -sfn "$PRODUCTS_DIR" "$WORKSPACE/agency/products"
  ln -sfn "$HOME/agency-jobs" "$WORKSPACE/agency/jobs"
}

generate_caddy_ports() {
  echo "🌐 Generating caddy-ports.json from registry..."
  
  node -e "
    const yaml = require('js-yaml');
    const fs = require('fs');
    const reg = yaml.load(fs.readFileSync('$REGISTRY', 'utf8'));
    const allocated = {};
    const urls = {};
    for (const [slug, prod] of Object.entries(reg.products || {})) {
      for (const [appName, app] of Object.entries(prod.apps || {})) {
        if (!app.port || !app.domain) continue;
        const key = app.pm2_name || slug;
        allocated[key] = app.port;
        urls[key] = app.domain;
      }
    }
    const infra = reg.infra || {};
    for (const [name, svc] of Object.entries(infra)) {
      if (svc.port && svc.domain) { allocated[name] = svc.port; urls[name] = svc.domain; }
    }
    fs.writeFileSync('$CADDY_PORTS', JSON.stringify({ allocated, urls }, null, 2));
    console.log('  ✅ Written', Object.keys(allocated).length, 'entries');
  "
}

install_and_build_apps() {
  echo "🔨 Installing and building apps..."
  
  for slug in $(yq '.products | keys | .[]' "$REGISTRY"); do
    for appName in $(yq ".products[\"$slug\"].apps | keys | .[]" "$REGISTRY" 2>/dev/null); do
      port=$(yq ".products[\"$slug\"].apps[\"$appName\"].port" "$REGISTRY")
      if [ "$port" = "null" ]; then continue; fi
      
      appDir="$PRODUCTS_DIR/$slug/apps/$appName"
      if [ ! -d "$appDir" ]; then continue; fi
      
      echo "  🔧 $slug/apps/$appName"
      cd "$appDir"
      npm install --legacy-peer-deps 2>/dev/null || npm install
      npm run build 2>/dev/null || echo "    ⚠️  Build failed for $slug/$appName"
    done
  done
}

register_pm2() {
  echo "⚙️  Registering PM2 processes..."
  
  for slug in $(yq '.products | keys | .[]' "$REGISTRY"); do
    for appName in $(yq ".products[\"$slug\"].apps | keys | .[]" "$REGISTRY" 2>/dev/null); do
      pm2Name=$(yq ".products[\"$slug\"].apps[\"$appName\"].pm2_name" "$REGISTRY")
      port=$(yq ".products[\"$slug\"].apps[\"$appName\"].port" "$REGISTRY")
      if [ "$pm2Name" = "null" ] || [ "$port" = "null" ]; then continue; fi
      
      appDir="$PRODUCTS_DIR/$slug/apps/$appName"
      if [ ! -d "$appDir" ]; then continue; fi
      
      echo "  ▶️  $pm2Name (port $port)"
      pm2 delete "$pm2Name" 2>/dev/null || true
      cd "$appDir"
      pm2 start npm --name "$pm2Name" -- start -- -p "$port"
    done
  done
  
  # Start Symph Agency
  if [ -d "$PROJECTS_DIR/symph-agency" ]; then
    echo "  ▶️  symph-agency (port 4000)"
    pm2 delete "symph-agency" 2>/dev/null || true
    cd "$PROJECTS_DIR/symph-agency"
    pm2 start npm --name "symph-agency" -- start -- -p 4000
  fi
  
  pm2 save
  echo "  ✅ PM2 saved"
}

# ─── Main ───────────────────────────────────────────────────────────────────

case "$MODE" in
  --full)
    clone_products
    clone_infra
    setup_symlinks
    generate_caddy_ports
    install_and_build_apps
    register_pm2
    echo "🎉 Full recovery complete!"
    ;;
  --products)
    clone_products
    setup_symlinks
    ;;
  --services)
    generate_caddy_ports
    register_pm2
    ;;
  *)
    echo "Usage: bootstrap.sh [--full|--products|--services]"
    exit 1
    ;;
esac
```

---

## Decisions (Confirmed 2026-02-08)

1. **Product repo naming**: `symphco/product-<slug>` ✅
2. **Storygen**: Already removed by Raven. Skip. ✅
3. **Landing pages**: Go in `landings/` folder inside the product repo (not submodules, not `apps/`) ✅
4. **Long slug names**: Keep as-is. Will build a namer skill later. ✅
5. **Products without builds**: YES — create GitHub repos for ALL products, starting at discovery. Every product gets a repo on creation. ✅

---

## Code Changes Required

### Symph Agency UI (`/home/aria/projects/symph-agency/`)

Files likely needing path updates:
- `src/lib/workspace.ts` — may reference projects dir
- `src/app/api/products/[slug]/build/*` — start/stop/status APIs
- `src/app/api/products/[slug]/code/*` — file browser, commit APIs
- `src/app/api/apps/[slug]/*` — app management APIs
- Build agent spawning — cwd for Claude Code

### Caddy Manager Skill

- Update `caddy-ports.json` path reference

### PIPELINES.md / Build Templates

- Update cwd references for where apps are cloned/built

### `build-details.yaml` (per product)

Add/update field:
```yaml
cwd: /home/aria/products/<slug>/apps/main
```
