# BaoLife MVP Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. After each task, run verification commands and require green output before marking complete. Refer back to `docs/plans/2026-04-13-baolife-mvp-completion-design.md` for the why.

**Goal:** Take BaoLife from current state (1087/1090 server tests passing, no E2E coverage) to TestFlight-ready iOS MVP via the simulator-first (Option A) approach.

**Architecture:** Detailed task lists for Phase 0 and Phase 1. Phases 2–5 are sketched at the strategic level — they will be expanded into bite-sized tasks at the start of each phase, once we know what bugs the simulator catches and what iOS surfaces look like under test.

**Tech Stack:** Node 22 + TypeScript, Vitest (backend tests), `ws` library (WebSocket), MySQL via mysql2, SwiftUI for iOS, XCUITest + XcodeBuildMCP for iOS testing, swift-snapshot-testing (pointfreeco) for visual regressions.

---

## Phase 0 — Cleanup & Truth

### Task 0.1: Verify production backend identity

**Files:**
- Modify: `CLAUDE.md` (record findings)

**Step 1: SSH and check service**

Run: `ssh lichun-master 'systemctl cat baolife-websocket.service | head -30'`
Expected: ExecStart line points to either `node` running `/var/www/lichun.app/lichun/server/dist/index.js` (TS) or `python` running `ws/app.py` (legacy).

**Step 2: Cross-check with running process**

Run: `ssh lichun-master 'ps aux | grep -E "node|python" | grep -v grep | grep -i lichun'`
Expected: Confirms TS (node) or Python (python) is the live process.

**Step 3: Update CLAUDE.md production notes**

Edit the "Production Server" section to record the verified ExecStart path and mark which backend is live as of 2026-04-13.

**Step 4: Commit**

```bash
git add CLAUDE.md
git commit -m "docs: record verified production backend identity (2026-04-13)"
```

---

### Task 0.2: Fix 3 stale contract tests

**Files:**
- Modify: `server/tests/contracts/frontend-contract.regression.test.ts`

**Step 1: Identify what the failing test is asserting**

Run: `cd server && npx vitest run tests/contracts/frontend-contract.regression.test.ts 2>&1 | tail -40`
Expected: 3 failures, all from `ENOENT: ios/lichunWebsocket/Core/Services/MessageRouter.swift`.

**Step 2: Find the current location of the IAP send logic in iOS**

Run: `cd /Users/craigvandergalien/Documents/GitHub/lichun && grep -rln "purchaseItem\|in-app purchase\|inAppPurchase" ios/lichunWebsocket --include='*.swift' | head -10`
Expected: New file path(s) where the IAP command is built.

**Step 3: Update the test to read from the current path**

Replace the `MessageRouter.swift` reference with the new path. If the assertion is no longer meaningful (logic moved to a different abstraction), replace with an equivalent assertion against the new file. Do NOT delete the test.

**Step 4: Run all contract tests to verify green**

Run: `cd server && npx vitest run tests/contracts/`
Expected: All contract tests pass.

**Step 5: Run full server suite**

Run: `cd server && npx vitest run --reporter=dot 2>&1 | tail -10`
Expected: 1090/1090 tests passing.

**Step 6: Commit**

```bash
git add server/tests/contracts/frontend-contract.regression.test.ts
git commit -m "test(contracts): update stale ios path references after WebSocketService refactor"
```

---

### Task 0.3: Delete deprecated web frontend

**Files:**
- Delete: `index.html`
- Delete: `main.js`
- Delete (if exists): `assets/` (only if all contents are legacy web frontend; verify first)

**Step 1: Confirm files are not referenced**

Run: `cd /Users/craigvandergalien/Documents/GitHub/lichun && grep -rln "index\.html\|main\.js" --include='*.ts' --include='*.swift' --include='*.kt' --include='*.json' | grep -v node_modules | grep -v dist | grep -v build`
Expected: No matches in active code paths. Documentation references are OK.

**Step 2: Inspect assets/**

Run: `ls assets/ 2>/dev/null && file assets/* 2>/dev/null | head -20`
Expected: Identify whether assets/ is purely legacy web or has shared resources.

**Step 3: Delete the deprecated files**

```bash
rm index.html main.js
# Delete assets/ ONLY if step 2 confirmed all-legacy
```

**Step 4: Verify nothing broke**

Run: `cd server && npx tsc --noEmit && npx vitest run --reporter=dot 2>&1 | tail -5`
Expected: All green.

**Step 5: Commit**

```bash
git add -A
git commit -m "chore: remove deprecated web frontend (index.html, main.js)"
```

---

### Task 0.4: Delete legacy PHP api/

**Files:**
- Delete: `api/`

**Step 1: Verify push notifications now run from Node service**

Run: `grep -rln "apns\|APNs\|push notif" server/src --include='*.ts' | head -5`
Expected: Confirms `server/src/services/notifications/` exists and handles push.

**Step 2: Confirm api/ is not referenced from production**

Run: `ssh lichun-master 'ls /var/www/lichun.app/lichun/api 2>/dev/null && cat /etc/nginx/sites-enabled/* 2>/dev/null | grep -A2 "api"'`
Expected: Either confirms api/ is unused on prod, or surfaces the dependency. **If api/ is still wired into nginx, do NOT delete — file an issue and skip this task.**

**Step 3: Delete if safe**

```bash
rm -rf api/
```

**Step 4: Commit**

```bash
git add -A
git commit -m "chore: remove legacy PHP api/ (push notifications moved to Node service)"
```

---

### Task 0.5: Update PROJECT_STATUS.md to current reality

**Files:**
- Modify: `docs/PROJECT_STATUS.md`

**Step 1: Rewrite Status doc**

Sections to update:
- "Last Updated" → 2026-04-13
- Backend column → mark TS as production-deployed (per Task 0.1 finding)
- Remove "Push notifications" from Remaining Work — already done
- Add iOS test coverage status
- Add note about Android v1.1 deferral
- Reference `2026-04-13-baolife-mvp-completion-design.md`

**Step 2: Commit**

```bash
git add docs/PROJECT_STATUS.md
git commit -m "docs(status): update PROJECT_STATUS.md to 2026-04 reality"
```

---

### Task 0.6: Phase 0 exit verification

**Step 1: Full server suite**

Run: `cd server && npx tsc --noEmit && npx vitest run --reporter=dot 2>&1 | tail -10`
Expected: 1090/1090 passing.

**Step 2: Working tree clean**

Run: `git status --short`
Expected: Empty (or only .DS_Store / Xcode user state).

**Step 3: Mark Phase 0 complete in TaskList.**

---

## Phase 1 — Headless Lifetime Simulator

### Task 1.1: Choose simulation entry point

**Files:**
- Read: `server/src/game/PlayerSession.ts`
- Read: `server/src/server/WebSocketServer.ts`
- Read: `server/src/handlers/index.ts`

**Step 1: Read PlayerSession to understand its constructor and tick loop**

Decision point: Can we instantiate `PlayerSession` directly with a stub output sink, or do we need to spin up the WebSocket server and connect a fake client?

**Decision rule:**
- If `PlayerSession` accepts an output interface (or one can be added in <50 lines without breaking production), simulate at PlayerSession level → faster, more deterministic
- Otherwise simulate at WebSocket level

**Step 2: Document the chosen approach**

Append a short ADR note to `docs/plans/2026-04-13-baolife-mvp-completion-design.md` under a new "Section 10: Decisions" heading.

**Step 3: Commit the ADR**

```bash
git add docs/plans/2026-04-13-baolife-mvp-completion-design.md
git commit -m "docs(plan): record simulator entry-point decision"
```

---

### Task 1.2: Create simulator harness skeleton

**Files:**
- Create: `server/tests/e2e/lifetime-simulator.ts`

**Step 1: Write the harness scaffold**

The harness exposes:

```typescript
export interface SimulatorOptions {
  seed: number;
  characterName?: string;
  characterAge?: number;
  characterSex?: 'male' | 'female';
  maxHours?: number;        // safety cap
  questionResponder?: (q: Question) => string;  // strategy for answering
  trace?: boolean;          // emit per-tick log
}

export interface SimulatorResult {
  finalAge: number;
  hoursSimulated: number;
  death?: { cause: string; ageAtDeath: number };
  events: Array<{ tick: number; fname: string }>;
  errors: Array<{ tick: number; error: Error }>;
  invariantViolations: Array<{ tick: number; rule: string; detail: string }>;
}

export async function simulateLifetime(opts: SimulatorOptions): Promise<SimulatorResult>;
```

Implement using the entry point chosen in Task 1.1.

**Step 2: Add invariant checker**

Module-level function that runs after every tick and pushes to `invariantViolations`:
- `gameSpeed` not paused unless an open question exists
- All `Person.energy/hunger/mood/etc.` are 0–100
- `player.events` Set integrity
- `player.c` is non-null
- No NaN in numeric fields

**Step 3: Smoke test**

Add `server/tests/e2e/smoke.test.ts`:

```typescript
import { describe, it, expect } from 'vitest';
import { simulateLifetime } from './lifetime-simulator';

describe('simulator smoke', () => {
  it('runs a single short lifetime without throwing', async () => {
    const result = await simulateLifetime({ seed: 1, maxHours: 24 * 7 });
    expect(result.errors).toHaveLength(0);
  });
});
```

**Step 4: Run smoke test**

Run: `cd server && npx vitest run tests/e2e/smoke.test.ts`
Expected: Pass. If it fails, the failures themselves are the first batch of bugs to capture for Phase 2 — do not "fix" them by silencing assertions; record them in the harness output and move on.

**Step 5: Commit**

```bash
git add server/tests/e2e/
git commit -m "test(e2e): add lifetime simulator harness skeleton + smoke test"
```

---

### Task 1.3: Add scenario test file

**Files:**
- Create: `server/tests/e2e/lifetime-simulator.test.ts`

**Step 1: Write scenario tests**

```typescript
import { describe, it, expect } from 'vitest';
import { simulateLifetime } from './lifetime-simulator';

const baseOpts = { maxHours: 24 * 365 * 100 }; // safety cap of 100 in-game years

describe('lifetime simulator scenarios', () => {
  it('full life with default responses completes', async () => {
    const r = await simulateLifetime({ seed: 42, ...baseOpts });
    expect(r.errors).toHaveLength(0);
    expect(r.invariantViolations).toHaveLength(0);
  });

  it('answer-all-yes completes', async () => {
    const r = await simulateLifetime({
      seed: 42,
      ...baseOpts,
      questionResponder: (q) => q.answers[0]?.id ?? 'yes',
    });
    expect(r.errors).toHaveLength(0);
    expect(r.invariantViolations).toHaveLength(0);
  });

  it('answer-all-no completes', async () => {
    const r = await simulateLifetime({
      seed: 42,
      ...baseOpts,
      questionResponder: (q) => q.answers[q.answers.length - 1]?.id ?? 'no',
    });
    expect(r.errors).toHaveLength(0);
    expect(r.invariantViolations).toHaveLength(0);
  });

  it.each([1, 2, 3, 5, 8, 13, 21, 34, 55, 89])('random seed %i completes', async (seed) => {
    const r = await simulateLifetime({ seed, ...baseOpts });
    expect(r.errors).toHaveLength(0);
    expect(r.invariantViolations).toHaveLength(0);
  });
});
```

**Step 2: Run all scenarios**

Run: `cd server && npx vitest run tests/e2e/lifetime-simulator.test.ts`
Expected: Many will fail. **That's the point.** Capture the failure output for Phase 2.

**Step 3: Commit (with failures expected — they document the work needed)**

If tests fail, mark the failing scenarios with `it.skip` (NOT `it.todo`) and add a comment `// captured in Phase 2 backlog: <one-line summary>`. Then commit:

```bash
git add server/tests/e2e/lifetime-simulator.test.ts
git commit -m "test(e2e): add scenario suite (passing scenarios first)"
```

**Step 4: Open Phase 2 backlog file**

Create `docs/plans/2026-04-13-baolife-phase2-backlog.md` with one row per skipped scenario.

---

### Task 1.4: Wire simulator into npm scripts and CI

**Files:**
- Modify: `server/package.json`
- Modify: `.github/workflows/test.yml` (if it exists)

**Step 1: Add `e2e` script**

```json
"scripts": {
  "e2e": "vitest run tests/e2e/"
}
```

**Step 2: Wire CI**

If a workflow file exists, add `npm run e2e` after `npm test`. Skip if no workflow.

**Step 3: Commit**

```bash
git add server/package.json .github/workflows/test.yml
git commit -m "ci: wire lifetime simulator into npm scripts and CI"
```

---

### Task 1.5: Phase 1 exit verification

**Step 1: Full suite**

Run: `cd server && npm test && npm run e2e`
Expected: All passing scenarios green; all skipped scenarios documented.

**Step 2: Commit any remaining cleanup**

**Step 3: Mark Phase 1 complete and proceed to Phase 2 planning.**

---

## Phase 2 — Bug Sweep (planning placeholder)

Phase 2 starts by reading `docs/plans/2026-04-13-baolife-phase2-backlog.md` (created in Task 1.3) and converting each backlog entry into a bite-sized task. Each task follows this structure:

1. Un-skip the scenario, run it, capture the failure
2. Spawn a focused sub-agent with: failing test output, list of relevant source files, instruction to write a failing unit test first
3. Sub-agent fixes the bug
4. Primary verifies via `npm test && npm run e2e`
5. Primary commits

Phase 2 also closes architectural issue C1 (function-based question rewards). That task is independent of simulator output and can be written now if needed.

**Phase 2 is fully expanded into bite-sized tasks at the start of Phase 2 execution, not now.** Reason: pre-writing 50 tasks for bugs we haven't seen yet wastes effort.

---

## Phase 3 — iOS Coverage Sprint (planning placeholder)

Phase 3 is expanded once Phase 2 is complete. Plan structure will be:

1. Add swift-snapshot-testing as SPM dep
2. Snapshot test each primary screen (one task per screen)
3. Extend UI tests (one task per flow)
4. Run via XcodeBuildMCP (`mcp__xcode__BuildProject`, `mcp__xcode__RunAllTests`)
5. Fix any failures via sub-agents

---

## Phase 4 — Soak & Polish (planning placeholder)

Run 1000-seed soak overnight, fix top 10, repeat until clean.

---

## Phase 5 — TestFlight Submission (planning placeholder)

Crash reporting verification → version bump → archive → upload → release notes doc.

---

## Execution Discipline

For every task in this plan:
1. **No completion claim without verification output.** "Should work" is rejected.
2. **Commit at task boundary, never mid-task.** Each commit is independently revertable.
3. **If verification fails, the task is not done.** No partial credit.
4. **Sub-agent outputs are verified by the primary** — read actual file diffs, re-run tests.
