에이전트 여럿에게 일을 시킬 때, 기능 하나에 이슈를 몇 개 만드는가.

안 A   계층마다 이슈를 만든다.   부모 1 + 기획·프론트·백엔드·테스트 4 = 5개
안 B   기능당 이슈 하나만 만든다. 계층은 그 이슈 안의 진행 단계다

안 A는 계층마다 상태와 담당이 트래커에 보인다. 누가 어디서 멈췄는지가 목록에 그대로 뜬다.

대신 사람이 기능 하나를 따라가려면 문서 다섯 개를 오가야 한다. 그리고 개정 전 표준이 배정을 낱개로, 커밋 전 확인을 티켓마다 하게 했으므로 이슈 개수가 그대로 사람 개입 횟수가 된다.

기능 1개  →  이슈 5개  →  배정 4회 + 커밋 확인 4회 = 개입 8회
기능 10개 →  이슈 50개 →  개입 80회

이 글은 안 B를 고른 기록이고, 동시에 이 표준을 두 번 갈아엎은 기록이다. 1차에서 이슈를 하나로 합쳤고, 2차에서 그 하나에 무엇을 어떤 문장으로 쓸지, 그리고 그 하나가 얼마나 커도 되는지를 정했다. 뒤집은 이유와 그때 같이 무너진 규칙들이 아래에 있다.


먼저: 에이전트는 사람이 있을 때만 돈다

이건 원칙이 아니라 전제다. 개정 전부터 있었고 두 번의 개정에도 그대로 남는다.

무인 운영을 하지 않기로 하면 설계해야 할 것이 통째로 사라진다.

밤·주말에 락이 걸린 채 방치되면?        → 그 시간에 아무것도 안 돈다
에이전트가 죽은 걸 어떻게 감지하나?      → 죽으면 사람이 본다
일감이 생긴 걸 어떻게 알아채나?          → 사람이 배정할 때 안다

폴링도 웹훅도 하트비트도 필요 없다. 없앤 게 아니라 애초에 안 생겼다.

대가는 분명하다. 야간과 주말에는 처리량을 벌 수 없다. 그 대가를 감수한 이유는 사람이 인지 못 한 채 굴러간 프로젝트를 되돌리는 비용이, 늦어지는 비용보다 크다는 것이다.

그리고 이 전제가 두 개정의 출발점이기도 하다. 사람이 있어야만 돈다면, 사람이 감당할 수 있는 모양이어야 한다. 이슈 다섯 개는 감당되지 않았고, 서식 없는 이슈 하나도 감당되지 않았다.


원칙 1. 기능 하나에 이슈 하나만 만든다

정해야 했던 건 이거였다 — 이슈를 무엇의 단위로 만드는가.

안 A — 계층마다 만든다. 부모 이슈 하나에 기획·프론트·백엔드·테스트 이슈를 매단다. 얻는 것은 상태와 담당이 트래커에 그대로 보이는 것이다. 어느 계층에서 멈췄는지를 목록만 보고 안다. 그리고 아직 시작하지 않은 계층에도 다른 계층이 정보를 남길 자리가 미리 생긴다.

버린 이유는 사람이 기능 하나를 따라가려면 문서 다섯 개를 오가야 하는 것이다. 그리고 그 다섯이 각자 다른 시점에 쓰였으므로, 읽는 사람은 매번 문맥을 처음부터 다시 세운다. 기능이 열 개면 이슈가 쉰 개고, 그때부터는 목록을 여는 것 자체가 일이 된다.

골랐다 — 안 B, 기능당 이슈 하나. 안 A가 못 지켜주는 것은 사람이 보는 단위와 일의 단위가 같다는 것이다. 기능 하나를 알고 싶으면 문서 하나만 열면 된다.

기획 → 개발 → 테스트

이 순서가 곧 의존이다. 트래커에 의존을 적지 않는다 — Jira 링크와 GitHub 텍스트는 표현이 달라서, 둘 다 지원하면 어댑터가 갈리고 갈리는 순간 한쪽만 고쳐진다. 적을 곳이 없으면 어긋날 곳도 없다.

대가는 트래커가 진행 상태를 덜 보여주는 것이다. 이슈 하나가 “지금 개발 중”인지 “테스트 중”인지는 목록에 안 뜬다. 그건 댓글을 열어야 안다(원칙 10).


원칙 2. 기능이 커도 기본은 쪼개지 않고 계약 개수로 신호만 띄운다

원칙 1이 「기능 하나에 이슈 하나」로 못 박았는데 그 기능의 크기를 정한 규칙이 없었다. 지금까지 정한 것들이 상한을 네 방향에서 이미 누르고 있는데도, 숫자가 없어서 어긴 것을 아무도 몰랐다.

기획이 닫을 때 계약을 다 쓴다   →  한 번의 대화로 다 뽑히는 크기여야 한다
작업 세션 하나가 기획과 개발    →  세션 하나가 감당하는 크기여야 한다
댓글이 다섯 줄을 안 넘는다      →  세 줄로 요약되는 크기여야 한다
PR 하나에 테스트까지 얹는다     →  오래 안 열려 있을 크기여야 한다

안 A — 상한을 넘으면 기획을 못 닫게 막는다. 얻는 것은 큰 이슈가 확실히 안 생기는 것이다. 버린 이유는 기능 축으로 쪼갤 수 없는 기능이 왔을 때 억지로 쪼개게 되는 것이다. 그 억지 쪼개기가 원칙 3이 금지하는 모양이 된다.

골랐다 — 안 B, 막지 않고 묻는다. 계약 개수가 기준을 넘으면 에이전트가 「쪼갤까요」를 묻고 사람이 답한다. 기본값은 「그대로 간다」다. 원칙 1이 이슈를 다섯에서 하나로 줄인 이유가 “이슈 개수가 곧 개입 횟수”였고, 쪼개는 것은 그 방향을 되돌리는 일이라 웬만하면 안 한다.

ai-map.rb가 1,000 토큰 넘는 섹션을 막지 않고 목록으로 알려주는 것과 같은 모양이다 — 크기가 문제라서가 아니라 「기능이 둘 섞였을 수 있다」는 신호라서다.

세는 것을 계약 개수로 한 이유는 그 시점에 셀 수 있는 게 그것뿐이라서다.

후보 기획이 닫는 순간에 아나
계약 개수 안다. 방금 쓴 것이다
세션이 감당할까 모른다. 해봐야 안다
사람이 읽을 diff 모른다. 기획은 코드를 읽지 않는다
배포 단위인가 주관적이라 판정이 안 선다

계약 개수가 작업량의 대리 지표가 되는 것은 원칙 7이 「한 줄 = 한 검증 항목」으로 단위를 정규화해뒀기 때문이다. 정규화가 없었으면 못 쓸 지표였다.

세는 시점이 기획이 닫기 직전인 것도 우연이 아니다. 아직 안 닫혔으므로 본문을 갈라도 원칙 8(추가만 허용)에 안 걸린다. 닫는 순간부터 불변이 시작된다.

기준 숫자는 project.yml에 둔다(원칙 17). 그 값은 재본 것이 아니다 — 한 번도 안 돌려봤으므로 근거가 없다. 설치 템플릿의 값은 임의값이고, 돌려보고 고친다. 빈칸으로 두는 안도 있었지만 아무도 안 채우면 이 규칙이 영영 안 도는 데다 안 돈다는 것조차 안 보여서 버렸다.


원칙 3. 쪼갤 때는 기능 축으로만 자르고, 못 자르면 큰 채로 간다

쪼개기로 했으면 어디를 자르느냐가 남는다. 축이 하나뿐이다.

// 자르면 안 되는 축 — 순서가 생기는데 적을 곳이 없다
이슈 A: 주문 취소 API   →   이슈 B: 주문 취소 화면

// 자르는 축 — 아무거나 먼저 해도 된다
이슈 A: 주문 취소       |   이슈 B: 부분 취소

계층으로 자르면 B는 A가 끝나야 시작할 수 있다. 그런데 원칙 1이 순서를 트래커에 적지 않기로 했으므로 적을 곳이 없고, 사람이 B를 먼저 배정해도 아무도 안 막는다. 배정받은 에이전트는 API가 없는 채로 화면을 만들다 막힌다.

둘을 가르는 검문은 새로 만들지 않았다. 원칙 12가 「테스트 세션은 자기 이슈의 계약만 읽는다」로 이미 정해뒀으므로 거기서 그대로 나온다.

이 이슈의 테스트를 이 이슈의 계약만으로 짤 수 있는가.

화면 이슈의 계약은 “취소 버튼을 누르면 취소된다”인데 백엔드가 없으면 테스트가 안 돌아 걸린다. 부분 취소는 자기 코드만으로 돌아 통과한다. 다른 후보였던 「각각 따로 배포할 수 있는가」는 배포의 뜻이 프로젝트마다 달라 판정이 흔들려서 버렸다 — 기능 플래그를 쓰면 화면 이슈도 배포 가능이 된다.

기능 축으로 못 자르면 자르지 않는다. 억지로 계층으로 자르면 아무도 모르는 순서가 생기는데, 그건 큰 이슈 하나보다 나쁘다. 대가는 원칙 2의 네 상한을 어긴 이슈가 그대로 남는 것이다.

쪼갠 상대는 서로의 「범위 밖」에 이름만 적는다.

## 범위 밖
- 부분 취소
- 관리자 강제 취소

새 자리를 만들지 않은 것이다. 원칙 6의 「범위 밖」이 원래 하던 일이고, 테스트 세션도 그 절을 읽으므로 없는 기능을 검증해서 실패로 올리는 일이 같이 막힌다.

이슈 번호는 적지 않는다. 번호를 적으면 그건 이슈 간 링크이고, 원칙 1이 피한 자리에 발을 걸친다. 대신 그 이슈를 찾으려면 사람이 트래커에서 검색해야 한다.


원칙 4. 나누는 것은 세션이지 이슈가 아니다

이슈를 합치면 곧바로 따라오는 질문이 있다 — 그러면 계층을 나눈 것은 무엇이었나.

계층을 나눈 근거를 다시 읽으면 전부 「문맥이 섞이면 안 된다」이지 「이슈가 따로여야 한다」가 아니었다.

나눈 자리 근거 실제로 요구하는 것
요구와 설계 갈려야 검증자가 생긴다 판단 주체 분리
개발과 테스트 같은 세션이면 자기가 만든 것에 맞춘 테스트가 나온다 세션 분리

이슈를 넷으로 쪼갠 것은 그 분리를 트래커에 표현하려다 나온 부산물이었다. 에이전트 종류가 넷이라 이슈도 넷이 됐고, 사람은 넷을 따라가게 됐다.

그래서 세션과 이슈를 따로 정한다.

세션 맡는 계층
작업 세션 기획 → 개발
테스트 세션 테스트

안 A — 계층마다 세션도 따로 연다. 넷을 그대로 유지한다. 버린 이유는 넘길 때마다 다음 세션이 읽을 것을 써야 하는 것이다. 그 쓰기 비용이 이슈를 장황하게 만든 원인이었다. 그리고 판단 주체가 넷이면 전환 지점도 넷이라, 이슈만 합쳐도 사람이 멈추는 횟수는 그대로다.

골랐다 — 안 B, 세션 둘. 기획과 개발은 판단 주체를 가를 이유가 없다. 요구와 설계를 갈랐던 것은 프론트가 요구를 내고 백엔드가 설계할 때의 얘기였고, 기획은 원래 API를 설계하지 않는다. 한 세션이 앞에서 사람과 기획을 만들고 뒤에서 그대로 개발한다.

대가는 개발이 기획 의도를 그대로 물려받는 것이다. 기획을 되돌아보게 만들 제3자가 세션 안에 없다. 그 자리를 사람이 든다(원칙 5·9).


원칙 5. 기획은 사람과 에이전트가 대화로 만든다

에이전트가 혼자 기획을 만들지 않는다. 사람에게 묻고 답을 받아 함께 만든다.

이 규칙이 다른 것들과 성격이 다르다. 개발과 테스트에서 사람은 검문관이지만, 기획에서는 저자다.

단계 사람의 역할
기획 같이 만든다 — 묻고 답하고 생각을 맞춘다
개발 확인한다 (커밋 전)
테스트 확인한다 (커밋 전)

안 A — 에이전트가 초안을 만들고 사람이 고친다. 얻는 것은 사람이 백지에서 시작하지 않는 것이다. 검토가 창작보다 싸다.

버린 이유는 초안이 그대로 굳는 것이다. 에이전트가 채운 빈칸은 근거 없이 그럴듯한데, 사람은 그걸 검토 대상이 아니라 배경으로 읽는다. 그리고 기획 의도는 에이전트의 소유 범위 밖이라, 채워 넣은 순간 소유 범위를 넘은 것이다.

골랐다 — 안 B, 대화로 만든다. 안 A가 못 지켜주는 것은 채워진 빈칸과 정해진 것을 구별하는 일이다. 물어서 답을 받으면 그건 정해진 것이고, 안 물었으면 아직 안 정해진 것이다. 경계가 흐려지지 않는다.

이건 「막히면 사람에게 올린다」를 앞쪽 끝까지 민 것이다. 그 규칙은 막혔을 때 올리게 하는데, 이 원칙은 막히지 않아도 애초에 혼자 시작하지 못하게 한다.

대가는 기획이 자동화 대상에서 빠지는 것이다. 개입 횟수만 보면 이게 제일 무겁다. 그 계산은 원칙 11에 있다.


원칙 6. 본문은 기획이 만든 것 전부를 다섯 절로 담는다

이슈가 하나면 그 본문이 언제 무엇을 담는지를 정해야 한다.

안 A — 사람이 이슈를 만들 때 확정한다. 기획 결과는 세션에만 남는다. 얻는 것은 본문이 가장 빨리 존재하는 것이다. 배정과 동시에 읽을 것이 생긴다.

버린 이유는 기획이 구체화한 것이 어디에도 안 남는 것이다. 입력 범위·예외·상태 전이는 대화에서 나오는데, 본문이 그 앞에서 닫히면 테스트가 검증할 수 없는 것이 생긴다. 테스트 세션은 기획 대화를 보지 못한다(원칙 12).

골랐다 — 안 B, 기획이 닫을 때 확정한다. 안 A가 못 지켜주는 것은 기획 산출물이 놓일 자리다.

그 자리를 자유 산문으로 두는 안도 있었다. 버린 이유는 무엇이 빠졌는지 아무도 모르는 것이다. 범위 밖을 안 적으면 테스트가 과잉 개발하고, 미정을 안 적으면 그걸 버그로 올린다. 빠진 것은 빈칸으로 보이지 않는다. 그래서 절을 고정했다.

## 목적
주문 취소 시 결제까지 함께 취소한다.

## 범위 밖
- 부분 취소
- 취소 수수료 정산

## 계약
- `S200_CANCEL_본인_PAID` PAID, PREPARING 상태만 취소할 수 있다
- `S400_CANCEL_본인_SHIPPED` 그 외 상태는 400으로 거부한다
- [C3] 결제 취소가 실패하면 주문 취소도 롤백된다
- [C4] 취소된 주문은 CANCELED 로 전이한다

## 정하지 않은 것
- 취소 알림 발송 여부

## 버린 안 — 테스트 세션은 읽지 않는다
- 취소 요청을 큐에 넣고 비동기 처리 → 결제사 응답을 사용자가 즉시 못 본다
- 취소 가능 상태를 설정값으로 뺌 → 상태 전이가 코드 밖으로 나가 검증이 안 된다

「버린 안」이 본문에 들어온 것이 2차 개정에서 뒤집힌 자리다. 1차에서는 본문에 계약만 담고 버린 안을 첫 댓글로 보냈는데, 댓글은 세 줄에서 다섯 줄이라 안 다섯 개가 들어가지 않는다. 규칙 둘이 같은 글에서 서로를 부정하고 있었다.

옮길 자리 후보가 셋이었다. decisions.md로 전부 보내는 안은 기능별 잡동사니로 커져서 「기능을 넘는 것만」이라는 그 파일의 기준을 무너뜨린다(원칙 18). 기획 댓글만 분량 예외로 두는 안은 「댓글은 짧다」가 첫 댓글부터 깨져서 나머지 넷의 근거까지 약해진다.

골랐다 — 본문에 절로 둔다. 둘이 못 지켜주는 것은 사람이 문서 하나만 열고 계약과 그 경로를 같이 보는 것이다. 원칙 1이 이슈를 하나로 합친 이유가 그것이었다.

대가는 원칙이 하나 무너진 것이다. 1차의 “본문에는 계약만 담는다”는 더 이상 사실이 아니다. 본문은 기획의 산출물 전부이고, 댓글은 일어난 일이다. 다만 진행 상황·구현 방법·작업 결과는 여전히 본문 밖이다.


원칙 7. 계약은 통과와 실패를 가르는 한 줄로 쓰고 이름으로 가리킨다

절을 고정해도 계약 절 안을 어떻게 쓰는지가 남는다.

안 A — 산문으로 서술한다. 얻는 것은 기획이 가장 가볍다는 것이다. 대화에서 나온 말을 그대로 옮기면 된다.

버린 이유는 테스트가 무엇을 몇 개 검증해야 하는지 판단해야 하는 것이다. 한 문단에 조건이 셋 섞여 있으면 세 개로 읽을지 하나로 읽을지가 사람마다 다르고, 원칙 12이 요구하는 「계약으로 못 짜겠으면 올린다」의 판정 기준이 서지 않는다.

골랐다 — 안 B, 한 줄이 검증 항목 하나다.

가리키는 값은 처음에 [C1] 번호였다. 나중에 이름으로 뒤집었다. 번호는 짧아서 댓글에 쓰기 좋았는데, 두 가지가 걸렸다.

하나 — 이슈마다 C1부터 다시 센다. C2로 전체 검색을 하면 이슈 수만큼 걸리고, 어느 이슈의 것인지 열어봐야 안다. 번호가 이슈 안에서만 통하는 값이었다.

둘 — 계약표 표준과 부딪혔다. API 테스트 계약표 표준「계약에 순번을 매기지 않는다」고 이미 정해뒀다. 이름이 무엇을 검증하는지 말하므로 번호는 정보를 더하지 않는다는 이유였다. 같은 계약을 한쪽은 번호로, 다른 쪽은 이름으로 부르고 있었다.

그래서 계약표가 이미 소유한 이름 체계를 그대로 쓴다 — S{상태코드}_{ENDPOINT}_{ACTOR}_{STATE}. 전역에서 유니크하고, 검색되고, 뜻까지 같이 보인다. 대신 댓글이 길어진다. 여럿을 가리킬 때는 공통 앞부분으로 줄여 S200_CANCEL_*처럼 쓴다.

이슈를 쓸 때 상수가 아직 없어도 된다. 이름이 표의 칸에서 기계적으로 정해지므로 표만 적으면 이름이 따라 나온다.

// 계약이 아니다 — 통과와 실패를 가를 수 없다
- `S200_CANCEL_본인_PAID` 취소를 적절히 처리한다

// 계약이다
- `S200_CANCEL_본인_PAID` PAID, PREPARING 상태만 취소할 수 있다

번호가 이 표준에서 제일 많은 것을 푼 장치다. 댓글이 짧아야 하는데 짧으면 무엇을 가리키는지 흐려진다는 문제가 있었는데, 번호가 생기자 세 줄 안에서 정확히 지목할 수 있게 됐다.

[테스트 완료] C1~C3 검증. C4 미검증.
[확인 필요] C2가 모호하다. 개발에서 막혔다.
[답] S400_CANCEL_본인_SHIPPED → 재취소는 400.
본문에 S400_CANCEL_본인_CANCELED 추가함.

댓글의 분량 제한이 처음으로 지킬 수 있는 규칙이 됐다. 1차에서 3~5줄이 안 지켜졌던 이유가 가리킬 좌표가 없어서였다.

대가는 기획이 문장을 쪼개는 수고를 지는 것이다. “적절히 처리한다”로 넘어가지지 않으므로 모호한 자리가 기획 단계에서 드러나고, 그때 사람에게 다시 물어야 한다. 그 수고가 원칙 5이 기획을 대화로 만든 이유와 같은 방향이다.


원칙 8. 본문은 계약 추가만 허용하고 수정과 삭제는 막는다

본문이 곧 계약이면 그걸 고칠 수 있느냐가 남는다. 1차에서는 “그 뒤로 고치지 않는다”였는데, 그 규칙이 곧바로 다른 규칙과 부딪혔다.

막혀서 확인 필요로 올리는 이유가 대부분 「계약이 부족하다」인데, 사람이 답을 주면 그 답이 곧 계약이다. 본문이 완전히 불변이면 그 답이 갈 곳이 없다.

안 A — 사람의 답을 댓글에 두고 그게 계약 효력을 갖는다. 얻는 것은 본문을 전혀 안 건드리는 것이다. 버린 이유는 테스트가 그 답을 못 보는 것이다. 원칙 12이 댓글을 못 읽게 하므로 계약이 둘로 갈라지고, 테스트는 갈라진 줄 모른 채 통과한다.

안 B — 이슈를 닫고 새로 판다. 얻는 것은 본문이 완전한 불변이라 기획이 쓴 것과 테스트가 본 것이 100% 같은 것이다. 버린 이유는 작은 누락 하나에도 이슈가 번식하는 것이다. 원칙 1이 세운 「기능 하나에 이슈 하나」가 바로 무너진다.

골랐다 — 안 C, 추가만 허용한다. 둘이 못 지켜주는 것은 계약을 한 곳에 유지하면서 이슈도 하나로 유지하는 것이다.

- `S400_CANCEL_본인_CANCELED` 재취소 요청은 400으로 거부한다   (추가: #7 답변)

불변의 뜻이 여기서 정확해진다 — 「글자가 안 변한다」가 아니라 「이미 쓴 계약의 뜻이 안 바뀐다」다. 그래서 규칙이 셋이다. 기존 줄을 고치지 않는다, 지우지 않는다, 번호를 재사용하지 않는다.

마지막이 조용한 함정이다. [C3]을 지우고 그 번호를 다른 뜻으로 다시 쓰면 이미 달린 “C3 검증함” 댓글이 거짓말이 된다. 번호는 댓글이 가리키는 주소라서, 주소를 재사용하면 과거 기록 전체가 어긋난다.

대가는 본문에 죽은 줄이 남는 것이다. 범위가 줄어도 그 계약을 지우지 못한다. 그때는 새 줄로 뒤집고 옛 줄을 남긴 채 둔다 — 뒤집힌 기록이 남는 편이 기록이 사라지는 것보다 낫다.


원칙 9. 댓글은 사람만 읽는다

이슈 글이 장황해진 진짜 원인은 독자가 둘인데 요구가 정반대라는 것이었다.

에이전트가 읽는다  →  손실이 없어야 한다. 요약하면 믿고 틀리거나 못 믿고 다시 읽는다
사람이 읽는다      →  짧아야 한다. 길면 안 읽는다

한 문서가 둘을 겸하면 장황한 쪽으로 수렴한다. 손실이 더 비싼 실패라서다.

안 A — 둘을 한 문서에 담되 섹션으로 가른다. 사람용 요약을 위에 두고 에이전트용 전문을 아래에 붙인다. 얻는 것은 한 곳만 보면 되는 것이다.

버린 이유는 가른 자리가 지켜지지 않는 것이다. 요약에 세부가 새고 전문에 소감이 섞인다. 그걸 검사할 사람이 없고, 어긋나도 아무것도 안 깨지므로 틀어진 채로 계속 간다.

골랐다 — 안 B, 독자를 하나로 자른다. 댓글은 사람만 읽는다. 에이전트가 이어받을 정보는 세션이 든다. 안 A가 못 지켜주는 것은 짧게 쓸 근거다. 에이전트가 읽지 않는다고 정해두면 짧아도 되는 이유가 규칙으로 생긴다.

본문과 댓글의 분업이 여기서 한 문장으로 정리된다 — 본문은 「합의된 것」이고 댓글은 「일어난 것」이다. 합의는 고쳐 쓰면 이전 합의가 사라지지만, 일어난 일은 쌓여야 한다. 그래서 이미 쓴 댓글도 고쳐 쓰지 않는다. 바뀌었으면 새 댓글을 단다.

대가는 무겁다. 세션이 끊기면 복원할 수 없다.

세션이 살아 있다  →  탐색 0회. 문맥이 이미 있다
세션이 죽었다     →  전부 다시 파악한다. 이슈에는 계약과 사람용 세 줄뿐이다

다시 파악하고 진행하기로 했다. 복원을 위한 기록을 남기지 않는다 — 그 기록을 남기는 비용이 애초에 장황함의 원인이었다. 세션이 자주 죽는다면 이 판단이 뒤집힌다.


원칙 10. 댓글은 사건마다 하나씩 다섯 서식 중 하나로 쓴다

1차에서는 「한 계층당 댓글 하나, 세 줄에서 다섯 줄」이었다. 그런데 확인 필요는 계층이 아니라 사건이고, 한 계층에서 여러 번 난다. 계층당 하나를 지키려면 이미 쓴 댓글을 고쳐야 하는데, 그건 원칙 9의 「일어난 일은 쌓인다」를 깬다.

단위를 계층에서 사건으로 옮겼다. 그리고 무엇을 빠뜨렸는지 검사할 기준이 없으면 필수 항목이 조용히 빠지므로 서식을 다섯으로 고정한다.

[기획 완료] 계약 4개 확정.
- 부분 취소는 범위 밖으로 뺌
- 판단 필요: 없음
[개발 완료] C1~C4 구현.
- 결제 취소를 한 트랜잭션으로 묶음
- 판단 필요: 없음
PR #42 · feat/order-cancel
[테스트 완료] C1~C3 검증. C4 미검증.
- C4는 외부에서 상태를 확인할 경로가 없다
[확인 필요] C2가 모호하다. 개발에서 막혔다.
- "그 외 상태"에 CANCELED 재취소가 포함되는지 안 적혀 있다
- 구현을 임의로 정하면 계약이 조용히 바뀐다
[답] S400_CANCEL_본인_SHIPPED → 재취소는 400.
본문에 S400_CANCEL_본인_CANCELED 추가함.

두 항목이 선택이 아니라 필수다.

  • [개발 완료]의 PR 번호와 브랜치 이름. 원칙 13이 「사람이 테스트 세션에 브랜치를 준다」로 정했는데, 사람이 그 브랜치를 읽는 곳이 여기뿐이다. 1차 규칙의 “링크로 대신할 수 있으면”은 선택으로 읽혔고, 그러면 경로가 끊긴다
  • [테스트 완료]의 검증한 번호와 못 한 번호. 사람이 세 줄만 보고 계약 대비 커버리지를 안다. 번호를 붙인 값어치가 여기서 나온다

[답]은 누가 쓰는지도 정해야 했다. 사람은 세션 안에서 말로 답하지 트래커에 타이핑하지 않는다. 사람이 직접 쓰게 하는 안은 귀찮으면 세션에서만 답하고 트래커에는 안 남아 계약이 조용히 사라진다.

골랐다 — 에이전트가 받아적고 사람이 확인한다. 대신 에이전트가 사람의 말을 계약 문장으로 번역하므로 번역 오류가 계약에 박힐 수 있다. 확인을 붙인 것이 그걸 막는 장치이고, 확인 전에는 계약을 추가하지 않는다.


원칙 11. 계층이 바뀔 때 배정하지 않고 승인받는다

이 원칙의 마지막 조항(개발 → 테스트는 배정이다)은 멀티 에이전트 위상 표준이 회수했다. 아래에 고쳐 적었다.

계층 전환을 사람이 어떻게 통과시키는가.

안 A — 계층마다 사람이 배정한다. 개정 전 방식이다. 얻는 것은 사람이 매번 무엇을 시키는지 고르는 것이다.

버린 이유는 배정이 사람에게 문맥 재구성을 시키는 것이다. 트래커를 열고 티켓을 찾고 읽고 담당을 정한다. 횟수보다 이 준비 비용이 크다.

골랐다 — 안 B, 에이전트가 묻고 사람이 답한다. 계층을 넘기 전에 넘어가도 되는지 묻고 답을 받는다. 안 A가 못 지켜주는 것은 사람이 이미 문맥 안에 있을 때 묻는 것이다. 방금 결과를 본 자리에서 예/아니오만 하면 된다.

이건 「에이전트가 큐에서 스스로 집는다」로 되돌아가는 게 아니다. 그 안을 버린 이유는 “사람이 인지 못 한 채 굴러간다”였는데, 묻고 기다리는 것은 인지의 반대가 아니라 인지 그 자체다.

개정 당시에는 둘 중 하나만 승인이었다. 개발 → 테스트는 세션이 끊기므로 물어볼 세션이 없고, 거기는 사람의 배정이었다.

그 자리를 멀티 에이전트 위상 표준이 회수했다. 2depth가 3depth를 직접 만들므로 사람이 끼지 않는다. 테스트가 필요한지는 구현해 본 쪽만 알기 때문이다.

1. 이슈 배정 (작업 세션 시작)
2. 기획 → 개발 전환 승인        ← 세션 안에서 묻는다. 예/아니오
3. 커밋 전 확인 (개발 결과)
4. 테스트 세션 시작              ← 2depth 가 3depth 를 만든다. 사람이 안 낀다
5. 커밋 전 확인 (테스트 결과)
6. PR 머지

개입 8회에서 6회가 됐지만, 6회의 무게가 균등하지 않다. 1번은 기획 대화 전체라 나머지 다섯을 합친 것보다 무겁다.

이건 손해가 아니라 자리 이동이다. 그 대화는 지금도 어차피 일어난다 — 다만 흩어진 티켓 넷에 나눠져서, 사람이 넷을 오가며 문맥을 매번 다시 세운다. 재구성 비용을 없애는 대신 대화를 앞에 한 번 몰아 넣는 것이다.

여기서 3·5번(커밋 전 확인)을 없애는 안은 아예 안 올렸다. 커밋 대기 표준이 이미 답한 자리라서다 — 커밋해버리면 멈출 계기가 사라진다.


원칙 12. 테스트는 세션을 끊고 본문의 세 절만 읽는다

테스트를 왜 따로 떼는가. 같은 세션이 구현을 들고 있으면 자기가 만든 것에 맞춘 테스트가 나오고, 구현이 틀렸을 때 테스트도 같이 틀린다.

그런데 1차 이전에는 이 규칙이 같은 표준 안의 다른 규칙과 부딪혔다. 착수할 때 앞 계층 이슈를 전부 읽게 했으므로, 규칙을 지키면 백엔드 이슈를 읽게 되고 읽는 순간 구현 의도가 그대로 들어왔다. 세션을 가른 값어치가 사라진다. 이건 장부에 열어둔 채 닫지 못한 항목이었다.

세션을 끊고 댓글을 사람 전용으로 잘라내자 그 경로가 구조적으로 사라졌다.

개발 세션의 문맥   →  세션이 끊겼으므로 못 본다
개발이 남긴 댓글   →  원칙 9이 읽지 못하게 한다
읽을 수 있는 것    →  이슈 본문

그런데 2차에서 본문에 「버린 안」이 들어오면서 옆문이 하나 생겼다. 버린 안에는 “이 방식으로 만들려다 말았다”가 섞일 수 있고, 그건 구현 의도다.

그래서 읽는 절을 지정한다 — 「계약」·「범위 밖」·「정하지 않은 것」 셋이다.

읽나
계약 읽는다 검증할 대상 그 자체다
범위 밖 읽는다 없으면 안 만들기로 한 것까지 테스트한다
정하지 않은 것 읽는다 없으면 미정을 버그로 올린다
버린 안 읽지 않는다 구현 의도가 섞일 수 있다
목적 읽는다 한 문장이라 의도가 실리지 않는다

규칙만 두면 안 지켜지므로 절 제목 자체에 박는다## 버린 안 — 테스트 세션은 읽지 않는다. 읽는 쪽이 그 줄을 보는 순간 알게 된다.

  • 계약 이름마다 테스트를 대응시킨다. 이름에 걸리지 않는 테스트를 만들지 않는다.
  • 계약으로 테스트를 짤 수 없으면 구현을 읽지 말고 확인 필요로 올린다.

마지막 줄이 중요하다. 못 짜겠을 때 구현을 열어보는 것이 가장 자연스러운 다음 행동인데, 그게 이 원칙을 무효로 만든다. 계약으로 안 짜이면 그건 테스트의 실패가 아니라 계약이 부족하다는 신호다.

개발 세션에서 하는 간단한 동작 확인은 허용한다. 계약 검증만 테스트 세션이 한다.


원칙 13. 테스트 브랜치는 개발 브랜치에서 따고 개발 PR에 얹는다

테스트 세션이 무엇을 읽는지를 정하고 나면 어디에 서서 읽는지가 남는다. 1차 규칙에는 이 자리가 아예 비어 있었다 — PR 주소는 댓글에만 있고 테스트는 댓글을 못 읽으니, 규칙만 따르면 검증 대상을 찾을 방법이 없었다.

먼저 누가 알려주는가를 정했다. 본문에 기계 판독 칸을 두는 안은 기획 시점에 없는 값을 본문에 넣게 되어 원칙 8의 불변이 깨진다. 트래커 필드를 쓰는 안은 Jira와 GitHub의 표현이 달라 어댑터가 갈리고, 원칙 1이 의존 관계에서 이미 피한 자리로 되돌아간다.

골랐다 — 테스트 세션을 여는 쪽이 브랜치를 준다. 어차피 그 자리에서 세션이 열리니 거기에 얹는다.

개정 당시 그 「여는 쪽」은 사람이었다. 사람이 [개발 완료] 댓글에서 브랜치 이름을 읽어 넘겼고, 그래서 원칙 10이 그 항목을 필수로 만들었다. 에이전트끼리는 못 넘기지만 사람을 거치면 넘어간다는 계산이었다.

멀티 에이전트 위상 표준이 그 자리를 2depth로 옮겼다. 넘기는 것은 이슈 번호와 브랜치 이름 둘뿐이고, 구현 요약이나 변경 파일 목록은 넘기지 않는다. 넘기는 주체가 사람에서 구현한 당사자로 바뀌면서 옆문이 하나 생겼기 때문이다 — 방금 짠 쪽이 프롬프트를 쓰면 원칙 12가 막으려던 구현 의도가 거기로 샌다.

그 다음이 어디서 따는가다.

안 A — 개발 PR을 먼저 머지하고 통합 브랜치에서 딴다. 얻는 것은 PR이 오래 안 열려 있고 충돌이 작게 쌓이는 것이다. 버린 이유는 계약 검증을 안 거친 코드가 통합 브랜치에 먼저 들어가는 것이다. 테스트가 실패하면 이미 들어간 것을 되돌려야 한다.

골랐다 — 안 B, 개발 브랜치에서 따서 개발 PR에 얹는다. 안 A가 못 지켜주는 것은 검증된 것만 통합 브랜치에 들어간다는 것이다. 그리고 기능 하나 = 이슈 하나 = PR 하나로 단위가 전부 맞는다.

대가는 PR 하나가 오래 열려 있는 것이다. 그동안 통합 브랜치가 앞서가면 머지 충돌이 커진다.


원칙 14. 막히면 이슈를 닫지 말고 확인 필요로 올린다

에이전트가 못 하겠다고 판단했을 때 무엇을 하는가.

안 A — 자기가 판단해서 계약을 바꾼다. 구현할 수 없으면 스펙을 조정해서 진행한다. 얻는 것은 사람을 안 부르고 계속 도는 것이다.

버린 이유는 끊을 주체가 없는 루프가 생기는 것이다. 계약을 바꾸면 테스트가 검증할 대상이 바뀌고, 그건 기획 의도까지 되돌아가야 풀리는 문제일 수 있다. 그 판단은 에이전트의 소유 범위 밖이다.

골랐다 — 안 B, 원인을 [확인 필요] 댓글에 적고 라벨을 붙이고 멈춘다. 닫지 않는다.

막힌 자리를 계약 이름으로 가리킨다. 이름이 있으면 사람이 본문의 그 줄만 보면 되고, 답이 계약 한 줄 추가로 바로 이어진다(원칙 8·8). 이름으로 가리킬 수 없는 문제면 그렇게 적는다 — 그건 계약 절 자체가 부족하다는 신호라서 다른 답이 필요하다.

1차 이전에는 닫지 않는 이유가 하나 더 있었다 — 닫힌 이슈가 다음 계층의 대기를 풀어버렸다. 이슈가 하나가 되면서 그 경로는 사라졌지만, 확인 필요는 그대로 남는다. 기다리는 대상이 사람이라는 것은 이슈 개수와 무관하다.

그래서 라벨이 하나만 남는다. 선행 계층을 기다리는 라벨은 없앤다 — 이슈가 하나이고 순차라 그런 상태 자체가 없다.

라벨 무엇을 기다리나 누가 떼나
확인 필요 사람의 판단 사람

라벨이 줄면서 규칙도 하나 사라졌다. “자기 blocked는 자기가 떼지 않는다”는 쓰는 권한과 푸는 권한을 가르는 검문이었는데, 새 구조에서는 그 권한이 통째로 사람에게 간다(원칙 11). 에이전트끼리 갈랐던 것을 사람과 에이전트 사이에서 가르는 것이라 검문은 오히려 세진다.


원칙 15. 겹치는 파일은 목록으로 막지 않고 사람의 배정 순서로 푼다

이 항목은 1차에서 정했다가 2차에서 통째로 지웠다. 원래는 이슈에 만질 파일 목록(touch)을 붙이고, 착수 전에 겹치면 시작하지 않고 작업 중에는 그냥 진행하는 비대칭 규칙이었다.

비대칭의 근거는 지금도 맞다 — 착수 전에는 사람이 순서만 바꾸면 되니 회피가 공짜인데, 작업 중에는 이미 투자한 것을 버려야 하니 중단이 비싸다.

지운 이유는 목록이 놓일 자리가 없어서다. 2차에서 본문과 댓글의 소유가 확정되자 세 곳 다 막혔다.

본문에 두면   원칙 6가 금지한 구현 정보가 들어오고,
              테스트가 원칙 12을 지켜도 구현 위치를 알게 된다
댓글에 두면   착수 전 겹침 검사를 하려고 에이전트가
              다른 이슈의 댓글을 읽어야 해 원칙 9이 깨진다
기획이 쓰면   기획은 코드를 읽지 않는다. 추측으로 채우게 된다

세 번째가 결정적이다. 애초에 기획 단계에 없는 값을 기획 산출물에 요구하고 있었다.

골랐다 — 없앤다. 사람이 배정 순서로 푼다. 이슈가 기능 단위라 사람은 목록 없이도 “이 둘은 같은 데를 건드린다”를 안다. 그리고 파일 단위 락은 원래 실제 충돌보다 과하게 잡았다 — 같은 파일이라도 다른 곳을 고치면 git이 알아서 합친다.

같이 사라진 규칙이 둘 더 있다. 착수 절차의 겹침 검사가 빠져 착수가 두 단계가 됐고, 확인 필요가 붙은 이슈의 touch를 풀지 않는다는 규칙도 사라졌다. 뒤엣것은 금요일 저녁에 걸린 이슈가 월요일까지 파일을 잠그는 문제였는데, 목록이 없어지면서 잠글 것 자체가 없어졌다.

대가는 사람의 판단이 틀리면 충돌이 머지로 넘어가는 것이다. 그때는 배정받은 에이전트가 푼다 — 읽는 것은 양쪽 이슈 본문의 「계약」 절뿐이다. 충돌은 코드 문제라 계약과 코드만 있으면 풀리고, 댓글을 읽지 않으므로 원칙 9이 지켜진다. 계약으로 판단이 안 서면 확인 필요로 올린다.


원칙 16. 대기 브랜치는 폐기됐다 — 통합 브랜치의 점유자가 고정되면서 전제가 사라졌다

이 원칙은 더 이상 유효하지 않다. 멀티 에이전트 위상 표준이 마스터를 통합 브랜치에 상주시키면서 서브가 통합 브랜치로 갈 이유 자체가 없어졌다. 작업 브랜치는 worker-start --base-branch <통합 브랜치>로 딴다 — git은 체크아웃하지 않은 브랜치를 기준으로 새 브랜치를 따는 것을 막지 않는다.

지운 게 아니라 남겨둔 이유는, 이 규칙이 왜 생겼는지가 왜 사라졌는지의 근거이기 때문이다. 이점이 있어서 만든 규칙이었다면 지금도 남아 있었을 것이다. 아래는 그 경위다.

이 항목은 한 번 정했다가 뒤집었다. 원래는 “워커별 브랜치를 만들지 않는다”였다.

원래의 버린 이유는 지금도 유효하다 — 담을 내용이 없고 동기화 지점만 는다.

뒤집은 것은 이점을 발견해서가 아니라 다른 길이 막혀서다. 에이전트마다 워크스페이스가 따로인데 별도 클론이 아니라 워크트리다. 워크트리는 .git 하나를 공유하고, 한 브랜치는 한 워크트리에서만 체크아웃된다.

워커 A 가 dev 에 서 있다
  → 워커 B 는 dev 로 이동조차 못 한다

별도 클론이었으면 안 걸렸을 문제다. 그래서 이건 안이 틀렸던 게 아니라 전제가 바뀐 것이다. 클론을 파주는 도구가 생기면 되돌릴 후보로 남는다.

맞추는 방법에도 안이 둘이었다.

안 A — 대기 브랜치에 dev를 머지한다. 익숙한 방법이다. 버린 이유는 대기 브랜치에만 쌓이는 머지 커밋이 어디로도 안 가는 것이다. 대기 브랜치는 푸시하지 않으므로 그 커밋은 영원히 로컬에 남는다.

골랐다 — 안 B, git reset --hard origin/dev로 덮어쓴다. 담는 것이 없으니 히스토리를 보존할 이유도 없다.

git fetch origin
git switch <대기 브랜치>
git reset --hard origin/dev

이 한 줄이 커밋 대기 표준과 부딪혔고, 그쪽을 고쳐서 풀었다. 그 표준은 되돌리는 명령을 금지하되 「담는 것이 없다고 표준이 선언한 브랜치를 통합 브랜치 최신으로 맞출 때 + 작업 트리가 깨끗할 때」를 예외로 둔다. 대기 브랜치가 그 선언이고, 깨끗함은 착수 1번이 검사한다.

작업 브랜치는 대기 브랜치에서 딴다. 방금 덮어썼으므로 내용은 dev와 같고, PR 대상만 dev로 되돌린다. 결과가 모이는 곳이 기준이다.


원칙 17. 프로젝트 고유의 값은 .coding-standards/project.yml에 둔다

개정 전 표준은 “프로젝트마다 다르므로 프로젝트 설정에 둔다”까지만 적고 그 자리를 정하지 않았다. 그러면 프로젝트마다 다른 곳에 적히고, 에이전트가 어디를 볼지 모른다.

자리를 아무 데나 잡으면 안 되는 이유가 있다. 설치기가 덮어쓰는 목록이 정해져 있다.

CLAUDE.md   AGENTS.md   .agents/   .claude/   .codex/
.github/copilot-instructions.md   .github/instructions/   config/

안 A — 루트 CLAUDE.md에 프로젝트 설정 절을 둔다. 얻는 것은 에이전트가 1단에서 판단 없이 읽는 것이다. 매 요청에 들어오므로 확실하다.

버린 이유는 관리 경로라 업데이트할 때마다 사라지는 것이다. 그리고 사라진 걸 아무도 안 알려준다. 표준 동기화 표준 원칙 7이 같은 이유로 「사본에서 규칙을 고치지 않는다」를 이미 정해뒀다. 그때는 규칙이 걸렸고 이번엔 설정이 같은 자리에 걸린다.

안 B — 표준마다 루트에 파일을 하나씩 둔다. 버린 이유는 표준이 늘 때마다 루트가 늘고, 에이전트가 볼 곳이 매번 달라지는 것이다. 지금 표준이 스물여덟 개다.

골랐다 — 안 C, .coding-standards/project.yml. 이 디렉터리는 관리 경로 목록에 없어서 업데이트가 손대지 않는다. 그리고 manifest.json·update.sh가 이미 거기 있어 표준 관련 파일이 한 곳에 모인다.

<프로젝트>/.coding-standards/
    manifest.json    설치기가 매번 덮어쓴다. 손대지 않는다
    project.yml      ← 사람이 쓴다. 업데이트가 손대지 않는다
    decisions.md     ← 원칙 18
    update.sh

설치기는 템플릿을 만들되 이미 있으면 덮어쓰지 않는다. manifest.json은 매번 새로 쓰는데, 설정 파일에 같은 짓을 하면 첫 업데이트에서 프로젝트 설정이 사라진다.

담는 것은 다섯이다 — 트래커 종류와 접속 방법, 계층의 실제 이름, 확인 필요 라벨의 실제 이름, 워크스페이스 경로와 통합 브랜치 이름, 쪼갤지 묻기 시작하는 계약 개수(원칙 2).

「에이전트별 대기 브랜치 이름」은 여기 있다가 빠졌다. 원칙 16이 폐기되면서 적을 것이 없어졌다.

계층 이름이 새로 들어온 자리다. 「기획 → 개발 → 테스트」가 모든 프로젝트에서 같은 이름일 리 없다. 다만 순서와 개수는 표준이 고정한다 — 원칙 1이 “계층 순서가 곧 의존”이라, 프로젝트가 순서를 바꾸면 그 원칙이 무너진다.

에이전트 개수는 이제 프로젝트가 정하지 않는다. 원칙 4가 세션 둘로 못 박았다.


원칙 18. 기능을 넘는 결정은 이슈가 아니라 decisions.md에 올린다

기획 대화에서 나온 결정은 어디에 남는가. 대부분은 이슈다 — 그 기능을 다시 만질 때 사람이 찾아가는 곳이 어차피 그 이슈고, 원칙 6가 본문에 「버린 안」 절까지 마련해뒀다.

문제는 여러 기능에 걸치는 결정이다. “이 프로젝트는 시간을 전부 UTC로 저장한다” 같은 것은 한 이슈에 묻히면 다음 기능에서 못 찾는다. 그리고 못 찾으면 다음 기획 대화에서 같은 것을 다시 정하고, 다르게 정한다.

안 A — 전부 이슈에 둔다. 얻는 것은 자리가 하나뿐이라 어긋날 곳이 없는 것이다. 버린 이유는 닫힌 이슈를 아무도 안 찾는 것이다. 결정이 살아 있는데 그것을 담은 이슈는 닫혀 있다.

안 B — project.yml에 같이 적는다. 버린 이유는 성격이 다른 것을 한 파일에 섞는 것이다. project.yml은 기계가 읽는 값이고 결정은 왜가 붙는 산문이다. 섞으면 설정을 읽으러 온 에이전트가 결정 전부를 끌고 온다.

골랐다 — 안 C, .coding-standards/decisions.md를 따로 둔다. 안 A가 못 지켜주는 것은 닫힌 이슈 밖에서 다시 찾을 수 있다는 것이고, 안 B가 못 지켜주는 것은 읽는 시점이 다른 둘을 나눠 두는 것이다.

  • 그 기능 안에서만 통하는 결정은 이슈에 둔다. 다음 기능에도 적용되는 것만 올린다.
  • 승격은 사람이 판단한다. 에이전트는 제안만 한다.
  • 결정 한 줄과 이유 한 줄로 적는다. 길어지면 표준 후보이므로 사람에게 알린다.
  • 기획을 시작할 때 이 파일을 먼저 읽는다.

대가는 자리가 둘로 늘어나 어긋날 수 있다는 것이다. 승격을 사람이 판단하게 한 것이 그걸 줄이는 장치다 — 자동으로 올라가면 이슈와 장부가 같은 것을 다르게 적기 시작한다.


판단 기준 정리

질문
이슈를 몇 개 만드나 기능당 하나 사람이 보는 단위와 일의 단위를 맞춘다
기능이 크면 쪼개나 웬만하면 안 쪼갠다 이슈가 늘면 개입이 는다. 원칙 1을 되돌리는 일이다
크다는 걸 무엇으로 아나 계약 개수 기획이 닫는 순간에 셀 수 있는 게 그것뿐이다
쪼갤 때 어디를 자르나 기능 축 계층으로 자르면 순서가 생기는데 적을 곳이 없다
기능 축으로 못 자르면 큰 채로 간다 아무도 모르는 순서가 큰 이슈 하나보다 나쁘다
의존을 어디에 적나 안 적는다 계층 순서가 곧 의존이라 어긋날 곳이 없다
세션을 몇 개 두나 판단 주체를 가르는 데 필요한 최소다
기획을 누가 만드나 사람과 함께 채운 빈칸과 정한 것을 구별해야 한다
본문에 무엇을 담나 절 다섯 개 빠진 것은 빈칸으로 보이지 않는다
계약을 어떻게 쓰나 한 줄 = 한 검증, 번호 못 짜겠다의 판정 기준이 서야 한다
본문을 고치나 추가만 한다 계약이 갈라지는 것과 이슈가 번식하는 것을 둘 다 막는다
계약 이름을 재사용하나 안 한다 과거 댓글의 참조가 거짓이 된다
댓글을 누가 읽나 사람만 독자가 둘이면 장황한 쪽으로 수렴한다
댓글 단위가 무엇인가 사건 확인 필요는 계층이 아니라 사건이다
계층을 어떻게 넘나 묻고 승인받는다 사람이 문맥 안에 있을 때 물어야 싸다
테스트가 무엇을 읽나 본문의 세 절 버린 안에 구현 의도가 섞일 수 있다
테스트가 어디서 따나 개발 브랜치 검증 안 된 코드를 통합 브랜치에 넣지 않는다
겹치는 파일을 어떻게 막나 안 막는다 목록이 놓일 자리가 세 곳 다 막혔다
막히면 닫나 안 닫는다 계약을 못 지킨 채로 끝난 것이 아니다
프로젝트 설정을 어디 두나 .coding-standards/ 관리 경로는 업데이트가 덮어쓴다
세션이 끊기면 다시 파악한다 복원용 기록이 장황함의 원인이었다

이 표준을 정하기까지

시작은 “이슈 글이 장황하다”였다. 에이전트가 남기는 기록이 중구난방이고 너무 길어서 사람이 읽기 힘들다는 것이었다. 문체 문제로 들어왔다.

첫 진단이 틀렸다. 나는 원인을 독자가 둘이라 장황해진다로 봤다. 진단 자체는 나중에 맞는 것으로 드러났지만 고칠 자리를 틀렸다 — 본문을 어떻게 쓰느냐의 문제로 봤다.

진짜 문제는 개수였다. 이슈가 다섯 개라 사람이 따라갈 수 없다는 것이었고, 세어보니 이슈 개수가 그대로 사람 개입 횟수였다. 기능 열 개면 개입 여든 번이다. 그리고 이건 표준이 스스로 지목한 병목이었다 — 개정 전 글에 “배정 대기가 쌓이면 에이전트 부족이 아니라 사람 부족”이라고 적혀 있는데, 결론이 “에이전트를 줄인다”로만 갔다. 공급을 줄였지 개입 횟수 자체는 안 건드렸다.

내가 낸 안이 기각됐다. 나는 에이전트 넷을 유지하고 이슈만 합치자고 했다. 버린 이유는 한 줄이었다 — 판단 주체가 넷이면 전환 지점도 넷이라, 이슈를 합쳐도 브레이크 개수는 그대로다.

나눠야 했던 것이 세션이라는 게 그 다음에 나왔다. 계층을 나눈 근거를 다시 읽으니 전부 「문맥이 섞이면 안 된다」였다. 이슈를 넷으로 쪼갠 건 그걸 트래커에 표현하려다 나온 부산물이었다.

여기서 방향이 완전히 꺾였다. 나는 두 독자를 화해시킬 방법을 찾고 있었는데 — 짧은 요약과 포인터를 두고 상세는 PR과 좌표가 담게 하자는 안까지 냈다 — 답은 하나를 다른 데로 보내는 것이었다. 에이전트용 정보는 세션이 들고, 댓글은 사람만 읽는다.

그 순간 티켓 좌표(CODEMAP) 설계가 통째로 죽었다. 기획과 개발이 한 세션이면 탐색이 처음부터 한 번뿐이라 넘길 좌표가 없다. 마커·필드 여섯 개·base SHA 검사·어댑터 둘이 다 필요 없어졌고, PM 에이전트도 같이 사라졌다.

장부에 열어둔 항목이 저절로 닫혔다. 테스트 세션 분리와 읽기 규칙이 서로를 무너뜨리는 자리였는데, 새 구조에서는 테스트가 구현 의도에 접근할 경로가 아예 없다.

여기까지가 1차 개정이고, 2차는 “그래서 그 하나에 무엇을 어떻게 쓰나”에서 시작했다. 1차는 자리를 정했지 문장을 정하지 않았다. 본문은 「계약만」, 댓글은 「세 줄에서 다섯 줄」까지만 적혀 있었고, 그 둘 사이에 규칙이 서로를 부정하는 자리가 남아 있었다.

세어보니 논리가 깨지는 자리가 여섯이었다. 테스트가 검증 대상 브랜치를 알 방법이 없었고(PR 주소가 댓글에만 있는데 테스트는 댓글을 못 읽는다), 첫 댓글이 「버린 안 전부」와 「세 줄」을 동시에 요구했고, touch 목록이 본문에도 댓글에도 못 들어갔고, 머지 충돌 규칙이 “양쪽 이슈를 읽는다”로 댓글 규칙을 깼고, 개입 계산에서 PR 머지가 빠져 있었고, 확인 필요가 파일을 주말 내내 잠갔다. 전부 1차가 자리만 정하고 서식을 안 정해서 생긴 것들이다.

계약에 번호를 붙인 것이 그 여섯 중 셋을 한 번에 풀었다. 댓글이 짧아야 하는데 짧으면 무엇을 가리키는지 흐려진다는 것이 근본 문제였는데, 번호가 생기자 세 줄 안에서 지목이 가능해졌다. “C1~C3 검증, C4 미검증”이 세 줄이면서 손실이 없다. 1차에서 3~5줄이 안 지켜졌던 이유가 분량이 빡빡해서가 아니라 좌표가 없어서였다.

버린 안을 본문으로 옮기면서 1차 원칙 하나를 스스로 깼다. “본문에는 계약만 담는다”였는데, 그러면 버린 안이 갈 곳이 댓글뿐이고 거기는 3~5줄이다. decisions.md로 보내는 안은 그 파일의 기준을 무너뜨리고, 기획 댓글만 분량 예외로 두는 안은 「댓글은 짧다」를 첫 댓글부터 깬다. 본문의 정의를 「계약」에서 「기획의 산출물 전부」로 넓히는 쪽을 골랐다.

그 대가로 테스트의 격리가 샜다. 버린 안에는 구현 의도가 섞일 수 있는데 테스트는 본문을 읽는다. 읽는 절을 지정하고, 규칙만으로는 안 지켜지므로 절 제목 자체에 「테스트 세션은 읽지 않는다」를 박았다.

본문 불변이 계약 추가와 부딪히는 자리도 여기서 나왔다. 막히는 이유가 대부분 “계약이 부족하다”인데 사람이 답하면 그 답이 곧 계약이다. 댓글에 두면 테스트가 못 보고, 이슈를 새로 파면 「기능 하나에 이슈 하나」가 무너진다. 추가만 허용하기로 하면서 불변의 뜻이 「글자가 안 변한다」에서 「이미 쓴 계약의 뜻이 안 바뀐다」로 정확해졌다. 번호 재사용 금지가 그때 딸려 나왔다 — 주소를 재사용하면 과거 댓글이 전부 거짓이 된다.

touch는 자리가 없어서 지웠다. 본문에 두면 원칙 6가 금지한 구현이 들어오고 테스트에 새며, 댓글에 두면 겹침 검사를 하려고 에이전트가 댓글을 읽어야 한다. 그런데 세 번째가 결정적이었다 — 기획은 코드를 안 읽는데 만질 파일 목록을 요구하고 있었다. 애초에 없는 값을 산출물로 받으려던 것이다. 비대칭의 근거는 지금도 맞지만 적용할 데이터가 없다는 것이 드러나서 규칙을 통째로 지웠고, 주말 파일 잠금도 같이 사라졌다.

테스트가 설 자리를 정했다. 사람이 세션을 열 때 브랜치를 주기로 했는데, 그러려면 사람이 그 브랜치를 어디서 읽는지가 정해져야 했다[개발 완료] 댓글이고, 그래서 PR과 브랜치가 필수 항목이 됐다. 에이전트끼리는 못 넘기지만 사람을 거치면 넘어간다는 것이 이 구조의 성격을 그대로 보여준다. 개발 PR을 먼저 머지하는 안은 검증 안 된 코드를 통합 브랜치에 먼저 넣게 되어 버렸다.

마지막에 바닥이 비어 있는 걸 발견했다. 원칙 1이 「기능 하나에 이슈 하나」로 못 박아놓고 기능의 크기를 아무 데서도 정하지 않았다. 그러면 계약 서른 개짜리 이슈가 와도 막을 것이 없고, 방금 정한 것들이 순서대로 깨진다 — 댓글 세 줄이 서른 개의 결과를 못 담고, PR이 며칠 열려 있고, 세션 하나가 감당하지 못한다. 「이슈 개수가 곧 개입 횟수」를 풀었더니 그 반대편이 열려 있었다.

방향을 먼저 못 박았다 — 기본값은 안 쪼개는 것이다. 원칙 1이 이슈를 다섯에서 하나로 줄인 이유가 개입 횟수였으므로 쪼개는 것은 그 결정을 되돌리는 일이다. 그래서 규칙이 「쪼개라」가 아니라 「너무 길 때만」이 됐다.

그런데 쪼개는 순간 원칙 1이 깨진다는 게 곧바로 나왔다. 「계층 순서가 곧 의존이라 적을 곳이 없다」는 한 이슈 안에서만 성립하는 말이고, 이슈 A와 B 사이에는 그런 고정 순서가 없다. 계층으로 자르면 순서가 생기는데 적을 곳이 없어서 사람이 뒤엣것을 먼저 배정해도 아무도 안 막는다. 그래서 자르는 축을 기능으로 못 박고, 그 축으로 못 자르면 큰 채로 가기로 했다 — 아무도 모르는 순서가 큰 이슈 하나보다 나쁘다.

판정 검문은 새로 만들지 않아도 됐다. 원칙 12가 이미 「테스트 세션은 자기 이슈의 계약만 읽는다」로 정해놨으므로, 테스트가 자기 계약만으로 도느냐가 그대로 독립 여부의 판정이 된다. 후보였던 「각각 배포할 수 있는가」는 배포의 뜻이 프로젝트마다 달라 버렸다.

셀 수 있는 것이 계약 개수뿐이라는 것도 재보고 알았다. 세션 소진량도 diff 크기도 기획이 닫는 시점에는 모른다 — touch를 지운 이유(기획은 코드를 안 읽는다)가 여기서 한 번 더 걸렸다. 계약 개수가 대리 지표로 쓸 만한 것은 원칙 7이 「한 줄 = 한 검증 항목」으로 단위를 이미 정규화해놨기 때문이고, 그게 없었으면 못 쓸 지표였다.

쪼갠 상대를 적을 자리도 새로 안 만들었다. 「범위 밖」이 원래 하던 일이라 그대로 쓰고, 이슈 번호는 안 적기로 했다 — 번호를 적는 순간 그건 이슈 간 링크고 원칙 1이 피한 자리다. 기준 숫자는 근거가 없다는 것을 표준에 적었다. 한 번도 안 돌려봤으므로 지어낸 값이고, 그 사실을 안 적으면 6개월 뒤에 잰 숫자로 오해한다.


정리

  • 기능 하나에 이슈 하나. 계층은 이슈를 나누는 단위가 아니라 진행 단계다
  • 기능이 커도 기본은 안 쪼갠다. 쪼개는 것은 이슈를 합친 결정을 되돌리는 일이다
  • 쪼갤 때는 기능 축으로만. 테스트가 자기 계약만으로 도는지가 검문이다
  • 나눠야 했던 것은 세션이지 이슈가 아니었다. 이슈 넷은 에이전트 넷의 부산물이었다
  • 에이전트가 혼자 기획을 만들지 않는다. 채운 빈칸과 정한 것이 구별되어야 한다
  • 본문은 기획의 산출물 전부를 다섯 절로 담는다. 빠진 것은 빈칸으로 보이지 않는다
  • 계약은 한 줄이 검증 하나이고 번호를 갖는다. 번호가 짧은 댓글을 가능하게 했다
  • 본문은 추가만 된다. 불변의 뜻은 글자가 아니라 이미 쓴 계약의 뜻이다
  • 댓글은 사람만 읽고 사건마다 쌓인다. 독자를 하나로 잘라야 짧게 쓸 근거가 생긴다
  • 계층 전환은 배정이 아니라 승인이다. 다만 세션이 끊기는 자리는 여전히 배정이다
  • 테스트는 본문의 세 절만 읽는다. 버린 안은 절 제목으로 막는다
  • touch는 지웠다. 기획이 못 만드는 값을 기획 산출물로 요구하고 있었다
  • 세션이 끊기면 다시 파악한다. 복원용 기록을 남기는 비용이 장황함의 원인이었다
  • 프로젝트 고유의 것은 .coding-standards/에 둔다. 관리 경로는 업데이트가 덮어쓴다

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기