# Phase 0 정리·삭제 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 새 운영 비전(외주 메인 + 사주·타로·음악)에 없는 기능(eBay 세트·packages/subscription·PortOne)과 도달 불가능한 죽은 코드를 제거하고, DB 마이그레이션과 문서를 정합화한다.

**Architecture:** 삭제 전용 리팩토링. 6개 삭제 그룹을 독립 커밋으로 진행하고, 각 커밋마다 `npm test` + `npm run build`로 회귀를 차단한다. IA(네비·홈)와 next.config.ts 리다이렉트는 건드리지 않는다.

**Tech Stack:** Next.js 16 (App Router), TypeScript, Supabase, vitest

**Spec:** `docs/superpowers/specs/2026-07-02-saas-operation-refactor-phase0-design.md`

## Global Constraints

- next.config.ts의 redirects()는 **한 줄도 수정 금지** (외부 URL 호환)
- `app/work/website/samples/**` 8종, `app/work/layout.tsx`, `app/work/website/layout.tsx`는 **삭제 금지** (Phase 1 자산 + 경로 세그먼트 유지)
- gyeol 세트(`/gyeol`, `/api/survey`, `admin/survey`, `survey_responses` 테이블)는 **삭제 금지** (CEO 의도적 보존)
- `app/api/projects/**`, telegram 3종(`webhook`·`connect`·`setup`), `lib/telegram.ts`는 **삭제 금지** (Phase 1~3 재활용)
- 기존 마이그레이션 파일은 이력이므로 삭제·수정 금지, 신규 파일만 추가
- 각 Task 종료 시 `npm test` 전체 통과 + `npm run build` 성공 후 커밋
- 커밋 메시지 끝에 다음 트레일러 포함:
  `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`

---

### Task 1: eBay 세트 삭제

**Files:**
- Delete: `app/api/questionnaire/` (submit/route.ts 포함 디렉토리)
- Delete: `app/admin/questionnaire/page.tsx` (디렉토리째)
- Delete: `app/api/admin/questionnaire/` (route.ts + [id]/route.ts 디렉토리)
- Delete: `app/admin/documents/page.tsx` (디렉토리째)
- Delete: `app/api/admin/documents/` ([filename]/route.ts 디렉토리)
- Delete: `lib/ebay-tools/` (crawler.ts·pricing.ts·ai-analyzer.ts·types.ts)
- Delete: `CONTENT/ebay-tool-questionnaire.html`, `CONTENT/ebay-tool-proposal.html`, `CONTENT/ARCHITECTURE_EBAY_PARTS_TOOL.md`
- Modify: `app/admin/components/AdminSidebar.tsx` (NAV_ITEMS 2항목 제거)
- Modify: `package.json` (`cheerio` 제거 — 유일 소비처가 lib/ebay-tools/crawler.ts:1)

**Interfaces:**
- Consumes: 없음 (독립 삭제)
- Produces: 없음. 이후 Task는 이 파일들이 없다고 가정

- [ ] **Step 1: 파일 삭제**

```bash
git rm -r app/api/questionnaire app/admin/questionnaire app/api/admin/questionnaire \
  app/admin/documents app/api/admin/documents lib/ebay-tools \
  CONTENT/ebay-tool-questionnaire.html CONTENT/ebay-tool-proposal.html CONTENT/ARCHITECTURE_EBAY_PARTS_TOOL.md
```

- [ ] **Step 2: AdminSidebar에서 메뉴 2개 제거**

`app/admin/components/AdminSidebar.tsx`의 NAV_ITEMS 배열에서 다음 두 객체를 통째로 제거 (href 기준으로 식별, 각 객체는 `{ href, label, icon }` 형태로 svg 포함 약 20줄):
- `href: '/admin/documents'` (label '프로젝트 문서', 약 79~100행)
- `href: '/admin/questionnaire'` (label '질문지 응답', 약 101~122행)

- [ ] **Step 3: cheerio 의존성 제거**

```bash
npm uninstall cheerio
```

- [ ] **Step 4: 잔존 참조 확인 후 테스트·빌드**

```bash
grep -rn --include='*.ts' --include='*.tsx' -iE 'ebay|questionnaire' app lib
# 기대: 0건
npm test        # 기대: 전체 PASS
npm run build   # 기대: 빌드 성공
```

- [ ] **Step 5: 커밋**

```bash
git add -A
git commit -m "chore(phase0): eBay 세트 제거 — 문진·문서 admin/API/lib/CONTENT + cheerio"
```

---

### Task 2: packages + subscription 삭제

**Files:**
- Delete: `app/packages/` (page.tsx + layout.tsx)
- Delete: `lib/saas-catalog.ts`
- Delete: `app/api/subscription/` (route.ts + [id]/route.ts)
- Delete: `app/api/cron/subscription-expiry/` (route.ts — cron 디렉토리에 다른 항목 없으면 `app/api/cron/`째)
- Delete: `vercel.json` (내용이 subscription cron 하나뿐)
- Modify: `lib/service-visibility.ts:6` (HideableService 타입에서 `'packages'` 제거)
- Modify: `app/api/admin/services/route.ts:57` (DEFAULT_SERVICES에서 packages 행 제거)
- Modify: `app/api/admin/stats/route.ts:18-31,52` (subscriptions 집계 제거)
- Modify: `app/admin/dashboard/page.tsx:10,160-171` (activeSubscribers 제거)
- Modify: `app/api/admin/members/route.ts:30-37` (subsRes/activeSub 제거)
- Modify: `app/admin/members/page.tsx:12,15,94-101,127-131,154-158` (activeSub UI 제거)
- Modify: `app/work/saju/result/page.tsx:113-136` (subscriptions 쿼리 제거, orders 단일화)

**Interfaces:**
- Consumes: 없음
- Produces: `/api/admin/stats` 응답에서 `activeSubscribers` 필드 소멸, `/api/admin/members` 응답에서 `activeSub` 필드 소멸. 이 두 API의 프론트 소비처 수정까지 이 Task에 포함

- [ ] **Step 1: 파일 삭제**

```bash
git rm -r app/packages lib/saas-catalog.ts app/api/subscription app/api/cron vercel.json
```
(사전 확인: `ls app/api/cron` 결과가 `subscription-expiry`뿐이면 cron째 삭제, 아니면 subscription-expiry만)

- [ ] **Step 2: HideableService 타입에서 packages 제거**

`lib/service-visibility.ts`:
```typescript
// 변경 전
export type HideableService = 'saju' | 'music' | 'gyeol' | 'packages' | 'lotto';
// 변경 후
export type HideableService = 'saju' | 'music' | 'gyeol' | 'lotto';
```

- [ ] **Step 3: DEFAULT_SERVICES에서 packages 행 제거**

`app/api/admin/services/route.ts`에서 다음 한 줄 삭제:
```typescript
  { id: 'packages', name: 'SaaS 제품 허브(구)', description: '구 /packages 페이지',             is_active: false, order_index: 104 },
```

- [ ] **Step 4: admin/stats에서 구독 집계 제거**

`app/api/admin/stats/route.ts`:
```typescript
// 변경 전 (18행, 24행, 31행, 52행)
const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes, subsRes] = await Promise.all([
  ...
  supabase.from('subscriptions').select('id', { count: 'exact', head: true }).eq('status', 'active'),
]);
const activeSubscribers = subsRes.count ?? 0;
return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, activeSubscribers, monthlyChart });

// 변경 후
const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes] = await Promise.all([
  supabase.from('profiles').select('id', { count: 'exact', head: true }),
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('status', 'paid'),
  supabase.from('contact_requests').select('id', { count: 'exact', head: true }).eq('status', 'pending'),
  supabase.from('payments').select('amount, created_at').eq('status', 'paid').order('created_at', { ascending: true }),
]);
// activeSubscribers 계산 라인 삭제
return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, monthlyChart });
```

- [ ] **Step 5: admin/dashboard에서 활성 구독자 카드 제거**

`app/admin/dashboard/page.tsx`:
- 인터페이스에서 `activeSubscribers: number;` 필드 삭제 (10행)
- `label="활성 구독자"`인 `<StatCard ... />` 블록(약 160~171행, svg 포함) 통째로 삭제

- [ ] **Step 6: admin/members API에서 구독 조회 제거**

`app/api/admin/members/route.ts`:
```typescript
// 변경 전 (30~37행)
const [ordersRes, paymentsRes, subsRes] = await Promise.all([
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('subscriptions').select('product_id, status, expires_at').eq('user_id', p.id).eq('status', 'active').order('created_at', { ascending: false }).limit(1),
]);
const totalPaid = (paymentsRes.data ?? []).reduce((s: number, x: { amount: number }) => s + x.amount, 0);
const activeSub = subsRes.data?.[0] ?? null;
return { ...p, orderCount: ordersRes.count ?? 0, totalPaid, activeSub };

// 변경 후
const [ordersRes, paymentsRes] = await Promise.all([
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('user_id', p.id).eq('status', 'paid'),
]);
const totalPaid = (paymentsRes.data ?? []).reduce((s: number, x: { amount: number }) => s + x.amount, 0);
return { ...p, orderCount: ordersRes.count ?? 0, totalPaid };
```

- [ ] **Step 7: admin/members 페이지에서 activeSub UI 제거**

`app/admin/members/page.tsx`에서:
- Member 인터페이스의 `activeSub: { product_id: string; status: string; expires_at: string } | null;` 필드 삭제 (12행)
- `PLAN_LABELS` 상수 삭제 (15행~)
- 테이블의 구독 셀 블록 삭제 (94~101행: `{m.activeSub ? (...) : (<span ...>-</span>)}` — 해당 `<td>`와 대응하는 `<th>` 헤더도 함께)
- 모바일 카드의 구독 뱃지 블록 삭제 (127~131행: `{m.activeSub && (<span ...>...)}`)
- 구독 만료 문구 블록 삭제 (154~158행: `{m.activeSub && (<p ...>구독 만료: ...</p>)}`)

- [ ] **Step 8: saju result에서 subscriptions 쿼리 제거 (orders 단일화)**

`app/work/saju/result/page.tsx`의 113~136행을 다음으로 교체:
```typescript
      // 로또 이용권 확인 — orders 테이블 (최근 31일 paid 주문)
      const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString();
      const { data: lottoOrder } = await supabase
        .from('orders')
        .select('id, created_at')
        .eq('user_id', user.id)
        .eq('status', 'paid')
        .in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
        .gte('created_at', thirtyOneDaysAgo)
        .maybeSingle();
      hasLottoSubscription = !!lottoOrder;
```
(`hasLottoSubscription` 변수명·`SajuFortuneSection` prop은 유지 — 시맨틱은 Phase 2에서 재정의)

- [ ] **Step 9: 잔존 참조 확인 후 테스트·빌드**

```bash
grep -rn --include='*.ts' --include='*.tsx' -E "saas-catalog|from\('subscriptions'\)|'packages'|activeSubscribers|activeSub\b" app lib
# 기대: 0건
npm test && npm run build
```

- [ ] **Step 10: 커밋**

```bash
git add -A
git commit -m "chore(phase0): packages·subscription 제거 — 페이지/API/cron/vercel.json + 파급(stats·members·saju) 수정"
```

---

### Task 3: PortOne 결제 잔재 삭제

**Files:**
- Delete: `app/components/PaymentButton.tsx`
- Delete: `app/payment/` (test·fail·success 3페이지)
- Delete: `app/api/payment/` (confirm/route.ts)
- Delete: `lib/payment-channels.ts`, `lib/products.ts`
- Modify: `app/work/saju/page.tsx:5` (미사용 import 삭제)
- Modify: `app/work/saju/result/SajuAISection.tsx:6,316-322` (PaymentButton → 안내 문구)
- Modify: `package.json` (`@portone/browser-sdk` 제거)

**Interfaces:**
- Consumes: 없음
- Produces: `PaymentButton` 컴포넌트 소멸 — 이후 어떤 Task/Phase도 import 불가. 결제는 `BankTransferModal`(계좌이체) 단일 경로

- [ ] **Step 1: 파일 삭제**

```bash
git rm -r app/components/PaymentButton.tsx app/payment app/api/payment lib/payment-channels.ts lib/products.ts
```

- [ ] **Step 2: saju 페이지의 미사용 import 삭제**

`app/work/saju/page.tsx` 5행 삭제 (렌더 사용처 없음 확인됨):
```typescript
import PaymentButton from '@/app/components/PaymentButton';
```

- [ ] **Step 3: SajuAISection의 결제 버튼을 안내 문구로 교체**

`app/work/saju/result/SajuAISection.tsx`:
- 6행 import 삭제: `import PaymentButton from '@/app/components/PaymentButton';`
- 316~322행을 다음으로 교체:
```tsx
          <p className="inline-flex items-center gap-2 bg-white/10 text-blue-100/80 font-semibold px-7 py-3 rounded-xl">
            AI 상세 해석은 서비스 개편 준비 중입니다
          </p>
          <p className="text-blue-200/40 text-xs mt-3">사주 서비스 개편(Phase 2)에서 무료 제공 예정</p>
```
(`hasPaid` 게이트 로직은 유지 — orders 테이블 기반이라 그대로 컴파일됨. 무료화 UX는 Phase 2)

- [ ] **Step 4: SDK 의존성 제거**

```bash
npm uninstall @portone/browser-sdk
```

- [ ] **Step 5: 잔존 참조 확인 후 테스트·빌드**

```bash
grep -rn --include='*.ts' --include='*.tsx' -iE 'portone|PaymentButton|payment-channels|@/lib/products' app lib
# 기대: 0건
npm test && npm run build
```

- [ ] **Step 6: 커밋**

```bash
git add -A
git commit -m "chore(phase0): PortOne 잔재 제거 — 계좌이체 단일 소스 확정, saju 결제 CTA 제거"
```

---

### Task 4: 죽은 페이지 4종 + 전이 고아 삭제

**Files:**
- Delete: `app/work/page.tsx` (`/work`→`/outsourcing` 리다이렉트에 가려짐. `app/work/layout.tsx`는 유지)
- Delete: `app/work/freelance/` (page.tsx + layout.tsx — 디렉토리째)
- Delete: `app/work/website/page.tsx` (`app/work/website/layout.tsx`와 `samples/`는 유지)
- Delete: `app/music/packs/` (page.tsx + layout.tsx — 디렉토리째)
- Delete: `app/components/ContactForm.tsx` (유일 소비처가 죽은 `/work/freelance`)
- Delete: `lib/freelance-portfolio.ts` (소비처가 죽은 `/work`·`/work/freelance`뿐)

**Interfaces:**
- Consumes: 없음
- Produces: 없음. `/work`, `/work/freelance`, `/work/website`, `/music/packs` URL은 리다이렉트가 계속 처리

- [ ] **Step 1: 파일 삭제**

```bash
git rm app/work/page.tsx app/work/website/page.tsx
git rm -r app/work/freelance app/music/packs
git rm app/components/ContactForm.tsx lib/freelance-portfolio.ts
```

- [ ] **Step 2: 잔존 참조 확인 후 테스트·빌드**

```bash
grep -rn --include='*.ts' --include='*.tsx' -E "ContactForm|freelance-portfolio" app lib
# 기대: 0건
npm test && npm run build
# 빌드 후 확인: /work/website/samples/* 8종이 라우트 목록에 존재해야 함
```

- [ ] **Step 3: 커밋**

```bash
git add -A
git commit -m "chore(phase0): redirect에 가린 죽은 페이지 4종 + 전이 고아(ContactForm·freelance-portfolio) 제거"
```

---

### Task 5: deepfield 잔재 + three 의존성 삭제

**Files:**
- Delete: `app/components/deepfield/HeroField.tsx` (import 0회)
- Delete: `app/components/deepfield/useFieldMode.ts` (HeroField 전용)
- Delete: `lib/deepfield-mode.ts`, `lib/__tests__/deepfield-mode.test.ts`
- Modify: `package.json` (`three` 제거 — 유일 소비처가 HeroField.tsx:5,166)

**Interfaces:**
- Consumes: 없음
- Produces: 없음. `deepfield/{ScrollReveal,ShowcaseGrid,ShowcaseCard,CountUp}.tsx`는 활성이므로 유지

- [ ] **Step 1: 파일 삭제**

```bash
git rm app/components/deepfield/HeroField.tsx app/components/deepfield/useFieldMode.ts \
  lib/deepfield-mode.ts lib/__tests__/deepfield-mode.test.ts
```

- [ ] **Step 2: three 의존성 제거**

```bash
npm uninstall three
grep -rn "from 'three'" app lib   # 기대: 0건
```

- [ ] **Step 3: 테스트·빌드**

```bash
npm test        # 기대: deepfield-mode.test 제외된 채 전체 PASS
npm run build
```

- [ ] **Step 4: 커밋**

```bash
git add -A
git commit -m "chore(phase0): deepfield 파티클 잔재 3파일 + three 의존성 제거"
```

---

### Task 6: 고아 API 삭제

**Files:**
- Delete: `app/api/track/[token]/route.ts` (추적 페이지가 Supabase 직접 조회 — `app/track/[token]/page.tsx:16` 주석 확인됨. **페이지는 유지**)
- Delete: `app/api/saju/lotto/route.ts` (프론트 fetch 0회, 외부 saju-engine 전용)

**Interfaces:**
- Consumes: 없음
- Produces: 없음. `/track/[token]` 페이지 동작 불변

- [ ] **Step 1: 파일 삭제**

```bash
git rm -r "app/api/track" "app/api/saju/lotto"
```
(주의: `app/api/saju/analyze`·`app/api/saju/save-interpretation`은 활성 — saju 디렉토리째 삭제 금지)

- [ ] **Step 2: 잔존 참조 확인 후 테스트·빌드**

```bash
grep -rn --include='*.ts' --include='*.tsx' -E "api/track|api/saju/lotto" app lib
# 기대: 0건
npm test && npm run build
```

- [ ] **Step 3: 커밋**

```bash
git add -A
git commit -m "chore(phase0): 고아 API 제거 — track/[token](페이지 직접조회로 대체됨)·saju/lotto"
```

---

### Task 7: DB 마이그레이션 + CLAUDE.md 정합화 + 최종 스윕

**Files:**
- Create: `supabase/migrations/2026-07-02-phase0-cleanup.sql`
- Modify: `CLAUDE.md` (삭제된 기능 서술 제거)

**Interfaces:**
- Consumes: Task 1~6 완료 상태
- Produces: DB 스키마와 코드의 정합. 마이그레이션은 클라우드+NAS 양쪽 수동 적용 항목으로 CEO에 안내

- [ ] **Step 1: 마이그레이션 파일 작성**

`supabase/migrations/2026-07-02-phase0-cleanup.sql`:
```sql
-- Phase 0 정리 (2026-07-02): 비전 제외 기능의 테이블·설정 제거
-- 적용 대상: 클라우드 Supabase + NAS self-host 양쪽 (운영 규칙)
-- survey_responses(gyeol)는 의도적 보존 — 건드리지 않음

DROP TABLE IF EXISTS questionnaire_responses;
DROP TABLE IF EXISTS ebay_search_history;
DROP TABLE IF EXISTS subscriptions;

DELETE FROM service_settings WHERE id = 'packages';
```

- [ ] **Step 2: CLAUDE.md 갱신**

`CLAUDE.md`에서:
- 숨김 서비스 표에서 `/packages` 행 삭제
- 파일 구조 트리에서 `payment/` 항목과 "PortOne 연동 (보존 전용, 미활성)" 서술 삭제
- 결제 플로우 섹션의 "PG(PortOne) 코드는 `products.pay_method` 플래그 기반으로 보존만, 현재 미활성" 불릿 삭제
- 운영 주의사항 등 나머지는 유지
- 파일 구조·표 어디에도 questionnaire/documents/packages/subscription 서술이 남지 않도록 검색(`grep -n "PortOne\|packages\|questionnaire\|subscription" CLAUDE.md`) 후 정리

- [ ] **Step 3: 최종 잔존 참조 스윕**

```bash
grep -rn --include='*.ts' --include='*.tsx' -iE \
  "portone|PaymentButton|payment-channels|saas-catalog|ebay|questionnaire|from\('subscriptions'\)|freelance-portfolio|HeroField|useFieldMode|deepfield-mode|from 'three'" \
  app lib scripts
# 기대: 0건
grep -n "cheerio\|three\|portone" package.json
# 기대: 0건
```

- [ ] **Step 4: 전체 테스트·빌드**

```bash
npm test        # 기대: 전체 PASS
npm run build   # 기대: 빌드 성공
```

- [ ] **Step 5: 커밋**

```bash
git add supabase/migrations/2026-07-02-phase0-cleanup.sql CLAUDE.md
git commit -m "chore(phase0): DB 마이그레이션(DROP 3테이블+packages 행) + CLAUDE.md 정합화"
```

- [ ] **Step 6: CEO 안내 사항 정리 (구현 아님, 보고)**

- 마이그레이션 SQL을 **클라우드 Supabase + NAS self-host 양쪽**에 수동 적용 필요
- Vercel 대시보드에서 기존 cron(subscription-expiry) 잔재 확인 (vercel.json 삭제로 다음 배포 시 자동 해제)
- 배포는 별도 지시 시 진행

---

## 검증 요약 (전 Task 공통)

| 검증 | 명령 | 기대 |
|------|------|------|
| 단위 테스트 | `npm test` | product-access·request-status·showcase 등 전체 PASS |
| 빌드 | `npm run build` | standalone 빌드 성공 |
| 잔존 참조 | Task별 grep | 0건 |
| 라우트 보존 | 빌드 출력 | `/work/website/samples/*` 8종, `/gyeol`, saju·music 라우트 존재 |
