--- name: refactor-plan description: 완료된 프로젝트(문서·플랜·하네스·LOOP.md 가 있고 루프가 도는 중이 아닌 상태)의 코드 품질을 올리는 리팩토링을 계획한다. 범위 셋(기저 언어·프레임워크 교체 / 라이브러리 최신화 / 코드 정리 — 중복 제거·안 쓰는 코드·너무 큰 파일·계층 어김) 중 무엇인지 먼저 묻고, 코드를 읽어 고칠 목록(문서와 어긋난 곳 포함)을 만들고, 스택·라이브러리가 바뀌면 docs/TRD.md 후미에, 구조가 바뀌면 docs/ARCHITECTURE/ 후미에 "## 리팩토링 N: <이름>" 절을 붙이고, plan-generator 를 부분 갱신 모드로 불러 plan_setup/ 에 phase-NN-refactor-<이름> 을 끝번호로 더한 뒤 harness-update·loop-update 를 부른다. 통과 조건은 새 수용 기준이 아니라 "기존 게이트 전부 통과(행위 보존)" + 구조 조건이다. 사용자가 "리팩토링하자", "코드 정리 좀 하자", "중복 없애줘", "파일이 너무 커서 나누고 싶어", "라이브러리 버전 올리자", "의존성 최신화해줘", "Express 를 Fastify 로 바꾸자", "TypeScript 로 옮기자", "계층 분리하자", "구조 좀 잡아줘"라고 하거나, 동작은 그대로 두고 코드의 모양·의존성·스택만 바꾸려 할 때 반드시 이 스킬을 사용한다. 코드를 바로 고치기 시작하지 말고 이 스킬로 먼저 목록과 phase 를 만든다. 요구가 바뀌는 것("로그인 제대로 만들자", "권한 넣자")은 feature-add, 안 되는 것을 고치는 것은 bug-fix, docs/PRD.md 가 없으면 code-to-docs 가 먼저다. --- # 리팩토링 계획 (Layer 6 · 코드 품질) ## 이 스킬의 목적 프로젝트가 완료된 뒤 코드 품질을 올리는 일은 세 가지로 온다 — 기저 언어·프레임워크를 바꾸거나, 라이브러리를 최신으로 올리거나, 코드 자체를 정리하거나. 셋 다 **동작은 그대로 두고 모양을 바꾸는 일**이라 새 요구사항이 없다. 그래서 통과 조건도 새 수용 기준이 아니라 **"기존 게이트 전부 통과(행위 보존)"** 와 **구조 조건**(파일이 어디에 있는가, 무엇이 무엇을 import 하지 않는가)이다. 이 스킬은 코드를 고치지 않는다. **범위를 정하고, 고칠 목록을 만들고, 문서 후미에 절을 붙이고, phase 하나를 더한다.** 고치는 것은 개발 루프다. ``` 범위를 묻는다 (교체 / 최신화 / 정리 — 손대는 문서가 다르다) → 코드를 읽어 고칠 목록 (문서와 어긋난 곳도 목록에) ← 이 스킬이 직접 → docs/TRD.md 후미 "## 리팩토링 N" (스택·라이브러리가 바뀔 때만) ← trd-generator 후미 절 모드 → docs/ARCHITECTURE/ 후미 "## 리팩토링 N" (구조가 바뀔 때만) ← architecture-generator 9단계 → plan_setup/phase-NN-refactor-<이름> (고칠 목록 = 태스크) ← plan-generator 10단계 → harness-update · loop-update ← 게이트 명령·DAG → 개발 루프 실행 ← 사용자 ``` **왜 목록과 문서를 먼저 만드는가.** 이 플러그인의 모든 스킬은 문서를 읽고 움직인다. 코드를 먼저 고치면 문서가 낡고, 그다음 작업(기능 추가·플랜 갱신·다음 리팩토링)이 낡은 문서를 근거로 삼는다. 그래서 **리팩토링이 끝나면 문서와 코드가 맞아야 하고**, 문서와 어긋난 곳은 고칠 목록에 들어간다. 그리고 리팩토링은 "무엇이 깨졌는지" 를 게이트가 말해 줄 때만 안전하다 — 그래서 게이트가 활성인 프로젝트에만 들어간다(0단계). ### 여기서 하는 일과 하지 않는 일 | 하지 않는 것 | 어디서 하는가 | |---|---| | 코드를 고치는 것 — 목록에 적은 것도 | 개발 루프(`LOOP.md`)가 phase-NN 의 태스크로 고친다 | | 요구를 바꾸는 것 — "로그인 제대로", "권한 넣자", "응답 형식 바꾸자" | `feature-add`. PRD 수용 기준이 바뀌면 리팩토링이 아니다 | | 안 되는 것을 고치는 것 — "필터가 빈 배열 나와" | `bug-fix`. 게이트 결과가 바뀌면 리팩토링이 아니다 | | TD·설계 단위·태스크의 형식과 절차를 정하는 것 | `trd-generator`·`architecture-generator`·`plan-generator`. 이 스킬은 **부를 때 넘길 지시**만 정한다 | | 게이트 명령을 바꾸고 DAG 를 다시 그리는 것 | `harness-update`·`loop-update`. 끝에 부른다 | | 문서 없는 코드에 리팩토링 목록을 만드는 것 | `code-to-docs` 가 먼저다 — 현행 고정 테스트 없이는 행위 보존을 판정할 수 없다 | ### 리팩토링과 기능·버그의 경계 경계가 흐리면 리팩토링 phase 에 요구 변경이 섞여 들어가고, 그러면 "기존 게이트 통과" 가 통과 조건으로 성립하지 않는다(바뀐 동작은 기존 테스트가 잡는다). 판정은 두 질문이다. | 질문 | 예 | 아니오 | |---|---|---| | PRD 의 수용 기준 한 줄이라도 바뀌는가 | `feature-add` (요구가 바뀐다) | ↓ | | 지금 활성인 게이트의 결과가 바뀌는가 (통과하던 테스트가 실패하게 되는가) | `bug-fix` (현행 고정 테스트가 잘못된 동작을 붙잡고 있다) 또는 `feature-add` | **리팩토링** | "리팩토링하는 김에" 는 경계를 넘는 가장 흔한 말이다. 같은 파일을 만지더라도 요구가 바뀌는 부분은 떼어 `feature-add` 로 보낸다 — 두 phase 가 같은 파일을 건드려도 순서만 정하면 된다(뒤 phase 는 앞 phase 의 결과 위에서 잘린다). --- ## 0단계. 진입 조건 세 가지를 확인하고 하나라도 걸리면 멈춰 보고한다. 조용히 진행하지 않는다. | 확인 | 걸렸을 때 | |---|---| | `docs/PRD.md`·`plan_setup/STATE.md`·`harness_setup/`·`loop_setup/LOOP.md` 가 있는가 | 없으면 `code-to-docs` 로 보낸다(문서 없는 코드). 일부만 없으면 무엇이 없는지 보고하고 사용자가 정한다 | | `STATE.md` 의 `Phase Overview` 에 `in_progress` 인 phase 가 없는가 | 있으면 멈춘다 — "루프가 도는 중이다. phase 완료 뒤에 다시 부르라". 문서·STATE.md·LOG.md 의 필자는 루프의 코디네이터 하나여야 한다(Layer 5). `pending` phase 는 막지 않는다(feature-add 가 phase 를 더한 직후가 그렇다) | | 게이트가 활성인가 — `harness_setup/HARNESS.md` 1절의 게이트 넷이 `활성`(또는 `부분 활성`) | 전부 `미활성` 이면 멈춘다 — "기존 게이트 통과" 가 뜻이 없다. `code-to-docs` 로 진입한 프로젝트는 **phase-01 baseline 이 `completed`** 여야 한다. 아니면 "루프를 한 번 돌려 phase-01 을 완료시키라" | `부분 활성` 은 막지 않되, 어느 영역이 덮이지 않는지 2단계에서 목록의 "행위 보존 근거" 열에 드러난다. **git 상태**를 본다. 미커밋 변경이 있으면 알린다 — 리팩토링 전 기준점 커밋이 있어야 "리팩토링 전과 같다" 를 비교할 수 있다. 사용자가 정하기 전에는 커밋하지 않는다. --- ## 1단계. 범위를 묻는다 범위가 손대는 문서를 정하므로 **코드를 읽기 전에** 정한다. 요청에 범위가 이미 명시되어 있으면 확인만 하고, 아니면 셋 중 무엇인지 묻는다(복수 가능). | 범위 | 무엇을 바꾸나 | 손대는 문서 | 행위 보존의 기준 | |---|---|---|---| | **기저 언어·프레임워크 교체** | 런타임·언어·웹 프레임워크·ORM 같은 굵은 TD | TRD 후미 절(결정 대체) + ARCHITECTURE 후미 절(대개 구조도 바뀐다) | **블랙박스 e2e 만** — 옛 스택의 unit 은 새 스택에서 돌지 않는다(4단계) | | **라이브러리 최신화** | 버전 올림, 대체 패키지, 폐기된 API 교체 | TRD 후미 절(고정 버전·결정 대체). ARCHITECTURE 는 3절 의존 방향 표가 그 라이브러리를 이름으로 적고 있을 때만 그 행을 대체하는 후미 절("구조 변화 없음" 명시) | 게이트 넷 전부 | | **코드 정리** | 중복 제거, 안 쓰는 코드, 너무 큰 파일, 계층 어김, 이름·위치 정리 | 문서를 안 건드리거나 ARCHITECTURE 후미 절만(설계 단위가 갈라지거나 의존 방향이 생길 때) | 게이트 넷 전부 | 물을 때 함께 확인하는 것 — **요청에 요구 변경이 섞여 있는가.** "라이브러리 올리면서 로그인도 제대로" 처럼 오면 뒤쪽은 `feature-add` 로 떼어 낸다고 답하고 리팩토링 범위만 진행한다. 사용자가 "그래도 같이" 라고 하면 그 결정을 기록하되, 이 스킬은 리팩토링 phase 만 만들고 기능 부분은 `feature-add` 를 이어서 부르라고 안내한다 — 한 phase 에 섞으면 통과 조건이 둘이 된다. 범위 안에서 **크기**도 묻는다 — 전체인가, 특정 영역(예: 서버만)인가. 리팩토링 phase 가 phase-01 만큼 커지면 루프가 그만큼 돌아야 하고, 그동안 기능 추가는 막힌다(0단계 진입 조건). 사용자와 대화할 수 없는 환경이면 요청에서 읽히는 범위로 진행하고 물었을 질문과 가정을 기록으로 남긴다. --- ## 2단계. 코드를 읽어 고칠 목록을 만든다 문서(TRD·ARCHITECTURE)를 먼저 읽고 코드를 읽는다 — 문서가 "있어야 할 자리" 이고 코드가 "실제" 라, 둘을 대조해야 어긋난 곳이 보인다. as-is 문서(`> 코드에서 역으로 작성`)면 문서가 곧 지금 코드이므로 어긋남은 대개 문서를 만든 뒤 바뀐 코드이거나 as-is 가 잘못 읽은 곳이다. ### 범위마다 보는 자리 | 범위 | 어디를 보나 | 무엇이 목록이 되나 | |---|---|---| | 교체 | TRD 의 그 TD 와 **되돌리기 비용** 항목, 그 TD 에 걸린 설계 단위 전부, 옛 스택에 묶인 테스트·설정·스크립트 | 새 스택 뼈대 → FR 묶음마다 이식 → unit 재구축 → 옛 코드 제거. 되돌리기 비용에 적힌 파일이 곧 대상이다 | | 최신화 | `package.json`·잠금 파일·TRD 에 적힌 고정 버전, 각 라이브러리의 변경 로그(호환 깨짐 목록), 깊은 경로 import·폐기 API 호출부 | 라이브러리(또는 같이 올려야 하는 묶음)마다 하나. 호환 깨짐이 있으면 그 호출부가 같은 항목 | | 정리 | 같은 이름·같은 본문의 함수, import 되지 않는 파일·export, 파일 크기·핸들러 수, ARCHITECTURE 3절 의존 방향을 어기는 import, 문서에 적힌 자리와 다른 파일, 문서에 없는 모듈 | 문제 하나 = 항목 하나. "파일 하나의 수정" 에 가깝게 자른다 | ### 항목 하나의 형식 목록의 한 항목이 그대로 태스크 하나가 된다. 그래서 여기서 관찰 가능하게 써 두면 plan-generator 가 옮겨 적기만 하면 된다. ```markdown | # | 종류 | 위치 | 지금 | 목표 | 구조 조건(관찰 가능) | 영향 문서 | 행위 보존 근거 | |---|---|---|---|---|---|---|---| | 1 | 중복 | `routes/projects.js` 7~11행, `routes/tasks.js` 7~11행, `server.js` 29~30행 | `getUserId` 사본 둘 + 인라인 하나 | `lib/auth.js` 한 곳 | `getUserId` 정의가 `lib/auth.js` 에만 있다(grep 1건). 두 라우터와 `server.js` 가 그것을 import 한다 | ARCHITECTURE backend.md §3.5 (대체), overview 3절 의존 방향 | unit: `test/api/*.test.js` 의 401 케이스 전부, e2e: 로그인 흐름 | | 2 | 문서 불일치 (문서 쪽) | `docs/TRD.md` TD-002 확정도 ↔ `package.json` | 문서는 `package.json` 10~11행이라 적었는데 phase-01 이 scripts 를 더해 16~17행이 됨 | 후미 절의 대체가 현재 값 | — (태스크 아님) | TRD `## 리팩토링 N` | — | ``` 열마다 지킬 것이 있다. - **종류** — `중복` / `미사용` / `큰 파일` / `계층 어김` / `문서 불일치` / `라이브러리` / `스택`. 뒤에서 태스크 순서와 손대는 문서를 이 열로 가른다 - **구조 조건** — 분리된 세션의 검증자가 파일 시스템과 grep 만으로 판정할 수 있게 쓴다. "깔끔하다", "응집도가 높다" 는 판정할 수 없다. "파일 X 가 경로 Y 에 있다", "모듈 A 에 `require('../db')` 가 없다", "파일 Z 가 200줄 이하다" - **영향 문서** — 이 항목이 끝나면 어느 문서의 어느 절이 낡는가. TD 번호 또는 `파일명 §절번호`. 비어 있으면 문서를 안 건드리는 정리다 - **행위 보존 근거** — 이 코드의 지금 동작을 붙잡고 있는 **게이트와 테스트 파일**. **비어 있으면 그 항목은 아직 안전하지 않다** — 현행 고정 테스트를 먼저 쓰는 태스크를 phase 앞에 두거나(3단계에서 plan-generator 에 넘긴다), 사용자에게 "덮이지 않은 채 고칠지" 를 묻는다. 조용히 비워 두고 넘어가면 루프가 무엇을 깼는지 모른 채 통과시킨다 **문서와 어긋난 곳**은 둘로 가른다 — 코드가 문서를 어긴 것이면 코드를 문서에 맞추는 항목(구조 조건 = 문서의 그 절), 문서가 틀린 것(as-is 가 잘못 읽었거나 그 뒤 의도적으로 바뀐 코드)이면 문서를 고치는 항목이고 그것은 태스크가 아니라 3단계 후미 절의 "대체" 다. 어느 쪽인지 코드만으로 모르면 사용자에게 묻는다 — 문서에 맞춰 코드를 되돌리는 것은 동작이 바뀔 수 있다. **목록에 넣지 않는 것** — 동작이 바뀌는 것(경계 표). 눈에 띄면 "문서에 일부러 넣지 않은 것" 으로 보고에 옮기고 `feature-add`·`bug-fix` 를 가리킨다. ### 목록을 사용자와 확인한다 목록이 곧 phase 의 크기다. 만들고 나서 **사용자에게 보여 주고 뺄 것·더할 것을 받는다.** 범위 판단은 사용자의 것이다 — 모델이 "이것도 정리하면 좋겠다" 로 목록을 늘리면 phase 가 커지고 그동안 기능 추가가 막힌다. 같은 이유로 **문제 하나를 여러 태스크로 쪼개지 않는다** — 태스크는 "검증 가능한 단위" 이고, 사본 둘을 합치는 것을 파일마다 쪼개면 중간 상태에서 게이트가 실패한다. --- ## 3단계. 문서 후미에 `## 리팩토링 N` 절을 붙인다 **어느 문서에 붙이는가는 범위가 정한다** — TRD 는 스택·라이브러리(TD)가 바뀔 때, ARCHITECTURE 는 구조(설계 단위·의존 방향)가 바뀔 때만. 코드 정리로 문서가 안 바뀌면 절을 붙이지 않는다. **TD 의 결정("무엇을 쓰는가")이 그대로면 TRD 는 열지 않는다** — TD 본문의 되돌리기 비용·근거 파일·줄 번호 같은 위치 서술이 리팩토링 뒤 낡는 것은 TRD 를 열 이유가 아니다. 그 서술은 ARCHITECTURE 후미 절에 "(TD-003 의 되돌리기 비용은 이 절 이후 `lib/store.js` 한 파일)" 로 적고, 이미 낡아 있던 것(문서 쪽 불일치)은 보고에만 둔다. TRD 는 다음에 결정이 실제로 바뀌는 요청(라이브러리 최신화·feature-add)이 열 때 함께 정리된다. 코드 정리 요청마다 TRD 에 절을 붙이면 TRD 가 결정 문서가 아니라 변경 일지가 되고, 뒤 스킬이 어느 TD 가 실제로 바뀌었는지 절 수로 알 수 없게 된다. 붙일 때는 이 스킬이 직접 쓰지 않고 **그 문서의 스킬을 부분 갱신 모드로 부른다** — 형식·추적표·버전·`CLAUDE.md` 절은 그 스킬의 규칙이고, 이 스킬은 아래 지시만 넘긴다. ### N 과 절 형식 - **N 은 요청 단위의 공통 번호다.** 이 요청이 TRD 와 ARCHITECTURE 에 남기는 절은 전부 같은 N 이고, 절이 안 생기는 문서는 그 번호를 건너뛴다. 문서마다 따로 세면 TRD 의 "리팩토링 2" 와 overview 의 "리팩토링 3" 이 같은 요청이 되어 교차 참조가 흔들린다. N 은 `docs/` 전체에서 `## 리팩토링 (\d+)` 의 최댓값 + 1 이다. `## 추가 N`(feature-add)과는 **따로 센다** — "리팩토링 1" 이 첫 리팩토링이어야 phase 문서와 LOG 에서 읽힌다 - **절은 후미에 붙이고 기존 절 본문은 고치지 않는다.** 기존 TD·설계 단위가 바뀌는 것은 절 안에 "TD-002 를 다음으로 대체" 로 적는다 — **같은 문서 안에서는 뒤 절이 앞 절을 이긴다.** 완료된 태스크가 가리키는 절이 그대로 남아야 통과 기록이 뜻을 가진다. 예외는 부른 스킬 자체의 규칙 — 표의 행 추가·값 갱신, 버전 줄, architecture-generator 9단계의 "폐기 — 대체: §3.y" 표시 - **절 첫 줄**에 날짜 · 요청 한 줄 · 이 절이 만든 ID 목록(새 TD·새 설계 단위 절·OQ)을 둔다. 뒤 스킬이 델타만 읽는 자리다 ### TRD — `trd-generator` 후미 절 모드 ``` trd-generator 호출 지시 (refactor-plan 에서) - docs/TRD.md 가 이미 있다. 1~8단계를 다시 돌리지 말고 문서 후미에 `## 리팩토링 N: <이름>` 절 하나만 붙인다 (N = <값>) - 절 첫 줄: 날짜 · 요청 한 줄 · 이 절이 만든 ID - 바뀌는 결정은 "TD-00x 를 다음으로 대체" 로 적고 TD 여섯 항목(영역·근거 요구사항·결정·검토한 대안·확정도·되돌리기 비용·검증 방법)을 그대로 채운다. **대체는 번호를 유지한다** — TD-002 는 계속 TD-002 이고 뒤 절이 이긴다(새 번호를 주면 13절 추적표와 아키텍처 근거가 전부 옮겨 적어야 한다). 새 번호는 추가 결정(대체가 아닌 것)에만, 기존 마지막 TD 번호 다음이다. 기존 TD 본문은 고치지 않고 그 제목 바로 아래에 `> 대체됨 — '## 리팩토링 N' (YYYY-MM-DD)` 한 줄만 더한다(본문만 읽는 사람이 옛 결정을 믿지 않게) - 근거 요구사항은 기존 FR·NFR 그대로다 — 새 요구사항은 없다. 검증 방법은 "기존 게이트 통과" 와 확인 가능한 사실(버전 문자열, import 경로) - 라이브러리 최신화: 고정 버전을 옛 값 → 새 값으로 적고, 호환 깨짐(깊은 경로 import·폐기 API)이 있으면 결정 본문에 호출부와 함께 - 언어·프레임워크 교체: 8절 테스트 TD 도 대체된다 — 새 스택의 러너·lint. 옛 unit 이 새 스택에서 돌지 않는다는 것과 "행위 보존 기준은 e2e" 를 결정 본문에 적는다 - 2절 요약 표에 행을 더하고(대체 행은 옛 행 옆에 "→ 리팩토링 N" 병기), 13절 추적표의 관련 결정 열을 갱신하고, 버전을 올린다(0.1 → 0.2). 새 OQ 는 마지막 번호 다음 - CLAUDE.md·AGENTS.md 의 `## 기술` 절은 평소대로 갱신하되 `현재 단계` 줄은 refactor-plan 이 끝에 다시 쓴다 ``` ### ARCHITECTURE — `architecture-generator` 9단계 ``` architecture-generator 호출 지시 (refactor-plan 에서) - docs/ARCHITECTURE/ 가 이미 있다. 9단계(부분 갱신)만 한다 - overview.md 와 바뀌는 영역 문서의 후미에 `## 리팩토링 N: <이름>` 절을 붙인다 (N = <값>). 첫 줄은 날짜 · 요청 한 줄 · 새 절 번호 목록 - 새 설계 단위는 9단계 규칙대로 영역 문서 `## 3. 설계 단위` 의 끝번호에 붙이고(다섯 항목 전부), 갈라지거나 없어지는 기존 절은 본문을 고치지 않고 "폐기 — 대체: §3.y" 로만 표시한다. 후미 절에는 무엇이 어디로 갔는지(옛 절 → 새 절)와 바뀌는 의존 방향을 적는다 - overview 3절 의존 방향 표는 후미 절에 "다음으로 대체" 로 적는다. 리팩토링이 끝난 뒤 지켜야 할 방향이 "금지되는 방향" 열에 규칙으로 생긴다 — as-is 문서에서 "관찰" 이던 것이 여기서 처음 규칙이 된다 - 근거는 기존 FR·NFR·TD(대체됐으면 리팩토링 N 의 TD)다. 검증 기준에는 구조 조건을 적는다 — 파일 경로, import 유무 - 7절 CF: 새 절만 검토. 8절 추적표는 폐기된 절을 새 절로 옮긴다. 버전을 올린다 - 폐기한 절을 `architecture` 로 가리키는 완료 태스크 목록을 보고에 적는다 — plan-generator 가 색인에 병기한다 - CLAUDE.md·AGENTS.md 의 `## 구조` 절은 평소대로. `현재 단계` 줄은 refactor-plan 이 끝에 다시 쓴다 ``` 문서를 안 건드리는 코드 정리라도 **N 은 배정한다** — phase 문서와 LOG 가 "리팩토링 N" 으로 이 요청을 가리킨다. --- ## 4단계. `plan-generator` 10단계 호출 — phase 하나, 태스크는 고칠 목록 plan-generator 는 평소처럼 자기 절차(태스크 frontmatter·phase 문서·STATE.md 행 추가·LOG.md 한 줄·자체 점검·보고)를 다 한다. 이 스킬은 아래 지시만 넘긴다. 지시가 없으면 plan-generator 는 설계 단위마다 "만드는" 태스크를 자르고 PRD 수용 기준을 통과 조건으로 쓴다 — 리팩토링에는 둘 다 맞지 않는다. ``` plan-generator 호출 지시 (refactor-plan 에서) - plan_setup/ 이 이미 있다. 10단계(부분 갱신)만 한다. 완료된 phase·태스크는 건드리지 않는다 - phase 하나를 끝번호로 더한다: phase-NN, 파일 이름 phase-NN-refactor-<이름>.md (NN = Phase Overview 끝번호 + 1, <이름> 은 영문 — 경로·스크립트 인자에서 인코딩 문제를 피한다). phase 문서 머리에 "리팩토링 N: <이름>" 과 후미 절 위치(TRD/ARCHITECTURE 의 절, 없으면 "문서 변경 없음")를 적는다 - 태스크는 아래 고칠 목록의 항목 하나가 태스크 하나다. 항목을 쪼개거나 합치지 않는다. 항목 번호를 태스크 본문 Context 에 적는다 - acceptance_criteria 는 새 수용 기준(기능)이 아니라 둘이다 — 관찰 가능하게: ① 구조 조건: 항목의 "구조 조건" 열 그대로 ("파일 X 가 경로 Y 에 있다", "모듈 A 가 B 를 import 하지 않는다") ② 행위 보존: "lint·unit·e2e 결과가 리팩토링 전과 같다 — <행위 보존 근거 열의 테스트> 전부 통과, 케이스 수 감소 0" 실패 경로 하나: "<구조 조건을 어기면> task_validation FAIL". 기능 수용 기준(PRD 의 새 FR)은 0건이어야 한다 - verification 은 넷 그대로. task_validation 은 구조 조건을 본다(파일 시스템·grep — 관찰 수단은 harness-update 가 정의서에 적는다) - architecture 는 새 절(리팩토링 N 이 만든 §3.x)을 가리킨다. 폐기된 절은 가리키지 않는다 - files 는 항목의 "위치" 열 + 목표 위치. 옮기는 파일은 옛 경로·새 경로 둘 다 - depends_on: 종류 순서 — 스택 뼈대 → 이식 → unit 재구축 → 옛 코드 제거 → 문서 불일치 정리. 정리 항목끼리는 files 가 안 겹치면 독립 - 행위 보존 근거가 빈 항목이 있으면 그 코드의 현행 고정 테스트를 쓰는 태스크를 그 항목 앞에 두고 depends_on 으로 잇는다 - PRD 9절은 건드리지 않는다 — 리팩토링 태스크는 어느 FR 의 담당도 아니고 새 FR 도 없다. PLAN.md 6절 추적표에도 행을 더하지 않는다 - phase 의 Acceptance Criteria: 태스크 전부 completed, 게이트 넷 통과, 구조 조건 전부 성립, "PRD 9절 상태 변화 0, 테스트 케이스 수 리팩토링 전 이상", 후미 절(리팩토링 N)이 가리키는 구조와 코드가 일치 - phase 문서 머리에 "리팩토링 전 기준값" 칸을 둔다 — 기준 커밋 해시와 unit·e2e 케이스 수. 케이스 수는 이 스킬이 세지 않고 비워 두며, 코디네이터가 첫 배정 전에 게이트를 한 번 돌려 채운다 (문서에 적힌 수와 파일에서 센 수가 다를 수 있고, "감소 0" 은 실행 결과끼리 비교해야 뜻이 있다) - STATE.md 의 Phase Overview 에 phase-NN pending 행을 더한다. Current Phase 는 옮기지 않는다(루프의 것). LOG.md 에 "플랜 갱신 — 리팩토링 N phase-NN 추가, 태스크 n개" 한 줄 - CLAUDE.md·AGENTS.md 의 `## 플랜` 절은 평소대로. `현재 단계` 줄은 refactor-plan 이 끝에 다시 쓴다 ``` ### 기저 언어·프레임워크 교체일 때 지시에 더하는 것 옛 스택으로 쓴 unit 테스트는 새 스택에서 돌지 않는다. 그래서 이 범위에서는 **행위 보존의 기준이 블랙박스 e2e 뿐**이고, unit 은 그 phase 안의 태스크가 새 스택으로 다시 세운다. ``` - 행위 보존 기준은 e2e 뿐이다. 모든 태스크의 ② 는 "e2e 결과가 리팩토링 전과 같다" 로 쓴다. unit 은 기존 게이트로 세지 않는다 - unit 재구축 태스크를 둔다(phase-NN-task-MM) — 새 스택의 러너로 기존 unit 케이스를 하나도 줄이지 않고 다시 쓴다. acceptance_criteria: "옛 unit 파일의 케이스마다 대응 케이스가 있다(수 감소 0)", "새 러너로 전부 통과" - 이 태스크 뒤의 태스크만 ② 에 unit 을 다시 넣는다. 이 태스크 ID 를 phase 문서에 "unit 게이트 활성 시점" 으로 적는다 — harness-update 가 그 값을 쓴다 - 기존 e2e 가 블랙박스(HTTP·브라우저만, 앱 코드 import 없음)가 아니면 그것을 블랙박스로 바꾸는 태스크가 phase 의 첫 태스크다 — 그전에는 행위 보존 기준이 없다 - lint 도구가 바뀌면 같은 방식 — 새 lint 설정 태스크가 활성 시점 ``` 게이트 명령(unit 러너·lint)이 바뀌는 것은 **그 태스크가 끝난 뒤 라운드 사이에 harness-update ①** 로 반영한다. phase 시작 전에 한 번에 바꿔 두면 그 사이 태스크들이 안 도는 명령으로 판정된다. plan-generator 가 끝나면 보고를 그대로 전한다. 확인할 것은 넷 — phase 가 하나 늘었는가, 태스크 수 = 목록 항목 수(+ 현행 고정·unit 재구축), 모든 태스크의 acceptance_criteria 에 구조 조건과 행위 보존이 있고 기능 기준이 없는가, PRD 9절이 그대로인가. --- ## 5단계. `harness-update` · `loop-update` 호출 둘 다 평소대로 자기 절차를 한다. 넘길 것은 리팩토링이 바꾸는 것뿐이다. ``` harness-update 호출 지시 (refactor-plan 에서) - phase-NN-refactor-<이름> 이 추가됐다. 태스크는 리팩토링(행위 보존 + 구조 조건)이고 새 FR 은 없다 — E2E 흐름 목록·커버 영역에 더할 것은 없다 - task_validation 정의서에 구조 조건의 관찰 수단(파일 존재·grep 으로 import 확인)이 없으면 더한다(②) - 언어·프레임워크 교체: unit(·lint) 게이트의 활성 시점을 phase-NN 의 재구축 태스크 ID 로 옮긴다 — 활성을 미루는 것이라 ③이다. 결과(그 태스크까지 unit 없이 e2e 만으로 판정)를 제시하고 확인받는다. 게이트 명령 교체는 지금 하지 않는다 — 그 태스크가 끝난 뒤 라운드 사이에 ① 로 한다 - 라이브러리 최신화·코드 정리: 활성 시점·명령은 그대로. 구성 요소 표(TRD 9.3)가 바뀌면 local-dev 에 반영(②) - 리팩토링으로 파일이 옮겨지면 lint 대상 경로·테스트 디렉터리가 바뀔 수 있다 — 목록의 "목표" 열을 보고 명령을 맞춘다(①) ``` ``` loop-update 호출 지시 (refactor-plan 에서) - 태스크가 늘었다. DAG 를 스크립트로 다시 만들고 자원 충돌 표·폭을 다시 잰다 — 리팩토링 태스크는 같은 파일을 여럿이 만지므로 files 겹침이 많다 - 언어·프레임워크 교체: 재구축 태스크 전까지 unit 이 미활성이다 — Standing Decisions 의 미활성 승인을 그 태스크 ID 까지로 받아 적는다 - phase-NN 이 completed 되면 루프를 멈추고 사용자에게 돌아온다 ``` --- ## 6단계. `CLAUDE.md` · `AGENTS.md` — `현재 단계` 줄 이 스킬은 자기 절이 없다. 제품·기술·구조·플랜·하네스·루프 절은 3~5단계에서 부른 스킬이 각자 갱신한다. 이 스킬이 하는 것은 **맨 끝에 `현재 단계` 줄을 덮어쓰는 것** 하나다 — 중간에 부른 plan-generator 가 그 줄을 "plan-generator → 다음: harness-setup" 으로 썼고 update 계열은 그 줄을 건드리지 않으므로, 끝에서 한 번 더 쓴다. 두 파일 다. ``` - 현재 단계: refactor-plan(리팩토링 N: <이름> → phase-NN) → 다음: 개발 루프 (phase-NN-refactor-<이름>) ``` 두 파일이 서로 다르면 지금 호스트의 파일을 기준으로 다른 쪽을 맞추되 손으로 적은 줄은 양쪽에 남긴다. 200줄을 넘기지 않는다. --- ## 7단계. 자체 점검과 보고 셋이 끝나면 처음부터 다시 읽으면서 확인한다. 열은 `확인할 것 | 통과 조건 | 걸렸을 때` 다. | 확인할 것 | 통과 조건 | 걸렸을 때 | |---|---|---| | 진입 조건 | `in_progress` phase 0건, 게이트 활성(code-to-docs 진입이면 phase-01 completed) | 0단계로 돌아가 멈춘다 | | 범위 | 셋 중 하나 이상이 명시됐고 요구 변경이 섞이지 않음 | 섞인 것은 보고의 "일부러 넣지 않은 것" 으로 feature-add·bug-fix 에 | | 목록 | 모든 항목에 구조 조건(관찰 가능)과 행위 보존 근거가 있음. 근거가 빈 항목은 현행 고정 태스크가 앞에 있음 | 채우거나 태스크를 더한다 | | 문서 불일치 | 대조한 설계 단위 절 전부(누락 0). 어긋난 곳이 목록(코드 쪽) 또는 후미 절 "대체"(문서 쪽)에 있음 | 대조를 마저 한다 | | 후미 절 | 범위가 요구하는 문서에만 `## 리팩토링 N` 이 있고 N 이 같음. 첫 줄에 날짜·요청·ID. 기존 절 본문 변경 0(9단계의 "폐기 — 대체" 표시와 대체된 항목 제목 아래 `> 대체됨` 한 줄 제외) | 되돌리고 후미 절로 옮긴다 | | 기존 항목 변경 | "TD-00x 를 다음으로 대체" 형식. 기존 TD·절의 본문이 그대로 | 되돌린다 | | phase | `plan_setup/phase-NN/phase-NN-refactor-<이름>.md` 가 끝번호이고 완료된 phase 는 바이트 단위로 그대로 | plan-generator 10단계 | | 태스크 | 수 = 목록 항목 수(+ 현행 고정·unit 재구축). 모든 `acceptance_criteria` 에 구조 조건 + 행위 보존, 기능 기준 0건. `verification` 넷 | plan-generator 10단계 | | 교체 범위 | ② 가 e2e 뿐이고 unit 재구축 태스크가 있으며 phase 문서에 활성 시점이 적힘 | plan-generator 10단계 | | PRD | `docs/PRD.md` 가 바이트 단위로 그대로 | 되돌린다 — 요구가 바뀌었으면 feature-add | | STATE.md | `Phase Overview` 에 phase-NN `pending` 행. 다른 절 그대로 | plan-generator 10단계 | | 하네스·루프 | 게이트 정의서의 활성 시점이 교체 범위에서만 바뀜(③ 확인 기록). `DAG.md` 에 phase-NN 태스크가 있음 | harness-update·loop-update | | `현재 단계` 줄 | `CLAUDE.md`·`AGENTS.md` 둘 다 6단계 값, 두 파일이 같음 | 덮어쓴다 | **자체 점검 결과는 문서에 남기지 않는다.** 보고로 간다. 보고는 이렇게 한다. 1. 범위와 크기 — 셋 중 무엇, 전체/영역, 리팩토링 N 2. 고칠 목록 요약 — 항목 n개(종류별), 그중 문서 불일치 n개(코드 쪽 / 문서 쪽), 행위 보존 근거가 없어 현행 고정 태스크를 더한 항목 3. 문서 — 후미 절을 붙인 문서와 대체된 TD·설계 단위 목록. 안 붙인 문서와 이유("구조 변경 없음") 4. phase-NN — 태스크 n개, 순서(depends_on), 교체 범위면 unit 재구축 태스크와 활성 시점 5. 하네스·루프 — 바뀐 활성 시점(③이면 확인 요청), DAG 폭 6. **문서에 일부러 넣지 않은 것** — 요구 변경(`feature-add`)·버그(`bug-fix`)로 보낸 것. 요청 내용을 그대로 옮겨 다음 스킬에서 다시 설명하지 않게 7. 다음: "루프를 돌려 phase-NN 을 완료시키세요. 교체 범위면 재구축 태스크 완료 뒤 라운드 사이에 harness-update 로 게이트 명령을 바꿉니다" --- ## 범위를 넘는 요청이 함께 오면 리팩토링 요청은 다른 것과 같이 오는 일이 많다. **하지 않고, 어디로 가는지 답한다.** 조용히 무시하지도, 조용히 하지도 않는다. | 요청 | 이 스킬이 하는 것 | 왜 | |---|---|---| | "라이브러리 올리면서 로그인도 제대로 만들자" / "권한 넣자" | 최신화만 목록에. 로그인·권한은 요구 변경이라 `feature-add`. 요청 문장을 보고에 그대로 옮긴다 | PRD 수용 기준이 바뀌면 기존 테스트가 실패하는 것이 정상이 되어 "기존 게이트 통과" 가 통과 조건으로 성립하지 않는다 | | "정리하는 김에 이 버그도" | 목록에 넣지 않는다. `bug-fix`(phase 완료 뒤, 또는 리팩토링 전에 먼저) | 버그 수정은 회귀 검사·BUG-NN 기록·분리 검증이 따른다. 리팩토링 태스크에 섞이면 셋 다 빠진다 | | "리팩토링 바로 해 줘, 문서는 됐어" | 목록과 phase 는 만든다. 후미 절은 범위가 요구하면 붙인다 | 문서 없이 고치면 다음 작업이 낡은 문서를 근거로 삼는다. 루프가 고치는 것은 같다 — 문서는 그 루프가 무엇을 지켜야 하는지다 | | "테스트 없는 부분인데 그냥 고치자" | 현행 고정 테스트 태스크를 앞에 두거나, 사용자가 명시하면 "덮이지 않음" 을 태스크 본문에 적고 진행 | 행위 보존 근거가 없는 항목은 루프가 무엇을 깼는지 모른다. 결정은 사용자가 하되 기록은 남긴다 | --- ## 산출물 요약 이 스킬이 직접 쓰는 것은 없다 — 고칠 목록은 보고와 phase 문서에 남고, 문서·플랜·하네스·루프는 부른 스킬의 형식이다. 여기서는 **후미 절과 phase·태스크의 달라지는 칸**만 보여 준다. ### `docs/TRD.md` 후미 절 ```markdown ## 리팩토링 1: 라이브러리 최신화 - **날짜**: YYYY-MM-DD · **요청**: "express·uuid 를 최신으로" · **이 절이 만든 ID**: TD-002·TD-004 대체, OQ-012 - **phase**: phase-02-refactor-deps ### TD-002 를 다음으로 대체 - **영역**: 스택 - **근거 요구사항**: FR-001~FR-012 (변경 없음) - **결정**: `express` 4.17.1 → 4.21.x, `body-parser` 제거(`express.json()` 내장) - **검토한 대안**: 5.x — 라우터 경로 문법이 바뀌어 핸들러 전부 재검토. 이번 범위 밖 - **확정도**: 확정 - **되돌리기 비용**: `package.json` 두 줄과 `server.js` 6행 - **검증 방법**: 기존 게이트 넷 통과. `npm ls express` 가 4.21 을 출력 (2절 요약 표 행 추가 · 13절 추적표 갱신 · 버전 0.2 — trd-generator 규칙) ``` ### `docs/ARCHITECTURE/overview.md` · 영역 문서 후미 절 ```markdown ## 리팩토링 1: 인증 검사 한 곳으로 - **날짜**: YYYY-MM-DD · **요청**: "getUserId 중복 정리" · **이 절이 만든 ID**: backend.md §3.6 (신설), §3.5 폐기 — 대체: §3.6 - **phase**: phase-02-refactor-auth-helper ### 3절 의존 방향을 다음으로 대체 | 부르는 쪽 | 부르는 대상 | 금지되는 방향 | |---|---|---| | `routes/*.js`·`server.js` | `lib/auth.js` | 라우터가 쿠키 문자열을 직접 파싱하지 않는다 (규칙 — 리팩토링 1 부터) | ### 옮겨진 것 | 옛 절 | 새 절 | 무엇이 | |---|---|---| | backend.md §3.5 | backend.md §3.6 | `getUserId` 정의 (사본 셋 → 한 곳) | ``` ### `plan_setup/phase-NN/phase-NN-refactor-<이름>.md` 머리 ```markdown # Phase 02 — refactor-auth-helper (리팩토링 1: 인증 검사 한 곳으로) - **Goal**: 동작을 바꾸지 않고 `getUserId` 사본 셋을 `lib/auth.js` 하나로 모은다. 기준 문서: overview.md·backend.md 의 `## 리팩토링 1` - **Dependencies**: phase-01 (게이트 넷 활성) - **Status**: pending - **행위 보존 기준**: 게이트 넷 전부 (교체 범위면 "e2e — unit 은 phase-02-task-03 부터") - **리팩토링 전 기준값**: 커밋 `<해시>` · unit 케이스 수 (코디네이터가 첫 배정 전에 채움) · e2e 케이스 수 (같음) ## Acceptance Criteria 1. 태스크 전부 completed 2. lint·unit·e2e 통과, 케이스 수가 기준값(unit n·e2e m) 이상 3. `getUserId` 정의가 `lib/auth.js` 에만 있다 (`grep -rn "function getUserId"` 1건) 4. `docs/PRD.md` 바이트 단위 변경 0 · 9절 상태 변화 0 ``` ### 태스크 frontmatter 의 `acceptance_criteria` ```yaml acceptance_criteria: - "`lib/auth.js` 가 있고 `getUserId` 를 export 한다" - "`routes/projects.js`·`routes/tasks.js`·`server.js` 에 `getUserId` 정의와 `/uid=` 정규식이 없다 (grep 0건)" - "세 파일이 `require('../lib/auth')` 또는 `require('./lib/auth')` 로 가져온다" - "lint·unit·e2e 결과가 리팩토링 전과 같다 — test/api/*.test.js 의 401 케이스 전부 통과, 케이스 수 감소 0" - "실패 경로: 정의가 두 곳 이상 남아 있으면 task_validation FAIL" verification: [lint, unit, e2e, task_validation] ``` --- ## 에이전트별 참고 - Claude Code에서 실행 중이면: `references/claude-code.md` - Codex에서 실행 중이면: `references/codex.md`