14 KiB
7747d745 — 우선순위 평가 및 실행 로드맵 Rev.2
Job: 7747d745 · Role: Planner · Supersedes: ecef05a3 (Rev.1)
응답 대상: 챌린지 7d604ee7 (agy, [CHALLENGE: RAISED]) — B-7 해법 미비 · 트랙 병렬 경합
Base: 245abe6 + 작업 트리
1. 판정 요약
두 건 모두 채택한다. 다만 두 건 다 지적 내용 그대로는 성립하지 않는다.
| # | 챌린지 | 판정 | 실측 |
|---|---|---|---|
| C-1 | B-7 해법이 cd $REPO_ROOT 뿐이라 미추적 파일을 못 잡는다 |
채택 — 단 전제 오류 | Rev.1 은 B-7 해법을 아예 명시하지 않았다. 인용된 cd "$REPO_ROOT" && git diff 는 내 문서에 없는 문장이다. 그러나 "진단만 하고 처방을 안 썼다"는 것 자체가 결함이고, 제안된 git add -N . 은 동작한다(실측) |
| C-2 | A-2 와 O-2/B-8 이 reconcile.sh 에서 충돌한다 |
채택 — 지목한 쌍은 존재하지 않음, 그러나 내 트랙 분해가 더 틀렸다 | reconcile.sh 의 MAM_LOOP_MARKER 참조 0건, send_keys_safe 참조 0건 → O-2·B-8 은 reconcile.sh 를 건드리지 않는다. 반면 파일 단위 매트릭스를 만들어 보니 내 §4.3 트랙 분해가 4곳에서 틀렸다 |
정정부터. Rev.1 §4.3 은 "트랙 C(정리)는 트랙 A/B 와 독립"이라고 썼다. 틀렸다.
B-6 은 run_loop.sh 를 고치므로 B-7·O-2 와 같은 파일이고, C-3a·C-4 는 lib.sh 를
고치므로 B-8·A-4 와 같은 파일이다. agy 는 엉뚱한 쌍을 지목했지만 "파일 단위 대조 없이
독립을 선언했다"는 지적의 실질은 옳고, 실제 피해는 그들이 본 곳보다 넓다.
git add -N . 은 채택하되 그대로는 쓰지 않는다. 인덱스를 오염시켜
이후 git commit -a 가 작성자가 추가한 적 없는 파일을 조용히 커밋한다(실측 §2.3).
Creator 에이전트가 같은 저장소에서 동시에 git 을 쓰는 구조라 이건 이론이 아니다.
인덱스를 건드리지 않는 동등 대안을 권한다(§2.4).
2. C-1 — B-7 해법: 채택, 기전 교체
2.1 전제 정정
챌린지는 "Plan §4 의 해법(cd $REPO_ROOT && git diff)"을 인용한다.
Rev.1 §4 표의 B-7 칸 전문은 다음과 같다:
리뷰어가 빈 diff 로 PASS. 신규 파일은 리뷰 대상 밖. 나머지 11건의 검증 근거를 훼손(§3.2)
근거만 있고 해법은 없다. cd "$REPO_ROOT" 는 내가 쓴 적 없는 문장이다.
그러나 이건 방어가 아니라 자기 결함의 확인이다 — P1-1 로 올려 놓고 처방을 안 썼다.
구현자가 §3.2 의 두 원인 중 눈에 띄는 쪽(cwd)만 고치고 끝냈을 가능성이 크고,
챌린지는 정확히 그 시나리오를 예측했다. 처방을 명시하는 것으로 갚는다.
2.2 git add -N . 은 동작한다 (실측)
빈 저장소에 tracked 수정 1건 · untracked 신규 2건 · .gitignore 대상 1건을 심고 측정했다.
[before] git diff $BASE --stat
tracked.txt | 1 + ← 신규 파일 0건
[after] git add -N . ; git diff $BASE --stat
pkg/__init__.py | 1 + ← 잡힘
sub/newfile.py | 1 + ← 잡힘
tracked.txt | 1 +
ignored.log hunks: 0 ← .gitignore 존중됨
제안의 두 가지 핵심 주장이 모두 참이다: 미추적 신규 파일이 diff 에 포함되고,
.gitignore 는 그대로 존중된다.
2.3 그러나 인덱스가 오염된다 — 그리고 그게 커밋으로 샌다
git add -N . 직후 인덱스 상태:
A pkg/__init__.py
A sub/newfile.py
M tracked.txt
이 상태에서 Creator 가 git commit -am "wip" 을 실행하면:
$ git commit -qam "creator wip" ; git show --stat HEAD
pkg/__init__.py | 1 +
sub/newfile.py | 1 +
tracked.txt | 1 +
-> sub/newfile.py in that commit? brand new ← 내용까지 들어갔다
작성자가 git add 한 적 없는 파일이 -a 한 번에 커밋된다. 평소 git commit -a 는
미추적 파일을 건드리지 않으므로, 이건 git 의 기본 안전 성질을 바꾸는 부작용이다.
MAM 에서 이게 가설이 아닌 이유: run_loop.sh 는 Creator 에이전트가 같은 저장소에서
동시에 작업하는 동안 돌아간다. 루프가 인덱스를 바꾸는 시점과 Creator 가 git 을 쓰는
시점이 겹친다. 게다가 루프는 반복 실행되므로 오염이 매 사이클 재발한다.
git reset 으로 되돌리는 보정을 붙일 수도 있지만, (a) 비정상 종료 시 남고
(b) 되돌리는 순간과 Creator 의 git 호출이 또 경합한다. 부작용을 만들고 지우는 대신
애초에 만들지 않는 편이 낫다.
2.4 권고: 인덱스를 건드리지 않는 동등 대안
CHANGES_DIFF=$(
cd "$REPO_ROOT" || exit 1
git diff "$BASE_COMMIT"
# 미추적 신규 파일: 인덱스를 바꾸지 않고 /dev/null 대비 diff 로 덧붙인다.
# --exclude-standard 가 .gitignore/.git/info/exclude 를 그대로 존중한다.
git ls-files -o --exclude-standard -z | while IFS= read -r -d '' f; do
git diff --no-index --binary /dev/null "$f" 2>/dev/null || true
done
)
같은 픽스처 실측:
diff --git a/tracked.txt b/tracked.txt ← 기존 파일 수정
diff --git a/sub/newfile.py b/sub/newfile.py ← 신규 파일
--- /dev/null
+++ b/sub/newfile.py
ignored.log present? 0 ← .gitignore 존중
[index] M tracked.txt / ?? sub/ ← 인덱스 무변경
동일한 결과를 내면서 인덱스를 건드리지 않는다.
git diff --no-index는 두 경로가 모두 저장소 밖일 때 rc=1 을 반환하지만, 여기서는 차이가 있을 때 rc=1 이 정상이므로|| true로 흡수한다.-z+IFS= read -r -d ''는 공백·개행이 든 파일명을 위한 것이다.
2.5 함께 고쳐야 할 것 — cd 와 크기 상한
(a) cd "$REPO_ROOT" 는 여전히 필요하다. §3.2 의 두 원인 중 하나이고
서브셸 안에서 처리하면 호출자 cwd 를 오염시키지 않는다(위 코드에 반영).
(b) 크기 상한이 없다. 실측: run_loop.sh:537,539 에서 만든 CHANGES_DIFF 가
아무 제한 없이 547행의 리뷰 프롬프트 문자열에 그대로 보간되고,
그 프롬프트는 send_keys_safe 를 통해 TUI paste-buffer 로 주입된다.
미추적 파일을 포함시키면 diff 는 커지기만 한다. 누군가 큰 산출물을 ignore 하지 않은 채
남겨 두면 리뷰 주입이 통째로 실패하거나 잘린다.
권고: 상한(예: 200 KB / 4000 줄)을 두고 초과 시 --stat 요약 + 초과 사실 명시로 대체.
잘렸다는 사실이 리뷰어에게 반드시 보여야 한다 — 조용히 잘리면 B-7 을
"빈 diff 로 PASS" 에서 "부분 diff 로 PASS" 로 바꾸는 것에 지나지 않는다.
3. C-2 — 트랙 경합: 지목한 쌍은 없고, 내 분해가 더 틀렸다
3.1 지목된 두 쌍은 성립하지 않는다
reconcile.sh 내 MAM_LOOP_MARKER / loop-guard-active 참조 → 0건
reconcile.sh 내 send_keys_safe / inject_instructions 참조 → 0건
- O-2: 마커는
run_loop.sh:83-89에만 있다.reconcile.sh는 자기 자신의 별도 락 (.mam/monitor.lock,fcntl.flock, 86-95행)을 이미 갖고 있다 — 다른 프로세스를 위한 다른 뮤텍스다. O-2 의 처방은run_loop.sh안에서 끝난다. - B-8:
send_keys_safe는lib.sh함수이고reconcile.sh는 이를 호출하지 않는다.
따라서 "A-2 ⟂ O-2/B-8 이 reconcile.sh 에서 충돌"은 실재하지 않는다.
3.2 그러나 Rev.1 §4.3 은 실제로 틀렸다
챌린지가 제기한 방법론적 문제 — 파일 단위 대조 없이 독립을 선언했다 — 는 옳다. 각 항목의 처방이 건드리는 파일을 근거에서 도출해 매트릭스를 만들었다.
| 파일 | 건드리는 항목 |
|---|---|
run_loop.sh |
B-6, B-7, O-2 |
lib.sh |
A-4, B-8, B-10, C-3a, C-4 |
reconcile.sh |
A-2, A-4, B-10 |
mqtt_common.py |
A-2, B-9 |
stop_session.sh |
B-10, C-6 |
registry.py |
A-2, C-4 |
create_session.sh |
A-4, C-4 |
Rev.1 §4.3 의 오류 4건:
B-6을 트랙 C(독립)에 뒀다.run_loop.sh이므로 B-7·O-2 와 같은 파일이다.C-3a·C-4를 트랙 C(독립)에 뒀다.lib.sh이므로 B-8·A-4 와 같은 파일이다.B-9를 P5 독립으로 뒀다.mqtt_common.py이므로 A-2 와 같은 파일이다.C-6을 독립으로 뒀다.stop_session.sh이므로 B-10 과 같은 파일이다.
agy 가 지목한 A-2↔O-2 는 없지만 A-2↔A-4, A-2↔B-10, A-2↔B-9, A-2↔C-4 는 있다.
lib.sh 는 5개 항목이 몰리는 최대 경합 지점이다.
3.3 결론: "트랙"이 아니라 "파일 소유권"으로 직렬화한다
트랙 개념 자체가 잘못된 추상화였다. 병렬 단위를 주제가 아니라 파일로 잡는다.
| 파일 소유 슬롯 | 순서 | 동시 실행 가능 |
|---|---|---|
run_loop.sh |
B-7 → O-2 → B-6 | 다른 슬롯과 병렬 |
lib.sh |
C-3a+C-4 → B-8 → (A-4 M0~) | 다른 슬롯과 병렬 |
MQTT 계열(mqtt_common.py·registry.py·publish_event.py·job_subscriber.py·reconcile.sh) |
A-2 → B-9 | 다른 슬롯과 병렬 |
stop_session.sh |
C-6 → (B-10) | 다른 슬롯과 병렬 |
- 한 슬롯 안은 직렬, 슬롯 간은 병렬. 슬롯을 넘는 항목(A-4, B-10)은
단독 실행한다 — A-4 는
lib.sh+reconcile.sh+create_session.sh, B-10 은lib.sh+reconcile.sh+stop_session.sh이므로 어떤 슬롯 조합과도 겹친다. reconcile.sh를 MQTT 슬롯에 넣은 이유: A-2 가 그 파일에서 가장 큰 변경을 하고, 나머지 두 소비자(A-4·B-10)는 어차피 단독 실행이다.
우선순위 표(Rev.1 §4)의 순위 자체는 바뀌지 않는다. 바뀌는 것은 병렬화 방식뿐이다.
4. 변경 요약 (Rev.1 대비)
| ID | 대상 | 내용 |
|---|---|---|
| R-1 | B-7 처방 (신규) | cd "$REPO_ROOT" + git ls-files -o --exclude-standard 기반 미추적 파일 덧붙이기. git add -N 은 채택하지 않음(인덱스 오염, §2.3) |
| R-2 | B-7 처방 (신규) | CHANGES_DIFF 크기 상한 + 잘림 사실 명시 |
| R-3 | §4.3 교체 | "트랙" → 파일 소유권 슬롯. Rev.1 의 독립 선언 4건 정정 |
| R-4 | A-4 · B-10 | 슬롯 경계를 넘으므로 단독 실행 명시 |
우선순위(P0-1 ~ P5, 종결 권고 B-5)와 §3 실측 결과는 전부 그대로 유효하다.
5. 검증
전부 임시 저장소(scratchpad/b7, b7b)에서 실측했다. 프로덕션 저장소의 인덱스는
건드리지 않았다 — 인덱스 오염이 바로 이 논점이므로 실 저장소에서 재현하는 것은 부적절하다.
| 검증 | 결과 |
|---|---|
git add -N . 이 미추적 파일을 diff 에 포함시키는가 |
✅ 포함 (2/2 신규 파일) |
.gitignore 존중 |
✅ ignored.log 0 hunks |
| 인덱스 잔존 여부 | ❌ A pkg/__init__.py, A sub/newfile.py 잔존 |
잔존 상태에서 git commit -a |
❌ 추가한 적 없는 파일이 내용째 커밋됨 |
대안(ls-files -o + --no-index) 포함 여부 |
✅ 포함 |
대안의 .gitignore 존중 |
✅ 0 hunks |
| 대안의 인덱스 영향 | ✅ 무변경 (?? sub/ 유지) |
reconcile.sh 의 O-2 심볼 참조 |
0건 → C-2 전제 반증 |
reconcile.sh 의 B-8 심볼 참조 |
0건 → C-2 전제 반증 |
CHANGES_DIFF 크기 상한 |
없음 (537·539 → 547 무제한 보간) |
6. 남는 불확실성
Rev.1 §6 의 4건(A-2 노출도 · O-2 경합 창 · B-9 호출자 전수 · A-4 상한)은 그대로 유효하다. 추가분:
6.5 크기 상한값은 근거 없이 제시했다. §2.5 의 "200 KB / 4000 줄"은 관례적 수치이지
측정값이 아니다. send_keys_safe 의 paste-buffer 가 실제로 어느 크기에서 실패하는지는
측정하지 않았다 — 실 세션에 대용량 주입을 시도하는 실험이라 Planner 범위에서 부적절하다.
B-7 구현자가 샌드박스 세션에서 상한을 측정해 확정할 것.
6.6 파일 매트릭스는 처방 기준의 추정이다. 각 항목이 실제로 어느 파일을 건드릴지는 구현 단계에서 늘어날 수 있다(특히 테스트 파일). 슬롯 배치는 구현 착수 시 재확인해야 한다.
6.7 git ls-files -o 는 서브모듈·심링크를 이 저장소에서 검증하지 않았다.
MAM 저장소에는 서브모듈이 없어 실측 대상이 아니었다. 다른 워크스페이스에 배포될 때를
고려하면 구현자가 한 번 확인하는 편이 좋다.
7. 결론
두 챌린지 모두 전제는 틀렸고 결론은 맞다.
C-1 이 인용한 cd $REPO_ROOT && git diff 는 내 문서에 없다 — 나는 B-7 의 처방을
아예 쓰지 않았다. 그게 더 나쁘다. 제안된 git add -N . 은 실제로 동작하지만
인덱스를 오염시켜 git commit -a 가 추가한 적 없는 파일을 커밋하게 만든다.
동등하면서 부작용 없는 형태로 교체해 채택한다.
C-2 가 지목한 A-2↔O-2/B-8 충돌은 reconcile.sh 참조 0건으로 존재하지 않는다.
그러나 파일 매트릭스를 만들어 보니 내 트랙 분해가 4곳에서 틀렸고,
lib.sh 에는 5개 항목이 몰려 있었다. 트랙이라는 추상화를 버리고 파일 소유권 슬롯으로 바꾼다.
우선순위 순서 자체는 Rev.1 그대로다. 바뀐 것은 B-7 의 처방과 병렬화 방식 두 가지다.
[AGREEMENT: REACHED]