Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAtcmKKtqDUe4NyVgy1aLQ
19 KiB
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: 파일 삭제
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 의존성 제거
npm uninstall cheerio
- Step 4: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -iE 'ebay|questionnaire' app lib
# 기대: 0건
npm test # 기대: 전체 PASS
npm run build # 기대: 빌드 성공
- Step 5: 커밋
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: 파일 삭제
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:
// 변경 전
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에서 다음 한 줄 삭제:
{ id: 'packages', name: 'SaaS 제품 허브(구)', description: '구 /packages 페이지', is_active: false, order_index: 104 },
- Step 4: admin/stats에서 구독 집계 제거
app/api/admin/stats/route.ts:
// 변경 전 (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:
// 변경 전 (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행을 다음으로 교체:
// 로또 이용권 확인 — 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: 잔존 참조 확인 후 테스트·빌드
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: 커밋
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: 파일 삭제
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행 삭제 (렌더 사용처 없음 확인됨):
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행을 다음으로 교체:
<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 의존성 제거
npm uninstall @portone/browser-sdk
- Step 5: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -iE 'portone|PaymentButton|payment-channels|@/lib/products' app lib
# 기대: 0건
npm test && npm run build
- Step 6: 커밋
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/packsURL은 리다이렉트가 계속 처리 -
Step 1: 파일 삭제
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: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -E "ContactForm|freelance-portfolio" app lib
# 기대: 0건
npm test && npm run build
# 빌드 후 확인: /work/website/samples/* 8종이 라우트 목록에 존재해야 함
- Step 3: 커밋
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: 파일 삭제
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 의존성 제거
npm uninstall three
grep -rn "from 'three'" app lib # 기대: 0건
- Step 3: 테스트·빌드
npm test # 기대: deepfield-mode.test 제외된 채 전체 PASS
npm run build
- Step 4: 커밋
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: 파일 삭제
git rm -r "app/api/track" "app/api/saju/lotto"
(주의: app/api/saju/analyze·app/api/saju/save-interpretation은 활성 — saju 디렉토리째 삭제 금지)
- Step 2: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -E "api/track|api/saju/lotto" app lib
# 기대: 0건
npm test && npm run build
- Step 3: 커밋
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:
-- 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: 최종 잔존 참조 스윕
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: 전체 테스트·빌드
npm test # 기대: 전체 PASS
npm run build # 기대: 빌드 성공
- Step 5: 커밋
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 라우트 존재 |