Skip to content

v0.23.0

Choose a tag to compare

@sol5288 sol5288 released this 15 Aug 06:30
· 713 commits to main since this release

전체 변경 이력: CHANGELOG.md

자율 진행에 범위가 생겼고, 중단된 작업에서 빠져나올 길이 생겼습니다.
stopGate: "auto"사전 위임(req:delegate) 범위 안의 검증된 변경만 통합합니다 — 무제한
자동이 아니고, 위임은 정확히 한 번 소비되며, hardCap 도달·HIGH 미위임·BLOCKED 리뷰·증거
불일치는 위임이 있어도 막습니다. 리뷰 예산이 길어질 때의 처리는 별도 축
(reviewBudget.onSoftLimit)으로 분리했고, 티켓은 만들어질 때의 정책으로 끝까지 갑니다
(정책 스냅샷 · 두 축 함께 동결).

나머지 절반은 교착 해소입니다. 커밋이 증거 기록 도중 죽었을 때·리뷰가 중간에 죽었을 때·
완료된 티켓에 phase 를 더 커밋했을 때·hardCap 에 닿았을 때, 지금까지는 안내가 나와도
그 명령이 실제로 실행되지 않는 경우가 있었습니다. 이번 판은 그 안내들을 실행 가능하게
고치고, 동시에 안내가 통제점을 넘어 대신 결정하지 않도록 반대편도 닫았습니다.

🔴 복구 창은 둘입니다 — 증거를 아직 소비하지 않은 구간(state 에 보임)과 증거는 커밋했지만
checkpoint 를 아직 안 찍은 구간(state 에 흔적이 없고 HEAD↔워킹트리 관계로만 드러남).
두 구간 모두에서 결속을 깨는 쓰기를 막습니다.

업그레이드(caret 는 minor 를 자동으로 넘지 않습니다):

npm i -D commitgate@0.23.0
npx commitgate sync          # 배포 템플릿 자산 동기화
npx commitgate doctor        # D32 로 정책 스냅샷 드리프트 확인

stopGate: "auto"reviewBudget.onSoftLimit명시 opt-in 입니다 — 기존 설정은
stopGate: "req" · onSoftLimit: "ask" 그대로 유지됩니다. 바꾸려면 npx commitgate setup
(사람이 직접 실행하는 대화형 명령입니다).

  • docs: README 첫 소개·워크플로·설치 템플릿에 옛 리뷰 예산 표현이 남아 있던 문제 — 직전 변경이
    빠른 시작·보장·설정 상세절을 고쳤지만, 가드의 적용 범위가 결함 범위보다 좁아 다음이 남았습니다:

    • README(한/영) 첫 소개 — "자동 5회 · 6~8회 사람 예외 · 9회부터 차단"

    • docs/workflow.md(한/영) 재리뷰 예산 절 — "(기본 6~8)"

    • docs/configuration.md(한/영) 요약 표 — 같은 파일의 상세절과 모순되고 있었습니다

    • AGENTS.template.md — setup 이 "리뷰 모델·추론강도"만 묻는다고 설명(🔴 설치 프로젝트로
      복사되는 에이전트 계약
      이라 모든 소비자가 틀린 계약을 읽게 됩니다)

    • README(한/영) 명령 표 — setup 설명에서 추론강도·리뷰 예산 정책 누락

    • 🔴 본체는 서술이 아니라 검사 대상입니다: 회귀 가드를 공개 문서 전체(README 한/영 ·
      quick-start 한/영 · workflow 한/영 · configuration 한/영 · guarantees 한/영 ·
      AGENTS.template.md)로 넓혔습니다. 넓히자마자 위 세 곳이 바로 red 가 됐습니다.

    • 🔴 9회차(=9번째 호출)는 금지하지 않습니다hardCap 은 호출 수를 세므로 정확한 서술입니다.
      오해를 만드는 것은 소프트 한도를 회차 번호로 말하는 쪽입니다.

    • 🔴 펜스 코드 블록은 검사하지 않습니다 — 그 안은 도구가 실제로 내는 출력의 축자 인용이고,
      고치면 문서가 도구와 달라집니다.

    • AGENTS.template.md 는 "에이전트가 스스로 실행하면 안 되는 이유"를 두 정책 축의 이름으로
      다시 씁니다 — setup 은 어디서 사람이 확인하는지(stopGate)와 예산을 넘겼을 때 사람 승인
      없이 계속할지
      (reviewBudget.onSoftLimit)를 정하고, 에이전트가 그것을 스스로 고르면 자기가
      통과할 게이트를 자기가 고르는
      경로가 열립니다.

    • auto 가 두 곳에 있다는 것을 빠른 시작에 표로 한 번 명시했습니다(stopGate: "auto"
      사전 위임이 있을 때 통합 자동 진행 · onSoftLimit: "auto" 는 예산 초과 뒤 사람 예외 승인 생략).
      🔴 둘 다 hardCap 을 해제하지 않습니다.

    • 🔴 코드 판별은 손으로 만들지 않고 remark(CommonMark 파서)에 위임합니다. r01·r02 에서
      손수 만든 펜스 판별기가 연달아 틀렸습니다 — 물결 펜스 미인식(r01) → 4칸 들여쓰기를 펜스로
      오인·block quote 안의 펜스 미인식·닫힘 길이/후행 텍스트 오인(r02). 손수 검증 oracle 은
      바닥이 없다
      는 REQ-2026-041→042 의 교훈을 그대로 반복한 것이라, 이미 docs:lint 가 쓰는
      파서로 넘겼습니다. 인라인 코드도 도구의 축자 문자열이므로 함께 제외합니다.

    • 판정 함수는 tests/helpers/md-prose.ts분리했습니다 — 테스트 지역 함수로 두면 회귀
      테스트가 그 함수를 부르지 못해 규칙을 다시 적는 공허한 오라클이 됩니다(r01 P1).

    확인할 파일: tests/unit/setup-docs-parity.test.ts(가드 범위) · tests/helpers/md-prose.ts · AGENTS.template.md

  • docs: setup 설명이 실제 동작과 어긋나던 문제setup 질문이 3개에서 4개로 늘고
    (reviewBudget.onSoftLimit 추가) stopGateauto 가 생겼는데, 설치·빠른 시작·보장·에이전트
    문서가 옛 전제를 유지
    하고 있었습니다. 핵심 정책 문서(README 정지 지점 절 · docs/configuration.md)만
    갱신됐던 것입니다.

    • README(한/영) — "질문은 세 개" → 네 개, 3단계 설명에 리뷰 예산 추가.
    • 빠른 시작(한/영) — 질문 표 4행, stopGateauto, onSoftLimit 행 신설.
    • 보장(한/영) — 재리뷰 예산을 설정 종속으로 다시 씀. 보장 문서에 조건부 동작을 절대 규칙처럼
      적으면 그 문서 전체의 신뢰가 떨어집니다.
    • 에이전트 프롬프트(한/영) — setup 이 고르는 항목에 리뷰 예산 정책 추가.
    • 🔴 두 축을 구분해 설명합니다: stopGate안전(사람이 승인할 자리), onSoftLimit
      비용(예산 초과 시 계속할지). 둘 다 "멈춘다"는 말을 써서 헷갈리던 지점입니다.
    • 🔴 auto 가 무엇을 없애는지 감추지 않습니다: stopGate 의 사람 확인은 그대로지만,
      예산 초과 뒤 요구되던 "사람 예외 승인"은 없어집니다. "사람 확인은 그대로"라고만 쓰면
      사용자가 잃는 확인 하나를 모르고 고르게 됩니다.
    • 🔴 회차 번호로 단정하지 않습니다("6~8회는 사람 예외 필요"). 두 한도는 세는 기준이 다릅니다 —
      autoBudget판정이 나온 리뷰, hardCap실제로 나간 호출입니다. 무효 응답이 하나
      있으면 6번째 호출도 자동으로 통과합니다.
    • 상세 표는 빠른 시작 한 곳에만 두고 나머지는 링크합니다 — 같은 표를 복제해 둔 것이 이번
      어긋남의 원인이었습니다.
    • 회귀 가드를 두었습니다: 질문 개수와 선택지를 문서끼리가 아니라 소스(buildQuestions ·
      CONFIG_SCHEMA)와
      대조합니다.

    확인할 파일: docs/quick-start.md(정본) · tests/unit/setup-docs-parity.test.ts

  • fix: 복구 창 가드가 두 구간 중 하나만 막던 문제 — 바로 아래 항목의 가드는
    pending_evidence_for 만 보았는데, 소비가 그 키를 제거합니다. 그래서 evidence 커밋은 끝났고
    소비 state 만 워킹트리에 남은 구간
    에서는 네 verb 가 전부 통과했습니다.

    • 그 구간에서 state 를 바꿔 checkpoint 커밋하면 결속과 다른 state 가 HEAD 에 영구 기록되고,
      워크트리가 깨끗해져 이후 복구가 대조할 기회조차 없습니다.
    • 이제 가드가 두 구간을 봅니다: ① pending_evidence_for(state 안) ② evidence 커밋 뒤·
      checkpoint 전(HEAD 와 워킹트리의 관계 — 소비 행을 HEAD 가 방금 추가했고 HEAD state 에는
      아직 없다). ②의 표식은 state 안에 없어 순수 술어로는 판정할 수 없습니다.
    • 🔴 루트 커밋(부모 없음)도 ②입니다. "부모 부재"와 "HEAD 를 못 읽음"은 다릅니다 — 후자만
      창이 아닙니다.
    • 🔴 결속값이 손상된 행도 ②로 막습니다. "정상 결속인 행만" 보면 손상된 행이 필터에서 빠져
      가드가 통과했고, 그 뒤 --finalizestate-mismatch 로 거부하는 영구 교착이 남았습니다.
      이 경우 안내가 "증거 결속이 손상됐다"고 다르게 말합니다 — 복구하면 된다고 오해하지 않도록.
    • 🔴 design 승인 행은 ②가 아닙니다. design 승인은 매니페스트에 소비 행을 남기지만
      consumed_approvals 에는 기록하지 않아(소비는 req:commit 이 합니다), 좁히지 않으면
      모든 티켓이 영구 차단됐습니다. 결속을 주장하는 행만 대상입니다.
    • 🔴 부수 효과: 이제 ③ checkpoint 커밋이 실패하면(그 실패는 조용히 삼켜집니다) 다음 리뷰·
      확인·정책 채택이 막힙니다. 의도한 동작이며 탈출구는 --finalize 하나입니다.
    • 🔴 검토의 하위 주장 하나는 반증했습니다: "dry-run 의 preview 파일이 foreign-files 판정을
      막는다"는 성립하지 않습니다. .review-preview.txt 는 gitignored 이고 porcelain 인자에
      --ignored 가 없어 dirty 목록에 들어가지 않습니다. 그래서 dry-run 을 막지 않았습니다
      고치지 않은 이유를 회귀 테스트로 고정했습니다.
  • fix: phase 게이트 parity 테스트가 실제 호출부와 다르던 문제 — 테스트가 비교 기준에
    \/ 정규화와 빈 조각 필터를 적용했는데 실제 호출부는 둘 다 하지 않습니다. 정규화가
    재도입돼도 일반 경로에서는 통과했습니다. 이제 호출부와 같은 값으로 비교하고, 실재하는 차이
    (빈 조각)는 그대로 적습니다.

  • fix: 복구 창에서 req:confirm·req:review-exception·req:review-codex 도 결속을 깨뜨리던 문제
    직전 릴리스는 req:repolicy 하나만 막았습니다("관측된 것만 막는다"). 같은 창에서 state 를
    바꾸는 verb 가 더 있었고, 각각 같은 영구 교착을 만들었습니다.

    • 이제 네 verb 모두 --run 하는 모든 모드에서 거부합니다 — review-codex 는 kind 와 무관하게,
      req:review-exception--close-stale·--resolve 를 포함해서입니다.
    • 🔴 거부는 첫 write 앞입니다. review-codex 는 늦게 막으면 원장·state 를 이미 쓰고
      유료 리뷰 호출까지 끝낸 뒤라 "아무것도 쓰지 않았습니다"가 거짓말이 됩니다.
    • 🔴 req:repolicy 는 정책이 이미 같은 경우(noop)에도 거부합니다 — 조기 반환이 가드를 앞지르면
      복구 안내를 받지 못한 채 성공으로 끝납니다.
    • dry-run 은 막지 않습니다. 승인만 받아 둔 상태(소비 전)도 정상이므로 막지 않습니다.
    • 재발을 구조로 고정했습니다: commitStateCheckpoint 호출부는 복구 자신임을 밝히는 마커가
      있거나 앞선 가드가 있어야 하고, 가드는 진입 함수의 첫 write 보다 앞이어야 합니다.
  • fix: git 이 준 경로를 도구가 바꿔 판정이 갈리던 문제 — 세 곳에서 \/ 로 정규화하고
    있었습니다. POSIX 에서 역슬래시는 파일명의 일부입니다.

    • 복구 계획과 D10 이 갈렸습니다: 계획은 정규화해 ready, D10 은 raw 로 차단 — 도구가 복구
      가능하다고 안내한 명령이 실행 불가였습니다. 이제 양쪽이 raw 경로로 같은 판정을 냅니다.
    • phase 면적 게이트가 우회됐습니다: 티켓 밖의 workflow\REQ-…/large.ts 가 내부처럼 보여
      max_files 계산에서 빠졌습니다. 이제 코드 파일로 셉니다.
    • 정규화는 도구가 만든 ticketRel 한 곳에만 남습니다.
    • 🔴 직전 릴리스가 같은 파일에 "git 경로를 정규화하지 말 것"을 적어 두고도 기존 코드를 고치지
      않은 것이 세 번째입니다.
  • fix: .gitignore 의 선행 공백 패턴을 부정으로 오인하던 문제trim() 때문에 !literal
    같은 줄을 부정 패턴으로 보고 완료 티켓의 후속 작업 안내를 불필요하게 막았습니다. gitignore 는
    후행 공백만 버립니다(git check-ignore -v 로 확인).

  • fix: 복구 창에서 정책을 채택하면 증거 결속이 깨져 영구 교착이 되던 문제 — 소비 state 결속
    대조가 한쪽 복구 경로에만 걸려 있었습니다. req:repolicy --run 은 그 창에서도 state 를 바꿔
    checkpoint 커밋했고, 이어지는 복구는 대조 없이 다른 바이트를 썼습니다. 그 뒤 한 번 더 중단되면
    다음 복구가 state-mismatch영원히 막혔습니다.

    • 이제 소비 state 를 쓰기 전에 결속과 대조합니다. 어긋나면 아무것도 쓰지 않고 멈춥니다.
    • req:repolicy --run 은 복구 창(pending_evidence_for 가 살아 있는 구간)에서 거부하고 복구를
      끝내는 명령을 안내합니다. dry-run 은 막지 않습니다. 승인만 받아 둔 상태(소비 전)는 정상이므로
      막지 않습니다 — 막으면 정책 채택이 통째로 봉쇄됩니다.
    • 🔴 도구가 되돌릴 명령을 만들지 못합니다. 승인 시점 state 는 커밋되지 않아 그 바이트를 알 수
      없습니다(측정으로 확인). 그래서 사실만 말하고, 그 창에서 한 변경을 사람이 되돌리게 합니다.
    • 대문자 SHA 로 기록된 결속이 비교에서 어긋나던 것도 함께 고쳤습니다(소문자로 정규화).
  • fix: .gitignore 를 완화·삭제한 상태에서 안내가 그 결정을 대신 확정하던 문제 — 종결 티켓
    안내는 미커밋 .gitignore종류 구분 없이 커밋하게 했습니다. 규칙을 지우는 변경이면 그
    완화가 새 티켓 브랜치에 영구히 남고, 감춰져 있던 파일이 드러나 다음 리뷰가 막혔습니다.

    • 이제 ignore 범위가 좁아질 수 있으면 자동 명령을 내지 않고 그 경로만 보여 준 뒤 사람이
      정하게 합니다. 줄 삭제 · 파일 삭제 · ! 삽입 · 순서 재배치가 대상입니다.
    • 🔴 순서까지 봅니다. gitignore 는 마지막에 일치한 패턴이 이기므로, 줄 집합이 같아도 순서가
      바뀌면 커버리지가 줄어듭니다.
    • 🔴 패턴이 무엇을 매치하는지는 해석하지 않습니다 — 순서와 부정 여부만 봅니다. 애매하면 멈춥니다.
    • 비-부정 줄만 더하는 경우는 종전대로 자동 안내합니다.
  • fix: hard-blocked 보고가 티켓 밖의 역슬래시 경로를 안으로 오분류하던 문제 — git 이 준
    경로에 \/ 변환을 하고 있었습니다. POSIX 에서 역슬래시는 파일명의 일부입니다. 바로 앞
    릴리스가 같은 파일에 "git 경로를 정규화하지 말 것"이라고 적어 두고도 기존 코드를 고치지 않았습니다.

    확인할 파일: scripts/req/req-commit.ts · scripts/req/req-repolicy.ts ·
    scripts/req/lib/evidence-recovery.ts · scripts/req/lib/gitignore-coverage.ts ·
    scripts/req/lib/hardblocked-facts.ts

    🔴 테스트가 통과했다는 사실이 증명이 아니었습니다: 직전 릴리스의 consume 복구 e2e 는 결속값이
    실제와 다른 fixture 로도 green 이었습니다(해시 대조 assert 가 없었습니다). 이번에 그 assert 를
    넣었고, 같은 부류로 역슬래시 회귀 테스트가 백슬래시 없는 경로를 검사하던 것도 고쳤습니다.

  • fix: hard-blocked 보고가 티켓 안의 변경을 "티켓 밖"으로 적던 문제 — repo 루트는 git 에게
    물어(=실경로) 얻고 티켓 경로는 받은 값 그대로 써서, 그 둘의 정규화 수준이 갈리면 상대경로가
    ../… 가 되고 티켓 안/밖 분류가 통째로 뒤집혔습니다.

    • 링크를 거치는 경로에서 발생합니다: macOS(/var/private/var), Windows 8.3 단축 경로,
      junction 으로 연결한 개발 디렉터리.
    • 🔴 게이트 차단은 정상이었습니다. hard-blocked 는 계속 막았고, 틀린 것은 보고 내용뿐입니다.
      그래도 사람을 잘못 안내하므로 고칩니다.
    • 이제 양쪽을 realpath 로 맞춘 뒤 상대경로를 구합니다. 해소에 실패하면 이전과 정확히 같은
      동작
      으로 떨어집니다 — 보고가 차단을 흔들지 않습니다.
    • 실제 링크를 만들어 재현하는 회귀 테스트를 두었습니다(REQ-2026-147 부터 있던 결함이며, 이
      저장소 CI 가 수동 전용이라 그동안 관측되지 않았습니다).

    확인할 파일: scripts/req/review-codex.ts(resolveReal) ·
    scripts/req/lib/hardblocked-facts.ts(toTicketRel) · tests/unit/hardblocked-report.test.ts

  • fix: 종결 안내가 그 상황에서 실행되지 않던 문제 — 바로 위 항목이 낸 안내(git stash push
    req:newgit stash pop)가 두 경우에 두 번째 줄에서 거부됐습니다. req:new 는 clean tree 를
    요구하는데 stash 가 그것을 만들어 내지 못했기 때문입니다.

    • 일반 untracked 파일: --include-untracked 를 쓰지 않아 그대로 남았습니다.
      🔴 이전 판단(“untracked 는 브랜치 전환을 따라오니 -u 는 불필요”)을 뒤집었습니다 — 유실은
      없지만 req:new 가 untracked 자체를 거부한다는 점을 보지 못했습니다.
    • 미커밋 .gitignore: stash 가 ignore 규칙 자체를 되돌려 감춰져 있던 파일(node_modules/ 등)이
      드러났습니다. 이제 그런 .gitignore 를 먼저 커밋하는 두 줄을 조건부로 앞에 냅니다.
      루트뿐 아니라 중첩 .gitignore 도 대상입니다. 🔴 그 커밋은 REQ 워크플로 밖이라
      verify-range --strict 에서 미입증으로 잡힙니다 — 통합 때 commitgate attest 로 사유를 남기십시오.
    • 🔴 경로가 셸 안전하지 않으면(공백 등) 명령열 전체를 내지 않고 목록만 보여 줍니다. 일부만
      숨기면 남은 명령을 실행했을 때 같은 노출이 그대로 일어납니다.
    • 두 상황 모두 실 CLI e2e 로 고정했습니다(안내에서 인자를 뽑아 그대로 실행합니다).

    확인할 파일: scripts/req/req-commit.ts(terminalReentryProblem) · tests/unit/terminal-reentry.test.ts

  • fix: consumed_state_sha256 의 형식 불량이 "결속 없음"으로 강등되던 문제 — 새 키를 허용 목록에만
    등록하고 형식을 보지 않아, {"consumed_state_sha256": null} 인 행이 레거시로 읽혀 결속 대조를
    통째로 건너뛰었습니다.

    • 이제 키의 부재만 레거시입니다. 키가 있는데 64hex 가 아니면 validateManifest 와 복구 판정이
      둘 다 거부합니다(대소문자는 기존 SHA256_RE 계약을 따릅니다 — 새 규칙을 만들지 않습니다).
    • 조회 결과를 absent·bound·malformed 세 갈래 타입으로 바꿔, 호출부가 다시 "없으면
      건너뛴다"로 뭉갤 수 없게 했습니다.
    • 🔴 하위호환은 그대로입니다 — 이 변경 이전 행에는 키 자체가 없습니다.
  • fix: 소비 시각 재사용 경로에 오라클이 없던 문제resumeFrom: 'consume' 창(evidence 커밋 직후·
    소비 state 쓰기 전 중단)의 CLI 테스트가 없어, 그 경로가 소비 시각을 새로 잡아도 아무 테스트가 red
    되지 않았습니다. 🔴 이전에 "도달 불가일 수 있다"고 적은 판단은 틀렸습니다 — 승인 핀이 살아 있어
    checkpoint 분기를 지나지 않고 이 경로로 옵니다. 이제 승인 핀·인벤토리·approved tree 를 갖춘 실 CLI
    e2e 가 그 창을 돌고, 시각을 새로 잡는 변이가 red 입니다.

    확인할 파일: scripts/req/lib/evidence.ts · scripts/req/lib/evidence-recovery.ts ·
    tests/unit/evidence-recovery.test.ts · tests/unit/evidence-recovery-wiring.test.ts

  • fix: 완료된 티켓에 새 phase 를 커밋하면 되돌릴 수 없는 교착으로 끝나던 문제dev-complete
    티켓에서 req:commit --run 을 하면 source 커밋을 먼저 만들고 그 뒤 종결 증거 충돌로 실패했고,
    그때 approvals.jsonl 이 더러워져 이후 모든 req:commit·--finalize 가 D10 에 막혔습니다.
    이 저장소가 실제로 밟았습니다.

    • 이제 어떤 write 보다도 앞(req:doctor 보다도 먼저) HEAD 정본 lifecycle 을 판정하고,
      dev-complete·migrated-complete·abandoned 이면 아무것도 쓰지 않고 멈춥니다.
    • 안내는 그 상태에서 실제로 성공하는 세 줄입니다: git stash pushreq:new <id>-followup --run
      git stash pop. (req:new 는 clean tree 를 요구하므로 한 줄만 내면 그 명령이 거부됩니다.)
    • 🔴 series-terminal차단하지 않습니다 — 대체 REQ 흐름이 그 상태를 지납니다.
    • 🔴 판정에 실패하면 차단하지 않습니다. 추가 안전장치가 새 교착을 만들면 안 됩니다.
    • 🔴 완료 티켓을 다시 여는 전이는 만들지 않았습니다. 사후 정정은 단일 phase micro-REQ 입니다.

    확인할 파일: scripts/req/req-commit.ts(terminalReentryProblem) · tests/unit/terminal-reentry.test.ts

  • fix: checkpoint 복구 창에서 state.json 을 손으로 고쳐도 함께 커밋되던 문제 — 복구는 "이 소비를
    HEAD 가 방금 추가했는가"만 봤고, 워킹 state.json 이 정말 도구가 만든 것인지는 보지 않았습니다.
    그 창 안에서 risk_level·policy_snapshot·phases·user_commit_confirmed 를 고치면 그대로 실렸습니다.

    • evidence-finalize 가 매니페스트 소비 행에 consumed_state_sha256(= 소비 state 바이트의 sha256,
      serializeState 정본)을 함께 커밋합니다. 복구는 워킹 파일 해시가 정확히 일치할 때만 열립니다.
      불일치·읽기 실패는 state-mismatch 로 거부합니다.
    • 🔴 하위호환 구멍을 감추지 않습니다: 이 변경 이전에 커밋된 증거 행에는 이 결속이 없습니다.
      그런 행은 결속 대조를 건너뛰고 종전(REQ-2026-150) 판정만 받습니다 — 그 창에 남는 위험은
      이전과 같습니다. 옛 crash window 를 막으면 그것이 곧 새 교착이기 때문입니다.
    • 🔴 정상 복구는 그대로입니다 — 손대지 않은 crash window 는 여전히 명령 한 번으로 수렴합니다
      (실제 CLI 로 검증, 다섯 가지 변조는 각각 거부).

    확인할 파일: scripts/req/lib/evidence-recovery.ts(판별자 D) · scripts/req/lib/evidence.ts ·
    scripts/req/req-commit.ts · tests/unit/evidence-recovery-wiring.test.ts

  • fix: 완료된 티켓에서 state.json 을 임의로 고쳐도 복구 예외가 열리던 문제 — checkpoint 복구가
    "커밋된 승인 증거가 있다"만 봤는데, 완료된 티켓에는 옛 승인 행이 당연히 있습니다.

    • 이제 HEAD 가 그 소비 행을 방금 추가했을 때만(HEAD 에 있고 HEAD^ 에 없다) 복구가 열립니다.
      그 사실은 커밋해야만 바꿀 수 있고, 커밋이 곧 이 게이트가 통제하는 대상입니다.
    • 완료 티켓의 임의 수정 · 시각만 변조 · 매니페스트 행을 state 로 복사 — 셋 다 거부됩니다.
    • 🔴 정상 복구는 그대로입니다: 증거 커밋 뒤 checkpoint 전에 멈춘 상태는 여전히
      req:commit --finalize --run 한 번으로 수렴합니다(실제 CLI 로 검증).
    • 🔴 막지 못하는 것: evidence-finalize 뒤에 다른 커밋을 하나라도 더 하면 복구가 열리지
      않습니다. 안전한 쪽으로 틀리는 선택이고, 그 상황은 사람이 판단합니다.

    확인할 파일: scripts/req/lib/evidence-recovery.ts · tests/unit/evidence-recovery-wiring.test.ts

  • fix: 도구가 안내에 넣은 자리표시자를 도구가 그대로 되받던 문제 — 안내는 --confirm "승인 문장"
    처럼 실행 가능한 값을 냈고, 받는 쪽은 비어 있지 않은지만 봤습니다. 그 줄을 값 수정 없이 실행하면
    사람의 결정 없이 대체·종결·HIGH 확인·사전 위임(main 병합 권한)·철회가 기록됐습니다.

    • 예약 등록부를 두고 안내를 내는 쪽과 받는 쪽이 같은 상수를 참조합니다. 값을 받는 6표면
      (--resolve · --close-stale · req:close --abandon · req:confirm --method ·
      req:delegate --sentence · req:delegate --revoke --reason)이 전부 거부합니다.
    • 보고가 명령보다 먼저 "따옴표 안은 자리표시자다 — 바꿔서 실행한다"를 말합니다.
    • 🔴 값의 진정성은 판정하지 않습니다. --confirm "x" 는 통과합니다. 사람인지 증명하지 못하는
      한계는 그대로이고, 이 변경은 도구가 자기 출력을 되받는 고리만 끊습니다.
  • fix: 안내 명령이 cmd.exe 에서 깨지던 문제 — 판정을 허용 목록으로 뒤집었습니다. cmd.exe 는
    큰따옴표 안에서도 %VAR% 를 확장하므로 금지 목록은 계속 뚫립니다. 안전하게 만들 수 없으면
    그 갈래의 명령을 통째로 내지 않고 값을 데이터로 보여 줍니다(반쪽 명령열 금지).

  • fix: 파킹 안내가 티켓 밖 staged 파일까지 커밋하던 문제git commit … -- "<티켓>" 으로
    범위를 가둡니다. 이미 staged 인 티켓 밖 변경은 커밋되지 않고 staged 로 남습니다.

  • fix: --resolve replace 가 지정하지 않은 series 를 닫던 문제 — 같은 (kind, phase) 에 열린
    series 가 둘이면 첫 번째가 닫히고 CLI 는 지정한 것을 닫았다고 보고했습니다. 이제 정확한
    series_id
    닫고, 없거나 이미 닫혔으면 조용히 넘어가지 않고 거부합니다.

    🔴 아직 남은 것: 완료된 티켓에서 state.json 을 임의 수정하면 checkpoint 복구가 D10 예외로
    통과할 수 있습니다(외부 리뷰 결함 3). 판별자 후보 3종이 전부 기각돼 별도 REQ 로 분리했습니다.

    확인할 파일: scripts/req/lib/placeholders.ts · scripts/req/lib/shell-safe.ts ·
    scripts/req/lib/nonconvergence.ts · scripts/req/req-review-exception.ts

  • feat: hardCap 에서 멈출 때 왜 수렴하지 않았는지·다음에 뭘 할지 함께 알려 줍니다 — 지금까지는
    맨 예외 한 줄이었습니다. 8회를 쓰고 멈춘 사람이 알아야 하는 것은 "멈췄다"가 아니라 무엇이 반복해서
    걸렸고 어디서 잘라야 하는가
    입니다.

    • 반복해서 걸린 축(파일·지적 본문)을 상위 3개까지, 라운드 요약을 한 줄로 냅니다.
    • 다음 선택은 지금 이 상태에서 성공하는 명령입니다 — --close-stale 은 열린 회차가 실제로 있을
      때만, 파킹 줄은 티켓이 더러울 때만 냅니다. 대체 REQ 의 slug 는 부모 branch 에서 산출합니다.
    • 🔴 조언이지 감사 증거가 아닙니다. 승인·커밋·병합 어느 판정도 이 문구를 읽지 않습니다.
    • 🔴 새 리뷰를 부르지 않습니다. "멈췄는데 또 부른다"는 자기모순이고, 그 호출은 hardCap 회계
      밖에서 비용을 만듭니다.
    • 🔴 hardCap 을 올리라는 선택지는 넣지 않습니다. 목록에 있으면 그게 기본 답이 됩니다.
    • 🔴 보고가 차단을 흔들 수 없습니다. 만들다 실패하면 원래 한 줄로 떨어집니다. 파손된 라운드는
      그 라운드만 건너뜁니다.
    • 관측이 없으면 권고도 없습니다 — 라운드 기록이 아예 없으면 "분석할 자료가 없다"고 말하고 분해안을
      내지 않습니다("매 라운드 지적이 달랐다"와 구별합니다).

    확인할 파일: scripts/req/lib/nonconvergence.ts · scripts/req/lib/hardblocked-facts.ts ·
    scripts/req/review-codex.ts · docs/workflow.md

  • fix: req:nextstopGate: "auto" 티켓에 다른 정책 이름과 틀린 다음 명령을 안내하던 문제
    같은 순간 req:doctorOK D32: 정지 정책 일치(stopGate="auto") 라고 했습니다. 두 도구가 같은
    티켓의 정책을 다르게 말했습니다.

    • 정책명을 실효값에서 보간합니다(한 자리에 merge 가 박혀 있었습니다 — defersToIntegration
      분기라 auto 도 그 문구를 들었습니다).
    • 🔴 auto 의 다음 지점은 통합이 아니라 사전 위임 발급입니다. 위임이 없으면 integrate
      absent 로 멈추므로, I1/I2/B1 승인 문장을 받아도 그 다음이 막혔습니다. 이제
      req:delegate 명령을 실제 REQ id·branch 가 박힌 형태로 안내합니다.
    • 🔴 HIGH 티켓에는 --high-risk 를 포함합니다 — 없으면 통합이 high-risk-unacked 로 막힙니다.
      반면 --allow-push·--allow-bypass안내하지 않습니다(기본 불허가 안전 속성이고,
      목록에 있으면 그게 기본 답이 됩니다).
    • 🔴 branch 값은 따옴표로 감싸 렌더링하고, 셸이 해석하는 문자(" ` $)가 있으면
      명령으로 만들지 않고 데이터로 보여 줍니다. 안전하게 만들 수 없으면 만들지 않습니다.
    • merge·req·phase 안내는 바이트 단위로 종전과 같습니다.
    • 회귀 가드는 Record<StopGate, …> 로 순회해 정책 값이 늘면 타입 검사가 깨집니다 — 배열
      리터럴이면 새 값에서 같은 드리프트가 조용히 재발합니다.

    확인할 파일: scripts/req/req-next.ts · tests/unit/next-policy-guidance.test.ts

  • fix: hardCap 이 안내하는 탈출구가 실행 불가였던 문제 — 상한에 닿으면 도구가 "종료하거나 정합한
    대체 REQ를 작성한다"고 안내하는데, req:new --successor-of 는 부모에 사람의 대체 결정이 기록돼
    있어야 진행합니다. 그 기록을 남기는 CLI 표면이 없었습니다(로직은 있었고 배선만 없었습니다).

    • req:review-exception <REQ> --resolve replace --series "<id>" --reason "…" --confirm "…" --run 신설.
      실행 직후 req:new --successor-of 가 다른 조작 없이 성공합니다.
    • 🔴 hardCap 을 열지 않습니다. 회차를 되돌리지도 늘리지도 않습니다 — 대체 REQ 는 새 티켓이고
      새 예산이며, 부모 이력은 lineage 로 보존됩니다.
    • --series 는 짐작하지 않습니다(한 티켓에 design·phase 가 동시에 열릴 수 있음). 잘못된 값에는
      그 티켓의 열린 series 목록을 실제 값으로 돌려줍니다.
    • --reason·--confirm 은 필수이며 공백만이면 거부합니다. 사유는 note, 승인 문장은 method
      각각 남습니다. 결정 시각은 실제 시계입니다.
    • 🔴 남의 staged 파일은 커밋하지 않습니다. 자기가 바꾼 state.json 만 커밋하고, 남은 것이 있으면
      막는 경로를 실제 값으로 열거하고 다음 명령을 줍니다.

    확인할 파일: scripts/req/req-review-exception.ts · tests/unit/resolve-replace.test.ts ·
    docs/workflow.md

  • docs: 이 저장소의 stopGate"auto" 로 전환(도그푸딩)auto명시 opt-in 이고,
    사전 위임(req:delegate)이 없으면 통합은 종전대로 막힙니다. 이 전환은 이 저장소의 설정이며
    도구 기본값이나 소비자 동작을 바꾸지 않습니다.

    • 전환 경로 실측: req:new 는 clean tree 를 요구하므로 config 를 먼저 바꿔도 티켓이 안 열립니다.
      티켓 안에서 config 를 바꾸고 req:repolicy <REQ> --run 으로 재채택하면 그 티켓부터 새 정책이
      적용됩니다(D32 드리프트는 WARN 이지 FAIL 이 아닙니다).
  • docs: REQ-2026-142 설계문서 정오표 — 완결 티켓의 본문은 고치지 않고 정오표 절만 덧붙였습니다.
    PinnedInventoryItem 필드명 · resumeFrom 값 3종 · 🔴 없는 테스트 경로(vitest 는 매칭 0건이어도
    exit 0 이라 초록이 거짓말을 합니다).

  • fix: 커밋이 증거 기록 도중 죽으면 안내하는 복구 명령 자체가 실행될 수 없던 문제req:next
    req:commit --finalize --run을 안내하는데, 그 상황에서는 approvals.jsonl이 더러워 D10
    그 명령을 막았습니다. staged로 바꿔도 같았습니다(responses/는 인덱스 여부와 무관하게 flag).
    (이 저장소가 도그푸딩 중 실제로 밟았습니다 — REQ-2026-140 phase-6.)

    • 🔴 D10을 완화하지 않았습니다. 승인 시점에 아카이브 목록(경로 + SHA-256)을 state에 못 박고,
      복구가 그것과 바이트 단위로 일치할 때만 정확히 그 파일들만 통과시킵니다. --finalize 플래그
      하나로는 아무것도 열리지 않습니다.
    • 목록에 없는 아카이브 주입·승인 이후 내용 변경·소스 파일 혼입·승인과 다른 tree·커밋된 매니페스트와의
      불일치는 전부 거부하고, 왜인지와 다음에 할 일을 알려 줍니다.
    • 어느 지점에서 죽었든 재실행이 남은 만큼만 이어서 하고 수렴합니다. 소비 상태만 미커밋인 창
      (예전엔 approval_evidence가 이미 사라져 복구가 원리적으로 불가능하던 구간)도 이제 풀립니다.
    • 🔴 이 릴리스 이전에 승인받은 티켓에는 그 목록이 없습니다. 근거가 없으므로 복구를 열지 않습니다
      (동작은 종전과 같음) — 재리뷰로 새 승인을 받으십시오.
    • 🔴 원장·state.json·approvals.jsonl을 손으로 고쳐 푸는 경로는 만들지 않았습니다.

    확인할 파일: scripts/req/lib/evidence-recovery.ts · scripts/req/req-doctor.ts(D10 배선) ·
    scripts/req/req-commit.ts(checkpoint 재개) · docs/workflow.md

  • fix: 리뷰 실행이 중간에 죽으면 그 티켓의 리뷰가 영구 차단되던 문제 — 원장에 attempt-opened
    남아 다음 리뷰가 리뷰 원장 무결성 실패(fail-closed)로 막혔고, 빠져나갈 방법이 없었습니다.
    (이 저장소가 도그푸딩 중 실제로 밟았습니다.)

    • req:review-exception <REQ> --close-stale <series_id> --reason "…" --run 신설. 원장에
      attempt-closed(판정 abandoned)를 남기고 state의 회차를 원장에 맞춥니다.
    • 🔴 원장 행을 지우지 않습니다. 그 회차에 호출은 실제로 나갔으므로 지우면 기록이 거짓이 됩니다.
      버렸다는 사실을 더해서 해소합니다. 사유는 필수입니다.
    • 🔴 비용은 사라지지 않습니다. 버린 회차는 hardCap에 그대로 남고 autoBudget에서만 빠집니다
      (void_attempts — 판정을 받지 못한 회차를 위한 기존 회계).
    • 🔴 이 명령 자체가 중간에 죽어도 재실행이 수렴합니다. 이미 기록된 행을 다시 만들지 않고 남은
      부분만 맞춥니다 — 그러지 않으면 고치려는 교착을 스스로 만듭니다.
    • 열린 회차가 여럿이면 가장 이른 것부터 닫습니다(재실행이 순서대로 해소).
  • fix: req:delegate가 dispatch 경계 계약을 어기고 있었습니다runCli를 export하지 않아
    dispatch.test.ts가 red였습니다. 전체 스위트에서만 드러나는 종류의 배선 갭입니다.

  • feat: stopGate: "auto" — 사전 위임 범위 안의 검증된 변경을 자동 통합합니다 — 🔴 "무제한 자동
    실행"이 아닙니다.
    값을 바꾸는 것만으로는 아무 권한도 생기지 않습니다. 권한은 설정이 아니라
    커밋되는 append-only 원장(workflow/delegations.jsonl)에서 나오고, 위임이 없으면 merge
    똑같이 통합 직전에 멈춥니다.

    • npx commitgate req:delegate 신설 — 발급·철회(--revoke)·조회(--status). 승인 문장은 사람이 말한
      그대로 넘기고, 시각·SHA·만료는 도구가 읽습니다(사람이 적을 자리가 없습니다). 만료 기본 12시간·
      상한 72시간 — 무기한 위임은 만들 수 없습니다.
    • 🔴 위임이 있어도 막는 것: hardCap 도달 · HIGH 위험(별도 --high-risk 없음) · BLOCKED/미판정 리뷰 ·
      trunk 이동 · 대상 브랜치 불일치 · 위임 범위 밖 티켓이나 delivery 레코드 · 귀속을 판정할 수 없는
      커밋이나 attested 커밋 · 이미 소비·철회·만료된 위임. 차단 지점에서 "모르겠음"은 통과가 아닙니다.
    • 🔴 권한은 정확히 한 번 소비됩니다(CAS). 소비 기록을 먼저 커밋하고 병합하며, 검증한 SHA와 병합할
      SHA 사이에는 그 소비 커밋 하나만 허용합니다. 실제 수행 결과는 별도 executed 행으로 남으므로,
      executed 없는 소비 행은 "소비했는데 결과를 모른다"는 정직한 중단 흔적입니다.
    • 🔴 push·bypass는 기본 불허이고 서로 독립입니다. --allow-push 없이는 로컬 병합까지만 합니다.
      push를 위임했다면 --allow-bypass도 필요합니다 — 병합으로 만들어진 merge SHA는 required check가
      돌아간 적이 없어 push 자체가 우회이기 때문입니다(CI를 실행해 통과했더라도 그것은 feature SHA에
      대한 검사입니다). 우회를 실제로 썼다면 원장과 최종 보고 양쪽에 남습니다.
    • auto가 아닌 설정은 아무것도 달라지지 않습니다. phase·req·merge에서는 이 축이 위임 원장을
      읽지도 않습니다.
    • 🔴 도구가 보장하지 못하는 것: 승인 문장이 실제로 사람에게서 왔는지는 검증할 수 없습니다
      (req:confirm과 같은 한계). 보장하는 것은 시각·SHA·만료·소비의 정직성입니다.
  • chore: 이 저장소가 리뷰 예산 onSoftLimit: "auto"를 채택합니다 — 🔴 도구 기본값 변경이
    아닙니다.
    소비자 기본값은 그대로 ask이며, 바뀐 것은 CommitGate 저장소 자신의
    req.config.json뿐입니다. stopGate: "merge"로 자율 진행을 고르고도 예산 축이 ask라 소프트 한도
    초과 회차마다 멈추던 상태를 해소합니다(REQ-2026-135가 안내하던 바로 그 업그레이드입니다).

    • hardCap그대로 8입니다. auto가 없애는 것은 소프트 초과 회차의 사람 예외 승인 하나뿐이고,
      반복 백스톱은 남습니다 — 이 값을 늘리면 auto가 "무제한 자동"이 됩니다.
    • 🔴 리뷰 예산은 정책 스냅샷 대상이 아니라 라이브로 읽힙니다 — 이미 진행 중인 티켓에도 즉시
      적용됩니다(stopGate와 다른 점입니다).
  • docs: 랜딩 README의 "사람이 멈추는 지점"이 실제 동작과 어긋나 있었습니다docs/configuration*.md
    이미 정정돼 있었지만 처음 읽는 사람이 보는 랜딩만 옛 서술로 남아, 정본 문서와 반대를 말했습니다.

    • 🔴 정지 축은 하나가 아니라 둘입니다. reviewBudget.onSoftLimit(기본 ask)은 소프트 한도를 넘긴
      재리뷰 회차마다 사람 예외를 요구합니다 — stopGatemerge로 두어도 거기서 멈춥니다. 랜딩이 축을
      하나로 적고 있어, 그렇게 멈춘 사용자는 이유를 어디서도 찾을 수 없었습니다. 두 번째 소절을 만들어
      ask·auto가 각각 어떻게 되는지와 hardCap두 값 모두에서 막는다는 것을 적었습니다
      (기본값·설정 방법은 docs/configuration*.md가 정본이므로 복제하지 않고 링크합니다).
    • 🔴 merge는 delivery set이 없어도 멈춥니다. 그때는 req와 같은 자리 — 그 REQ의 통합 직전 —
      입니다. 랜딩은 묶음 종료만 적고 있어 merge를 고르면 묶음을 만들기 전까지 정지가 아예 없다고
      읽혔습니다. 표 셀과 본문 양쪽에 두 경우를 담고, 용어집의 delivery set 행에도 묶음이 선택임을
      밝혔습니다.
    • "setup의 세 번째 질문"이라는 순서 표현도 걷어냈습니다 — setup은 이제 리뷰 예산도 묻습니다.
    • 위 두 오표현을 RETIRED_CLAIMS에 한·영으로 등재해, 같은 문장이 문서로 되돌아오면 테스트가 red입니다.
    • 첫 소개(흐름도·:57)에도 같은 배타 표현이 남아 있어 함께 고쳤습니다 — "중간에는 멈추지
      않는다"·"그 두 지점에서만"은 리뷰 예산 초과 회차의 사람 정지를 지웁니다. 바로 아래 줄이 이미
      "6~8회는 사람이 예외를 기록해야 한다"고 적고 있어 같은 화면 안에서 앞뒤가 맞지 않았습니다.
      지운 것은 "기본값에서 phase 커밋마다 부르지 않는다"는 사실이 아니라 예외를 함께 지우는 절대
      표현입니다. 한글 흐름도의 여기서만은 산문과 같은 주장이라 함께 고쳤습니다(영문 흐름도에는
      원래 배타 표현이 없었습니다). 이 문구들도 등재해 재발하면 red입니다.
  • feat: 기존 프로젝트도 commitgate quickstart로 자율 진행 계약을 받습니다 — 위 항목이 계약에 §4-1을
    넣었지만 init은 seed-once라 이미 AGENTS.md가 있는 저장소에는 닿지 않았습니다. "직접 반영하세요"라고
    적을 수밖에 없던 자리를 도구가 대신합니다. 관리 블록이 집합이 되어(quickstart·autonomy) 같은
    verb가 계약 블록도 동기화합니다.

    • ⚠️ 이름은 Quick Start에서 왔지만 이제 관리 블록 전체를 다룹니다 — help·docs/upgrade*.md·
      docs/workflow*.md·docs/agent-prompt*.md를 그 범위로 갱신했습니다.
    • 🔴 마커가 손상된 파일은 아무것도 쓰지 않습니다. 안전 판정을 블록별 개수가 아니라 문서 전체 마커
      스트림
      으로 합니다 — 두 블록이 교차 중첩되면 각 id는 "정상 쌍 1회"로 보이지만, 한 블록을 치환하면
      다른 블록의 마커와 그 사이 사용자 내용이 함께 지워집니다. 반쪽·중복·중첩·교차 전부 차단합니다.
    • 🔴 계약 마커가 없는 AGENTS.md는 고치지 않되 조용하지도 않습니다. 무엇을 AGENTS.commitgate.md
      (설치 시 놓이는 계약 사본)에서 옮기면 되는지 알려 주고, 사본이 없으면 얻는 법을 안내합니다.
    • 한 파일에 여러 블록이면 한 번만 씁니다(각 블록을 원본 기준으로 따로 쓰면 마지막 쓰기만 남아 한
      블록을 잃습니다). 블록 밖은 바이트 보존이고 재실행은 멱등입니다.
    • req:doctor D21네 사유를 구별합니다: 부재 · 드리프트 · 마커 손상 · 계약 아님.
      앞 둘에만 quickstart --apply 해소 명령이 붙습니다(뒤 둘은 그 명령으로 해소되지 않습니다).
  • docs+feat: auto 정책의 범위를 확정하고 기존 프로젝트의 업그레이드 경로를 열었습니다
    onSoftLimit: "auto"가 무엇을 없애고 무엇을 남기는지가 문서에 흩어져 있어, 특히 hardCap에서 멈추는
    것이 정책 실패인지 설계인지 구분되지 않았습니다. docs/configuration*.md의 리뷰 예산 절을 정본으로
    삼아 표로 확정했습니다(workflow*.md는 그 정본을 가리킵니다 — 표를 복사하지 않습니다).

    • 🔴 hardCap은 비용 상한이 아니라 반복 백스톱입니다. autoBudget은 판정을 낸 회차(productive)를
      세고 hardCap나간 호출 수(dispatched)를 셉니다 — 판정이 없던 회차도 소모되므로 유효 리뷰를
      hardCap번 받지 않고도 도달할 수 있습니다. 그래서 auto가 이 정지를 열지 않는 것은 설계입니다.
    • 자율 통합 값을 이때는 두지 않았습니다. 통합 승인은 세 값 공통 불변식이고, 도구가 확인 기록을
      대신 만드는 방식은 시각 날조 표면을 되살리기 때문입니다. 같은 문서가 정직한 유일한 형태는 작업
      시작 시점의 사전 위임 기록
      이라고 적었고, 그것이 안전 속성 변경이라 별도 논의가 필요하다고 봤습니다.
      그 논의의 결과가 이 릴리스의 첫 항목입니다stopGate: "auto"가 정확히 그 사전 위임 형태로
      들어왔습니다. 근거는 뒤집히지 않았고, 그 형태를 실제로 구현한 것입니다.
    • 업그레이드 안내가 마찰을 겪는 자리에서 나옵니다: stopGatereq·merge인데 아직 ask라서
      소프트 예산에서 멈췄을 때, req:next가 그 정지를 끄는 방법을 함께 알려 줍니다. hardCap 도달에는
      내지 않습니다(설정으로 열리지 않는 정지를 열 수 있다고 말하면 거짓 안내입니다). 상시 진단으로
      만들지 않은 이유도 같습니다 — ask는 정당한 선택입니다.
  • fix: 정책 스냅샷이 "반쪽 동결"이던 결함 — 이제 두 축이 함께 동결됩니다 — 스냅샷은 stopGate
    얼렸고 거기서 파생되는 phaseCommit.autoApprovereq:next현재 config에서 직접 읽었습니다.
    그래서 스냅샷이 merge인데 config가 phase인 티켓은 커밋 게이트는 통과시키는데 req:next는 매 phase
    멈추라고
    했습니다 — 한 티켓이 두 정책으로 판정됐고, "설정 변경과 무관하게 LOW phase를 순차 자동
    진행"이 정상 경로에서 깨졌습니다. effectiveExecutionPolicy(state, cfg)가 두 값을 함께 내고 req:next
    config의 파생 축을 더 이상 읽지 않습니다(소스 검사로 고정).

    • 파생 규칙은 새로 만들지 않았습니다 — AUTO_APPROVE_OF(기존 번역표)가 그대로 SSOT입니다. 스냅샷에는
      계속 stop_gate 하나만 저장합니다(두 값을 저장하면 저장 시점에 갈라질 자리를 새로 만듭니다).
    • 스냅샷 없는 legacy 티켓은 완전 무변경 — 두 축 모두 현재 config를 따릅니다.
    • 회귀는 실 git에서 req:next main을 태워 4종(교차 2 + legacy 2)을 고정합니다. 순수 함수는 이미
      옳게 동작했으므로 순수 테스트만으로는 이 결함을 잡을 수 없었습니다.
  • feat: commitgate setup이 정지 지점을 정확히 말하고 예산 정책도 묻습니다 — 설정을 고르는 화면이
    merge를 "묶음이 끝날 때까지 미룸"이라고만 설명해, 묶음이 없으면 req처럼 통합 직전에 멈춘다
    사실이 빠져 있었습니다. 같은 화면의 고지 문구와 docs/configuration*.md의 설정 표도 함께 정정했습니다 —
    값 설명만 고치면 한 화면에서 상반된 두 문장을 동시에 보게 됩니다.

    • 질문이 셋에서 으로 늘었습니다: reviewBudget.onSoftLimit이 추가됩니다. stopGate로 자율 진행을
      골라도 예산 정지는 따로 끼어드는데, 그 축은 req.config.json을 직접 편집해야만 닿았습니다 —
      "사람이 멈추는 지점"을 묻는 화면이 정지를 만드는 축 하나를 빠뜨리면 setup을 마쳐도 여전히 끊깁니다.
    • autoBudget·hardCap묻지 않고 기존 값을 보존합니다(setup이 사용자가 조정한 값을 덮지 않습니다).
    • 정지 지점 고지가 stopGate 질문에만 붙습니다(예전 조건은 "null을 못 받는 키"라는 간접 조건이라,
      새 질문에도 엉뚱하게 붙었을 것입니다).
  • feat: 리뷰 소프트 예산 초과 처리를 고를 수 있습니다 — reviewBudget.onSoftLimitstopGate
    req·merge로 두고 자율 진행을 설정해도, 리뷰가 autoBudget(기본 5)을 넘는 순간 워크플로가 매 회차
    멈췄습니다. 그 정지는 stopGate가 정한 것이 아니라 예산 축이 따로 만든 것이라, 사용자가 고른 정지 지점과
    무관하게 끼어듭니다. 이제 "auto"로 두면 6~8회차가 사람 승인 없이 진행됩니다(기본은 "ask" — 현행 유지).

    • 🔴 hardCap은 두 값 모두에서 그대로입니다. auto는 무한 재시도가 아니라 6~8회차의 사람 확인
      생략이며, 이 정지는 비용 통제이지 안전 게이트가 아닙니다 — 리뷰 승인·증거·통합 통제점은 불변입니다.
    • 원장에 soft_limit_resolution(exception/policy)을 남깁니다. exception_consumed의 의미는 넓히지
      않았습니다 — policy일 때 그 값은 false입니다(정책 통과가 사람 승인으로 위장하면 안 됩니다).
    • auto에서 req:review-exception은 예외를 부여하지 않습니다(소비될 일 없는 승인 기록 방지).
    • 기존 {"autoBudget":3,"hardCap":6} 설정은 그대로 유효하고 onSoftLimitask로 채워집니다
      (로더가 reviewBudget을 키별로 병합하도록 바꿨고, 스키마는 새 키를 required에 넣지 않았습니다).
  • docs: 계약이 "언제 묻지 않는가"를 명시합니다 — 정지 지점은 stopGate 하나가 정하도록 도구를 정리했지만
    (아래 두 항목), 끊김의 나머지 절반은 에이전트 계층이었습니다. 계약이 "AWAIT_HUMAN이면 멈춘다"만
    말하고 RUN일 때 물어봐도 되는지를 말하지 않아, 같은 설정에서 같은 워크플로가 세션마다 다르게
    끊겼습니다. 이제 AGENTS.template.mdRUN·AGENT·DONE에서 묻는 것을 계약 위반으로 규정하고,
    멈추는 자리를 예외 9항목으로 못 박습니다.

    • 🔴 예외는 kind가 아니라 행위로 판정합니다. req:rebind처럼 확인 문장을 요구하는 명령은
      AGENT 상태의 진단 줄로 나올 수 있어, kind만 보면 에이전트가 사람의 확인 문장을 대신 쓰게 됩니다.
    • 설계 정정(같은 목표·방법 수정 → 자율 + 재승인)과 범위 변경(00-requirement.md가 바뀜 → 사람)의
      경계를 세웠습니다. 이 경계가 없어 기존 "설계 범위 변경은 보고" 규칙과 자율 진행이 정면 충돌했습니다.
    • 권장안 채택 기록은 사람 확인이 아닙니다user_commit_confirmedreq:confirm만이 씁니다.
    • docs/agent-prompt*.md의 "사람 전용 명령" 표에서 req:confirm을 뺐습니다. 그것은 통제점이지
      사람 전용 명령이 아닙니다(계약의 사람 전용 표에는 commitgate setup 하나뿐이고, req:next는 그 명령을
      에이전트가 실행하도록 출력합니다) — 두 문서가 서로 다른 말을 하고 있었습니다.
    • ⚠️ 기존 AGENTS.md는 자동으로 갱신되지 않습니다(init은 파일이 없을 때만 생성). 새 계약을 받으려면
      기존 파일에 해당 절을 직접 반영하세요.
  • feat: delivery 묶음의 통합 승인이 "그때 그 내용"에 결속됩니다 — 승인은 state: "approved" 플래그
    하나여서, 승인 뒤 묶음 브랜치에 커밋이 더 들어와도 게이트가 조용했습니다. 사람은 "승인했다"고 기억하지만
    승인한 것과 다른 내용이 main으로 갈 수 있었습니다(phase 층에는 이미 같은 결속이 있습니다 — D9·
    approved_tree provenance). 이제 approve가 승인 직전 묶음 tip을 남기고, 이후 레코드 밖을 건드린
    커밋
    이 있으면 AWAIT_HUMAN(재승인)이 됩니다. 재승인은 reopensealapprove 순서입니다.
    승인이 만드는 레코드 커밋은 제외되므로 승인이 자기 자신을 무효화하지 않습니다. 결속이 없는 옛 레코드는
    그대로 통과합니다(소급 요구 없음).

  • feat: commitgate integrate가 stale·미승인 묶음의 병합을 막습니다 — 소스가 delivery/*면 그 묶음의
    승인이 병합 인가입니다. 기본 branchPrefix에서는 delivery 브랜치가 전제에서 걸러지지만
    branchPrefix: "delivery/"는 지원되는 설정이라 병합 지점까지 도달할 수 있어, 소스 브랜치 이름으로
    판정합니다. 이 지점에서는 확인 불가가 통과가 아닙니다: 미승인(open·sealed 미종결)·레코드 파싱
    실패·승인 결속 확인 불가(base_sha가 이력에 없음)·레코드의 branch가 소스와 불일치는 전부 차단합니다.
    (안내 지점인 req:next·delivery status는 반대로 판정 불가를 무판정으로 둡니다 — git이 잠깐 실패했다고
    멀쩡한 승인을 무효화하지 않기 위해서입니다.)

  • feat: 티켓은 만들어질 때의 stopGate로 끝까지 갑니다(정책 스냅샷) — 게이트가 매번
    req.config.json을 다시 읽어, phase-1·2를 phase로 확인받고 중간에 설정을 merge로 바꾸면 나머지가
    확인 없이 자동 커밋됐습니다. 이미 받은 확인의 의미가 사후에 바뀌는 상태였고, 완료된 티켓의 증거만
    보고 어떤 정책으로 진행됐는지 알 수도 없었습니다. req:new가 해소값을 state.policy_snapshot.stop_gate
    고정하고 게이트 다섯이 그 값을 봅니다. 스냅샷이 없는 기존 티켓은 예전처럼 config를 따릅니다(무회귀).

  • feat: commitgate req:repolicy — 진행 중 티켓에 현재 config 정책을 채택합니다. 스냅샷만 넣고 이 경로를
    빼면 정책을 바꾼 사용자의 티켓이 옛 정책에 영구히 갇힙니다(REQ-2026-072·093에서 겪은 교착의 재발).
    🔴 게이트 우회가 아닙니다 — 바뀌는 것은 정지 지점뿐이고 기록된 확인은 지워지지 않습니다. 채택 이력은
    append-only이며 시각은 실제 시계에서 읽습니다.

  • feat: req:doctor D32(WARN) — 티켓 스냅샷과 config가 다르거나 스냅샷이 손상됐으면 알리고 채택 명령을
    안내합니다. FAIL이 아닙니다 — 정책 변경은 정당한 행위이고, 게이트가 이미 스냅샷을 쓰므로 판정은 일관합니다.

  • fix: 자유 텍스트 옵션이 플래그를 값으로 삼키던 결함req:confirm --method --run처럼 값 자리에 온
    알려진 옵션이 승인 문장·사유로 해석돼, DRY-RUN 의도가 실제 기록으로 바뀔 수 있었습니다. 모든 대시를
    거부하지는 않습니다(정당한 -이유는 그대로 값) — 이 CLI가 해석하는 플래그만 값 누락으로 봅니다.

  • fix: stopGate: "merge"가 delivery 묶음 없이도 통합 직전에 멈춥니다 — 묶음에 속하지 않은 REQ의
    req:next 종단이 DONE이라, 가장 늦게 멈추겠다고 고른 값이 오히려 아무 데서도 멈추지 않았습니다
    (같은 자리에서 reqAWAIT_HUMAN을 냅니다). 이제 묶음이 없으면 req 종단과 같은
    AWAIT_HUMAN(통합 feature→main)입니다. 묶음이 살아 있으면(열려 있거나 다른 member가 남음) 종단은
    그대로 DONE입니다 — 그건 이 값의 존재 이유이므로 바꾸지 않았습니다.

  • fix: merge + 묶음 없음에서 HIGH 사람 확인이 아무 게이트에도 걸리지 않던 공백을 닫았습니다
    merge는 커밋을 막지 않고(userConfirmGate) 묶음 확인은 delivery integrate에서만 요구되므로,
    묶음이 없는 HIGH 티켓은 확인 기록 0건으로 통합 지점에 도달할 수 있었습니다. 이제 종단이
    req:confirm --scope req를 먼저 요구합니다. req에서는 이 요구를 켜지 않습니다 — 그 값은 REQ를
    완성시키는 커밋에서 이미 확인을 받고 소비하므로 종단에서 다시 물으면 같은 확인을 두 번 받는 셈입니다.

  • refactor: 확인 scope 판정을 상수표에서 함수로 승격했습니다(requiredConfirmScope) — merge
    요구하는 scope는 delivery 묶음 소속에 따라 갈리는데(속함=delivery·없음=req) 표는 그 조건을 담지
    못했습니다. req:next(안내)·req:commit(게이트)·req:confirm(입력 검증)·delivery integrate(자격검사)
    네 곳이 이 함수 하나를 공유합니다. 오버로드로 merge를 포함한 호출부는 묶음 맥락을 반드시 주게 해,
    조기 반환이 사라지면 조용한 오답 대신 타입 에러가 나도록 했습니다.

    • 부수 효과로 안내와 도구의 불일치가 사라집니다 — 이전에는 종단이 --scope req를 안내해도
      req:confirmdelivery만 받아 실행할 수 없는 명령을 안내할 수 있었습니다.
    • readDeliveryGate·mentionsMemberlib/delivery.ts로 이동했습니다(req-next re-export 유지).