gatebound
08장

CLI 레퍼런스

gatekit 0.16.14 · docs/manual 767bccc 커밋에서 생성

반드시 이 형식이어야 한다

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" <subcommand> [args]

왜 다른 형식은 안 되는가

커맨드는 사용자의 프로젝트 디렉터리에서 실행된다. 거기서는 gatekit 패키지가 sys.path에 없다. 그래서 모듈 실행 형식은 프로젝트 디렉터리에서 import 오류로 죽는다.

bin/gatekit.py 런처는 자기 위치에서 플러그인 루트를 계산해 sys.path 맨 앞에 넣은 뒤 디스패처를 부른다. 그 외에는 동작이 같다.

플러그인 루트로 cd한 뒤 실행하는 것도 안 된다. 작업 디렉터리가 바뀌면 프로젝트 루트 탐지와 상대 경로가 전부 플러그인 쪽을 가리키게 된다.

이 규칙은 CI 게이트 tools/gate_command_invocations.py가 강제한다. 커맨드·정책 파일이나 스펙 템플릿(plugin/spec-kit/templates/)에 실행되지 않는 호출 형식이 들어가면 빌드가 실패한다.

서브커맨드 10개

cli.py의 SUBCOMMANDS 레지스트리가 전부다. 모듈은 지연 import되므로 하나가 깨져도 나머지는 동작한다.

서브커맨드역할
doctor설치·훅·상태·워커·호스트 8축 진단
spec스펙 세트 검증
contract완료 계약 파생·상태·실행
approve해시 앵커 승인
jobs워커 잡 실행과 관리
workers워커 백엔드 관리
ledger세션 원장 조회·파이프라인 설정
installCodex 호스트 층 생성 (--host codex)
migrate상태 디렉터리를 다른 이름으로 옮김 (기본은 미리보기)
lang출력 언어 감지

인자 없이 부르면 사용법을 출력하고 종료 코드 1을 낸다. -h·--help·help는 0을 낸다. 없는 서브커맨드는 2다.

doctor

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" doctor [--root PATH] [--json]

8개 축(플러그인 파일, 훅 등록, 프로젝트 상태, 스펙 세트, 계약 신선도, 워커, 파이썬, Codex 호스트 층)을 각각 판정하고 축마다 fix 문자열을 낸다.

종료 코드뜻
0fail 축이 하나도 없음
1fail 축이 하나 이상

spec

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" spec validate [--root PATH] [--json] [--lang ko|en]

validate가 유일한 하위 명령이다. --root를 주면 그 경로를 프로젝트 루트로 직접 지정한다. 주지 않으면 상위로 걸어 올라가며 탐지한다.

종료 코드뜻
0판정이 fail이 아님
1판정이 fail
2validate 외의 하위 명령

contract

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" contract derive [--root PATH] [--json]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" contract status [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" contract run [--root PATH] [--json] [--budget SECONDS]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" contract baseline [--root PATH] [--json] [--budget SECONDS]
동작하는 일
derive05-gate.md의 펜스를 .gatekit/contract.json으로 파생. 소스 해시와 예산을 함께 기록
statusok(최신) / fail(stale) / unverified(없음) 중 하나를 출력
run각 기준을 실행하고 집계 판정을 낸다
baseline승인 전에 기준을 한 번 실행해 already_passes(작업 전부터 통과) / not_yet_runnable(어떤 태스크가 만들 경로가 아직 없음) / fails / command_error / unverified(테스트를 하나도 돌리지 않음, 수집한 테스트를 전부 건너뜀, 아무 태스크도 manifest를 만들지 않는데 설치 전인 node_modules·.venv 안의 프로그램 포함)로 분류하고 .gatekit/baseline.json에 기록한다. 작업 전 트리에서 실행하므로 기준이 만든 파일(빌드 결과, DB 파일 등)은 그대로 남는다. Stop 게이트의 기록은 건드리지 않는다(ADR-0022)

--budget은 계약에 선언된 예산을 덮어쓴다.

종료 코드뜻
0derive 성공, status/run이 ok, 또는 baseline에 command_error가 없음
1derive 실패, status/run이 ok가 아님, 또는 baseline할 최신 계약이 없음
2인자 오류
4baseline에서 명령 자체가 오류인 기준이 있음

approve

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" approve <path> [--note "..."] [--by NAME] [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" approve check <path> [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" approve list [--root PATH]

approve <path>는 아무것도 묻지 않고 현재 해시를 기록한다. 사용자에게 AskUserQuestion으로 묻는 것은 커맨드 파일의 책임이다. spec/05-gate.md를 승인하면 각 기준의 채점 파일 해시도 함께 기록하고, approve check spec/05-gate.md는 그 파일이 바뀐 채 다시 derive된 계약이면 fail을 출력하고 stderr에 경로를 적는다(ADR-0023). 쓰기 게이트는 파일 해시만 보므로 이것 때문에 쓰기가 막히지는 않는다. 워커 안(GATEKIT_TASK_ID가 설정됨)에서는 approve가 종료 코드 1로 거부된다. check·list는 그대로 동작한다. 워커가 env -u GATEKIT_TASK_ID로 변수를 지우고 실행하려 하면 bash 게이트가 그 명령을 먼저 거부한다.

종료 코드뜻
0승인 성공, list 성공, 또는 check가 ok
1대상 파일 없음, 워커 안에서 승인 시도, 또는 check가 fail/unverified
2인자 누락

jobs

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs start [--tasks id,id] [--backend name] [--parallel N] [--dry-run] [--no-preflight] [--force-retry id,id]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs status [--job ID | --all] [--json]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs wait [--job ID] [--timeout S]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs results [--job ID | --all] [--compact|--json]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs redelegate <task_id> [--job ID]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs stop [--job ID]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs evaluate [--backend name] [--prompt FILE] [--lang ko|en] [--force-read-only-evaluator] [--json]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" jobs clean [--all]

results --compact는 태스크당 한 줄로 id state gates_passed/total을 출력한다. 게이트가 한 번 실패한 뒤(미리 돌린 preflight 실패와 앞선 잡의 실패도 포함) 그 게이트 argv가 가리키는 채점 파일(테스트 파일·argv[0] 스크립트)이 바뀌고 나서야 통과한 태스크는 passed를 유지하되, status 표에 (grading changed after failure: <경로>), results --compact에 grading-changed=<경로>가 붙고 status.json의 grading_changed_after_failure에 기록된다. 실패 당시 해시는 .gatekit/attempts.json에 태스크별로 남고, 통과하면 비교 후 지워진다. --force-retry는 실패 횟수만 지우고 이 해시는 남기므로, 실패 → 테스트 완화 → --force-retry → 통과도 표시된다. status·results에 --all을 주면 모든 잡을 오래된 것부터 보여 주고(--json이면 {"jobs": [...]}), 종료 코드는 가장 최근 잡의 판정을 따른다. 쓰기를 막거나 판정을 바꾸지는 않는 보고다(ADR-0023). clean은 기본적으로 가장 최근 잡을 남기고, --all은 전부 지운다. evaluate는 verify.evaluator(또는 --backend)가 가리키는 백엔드를 평가자로 쓴다. Codex 백엔드는 신뢰된 프로젝트 훅이 있으면 --sandbox workspace-write로(쓰기 게이트가 실제 보호막), 없으면 정확한 해결 명령과 함께 거부한다 — --force-read-only-evaluator는 이 거부 대신 예전처럼 --sandbox read-only로 강행한다(ADR-0015). .gatekit/jobs/<잡>/evaluate/에 기록하고 응답 꼬리(판정표)를 출력한다. 상태가 passed가 아니면 모든 기준이 unverified다.

start는 워커를 띄우기 전에 태스크마다 게이트를 한 번 먼저 돌린다(ADR-0009). 이미 통과하면 워커 없이 passed로 기록하고, 쓰기 범위에 파일이 하나도 없는데 통과했다면 warn을 붙인다(항상 통과하는 게이트일 수 있다). 게이트 명령 자체가 오류이면(종료 코드 126·127, 또는 Cannot find module·No such file or directory 같은 출력이 게이트 인자 중 하나를 직접 가리킬 때) 잡을 시작하지 않고 종료 코드 4로 태스크와 게이트 이름을 알린다. 종료 코드 2 이상이나 인자를 가리키지 않는 비슷한 출력은 의심만 하고 경고를 남긴 채 시작한다. 단, 출력이 가리키는 없는 경로(예: Could not read package.json)를 같은 잡의 어떤 태스크가 write_scope로 만들게 되어 있으면 아직 실행할 수 없을 뿐이므로 경고 없이 시작하고 preflight.json에 그 태스크 이름을 남긴다. bash scripts/e2e.sh·node scripts/e2e.js처럼 셸·인터프리터가 인자로 받은 스크립트가 없어서 127로 끝나도, 그 스크립트를 어떤 태스크가 만들면 마찬가지다(프로그램 자체를 찾지 못한 경우는 여전히 거부). 없는 경로가 게이트 인자에 직접 적혀 있으면 태스크마다 한 줄씩 note:로 그 경로와 만들 태스크를 알린다. 경로에 오타가 있으면 그 태스크가 끝난 뒤에야 드러나기 때문이다(경고가 아니라 안내). 아무 태스크도 만들지 않는 package.json이 없으면 종료 코드 4로 거부하고 메시지에 package.json을 적는다. 실행 자체가 안 되는 프로그램(이름이든 경로든)도 그것을 만드는 태스크가 없으면 거부한다. 단, ./node_modules/.bin/playwright·.venv/bin/pytest처럼 node_modules·.venv·venv 안의 프로그램은 의존성을 아직 설치하지 않았을 뿐이므로 거부하지 않는다. 어떤 태스크가 package.json(또는 pyproject.toml, requirements.txt 등)을 만들면 경고 없이, 아니면 경고를 남기고 시작한다(ADR-0022). 테스트를 하나도 돌리지 않고 통과한 게이트(No tests found, Ran 0 tests, pytest에서 전부 deselect된 경우 등)와 테스트를 전부 건너뛴 게이트(3 skipped, OK (skipped=3), 0 passing 뒤 3 pending 등, ADR-0022 개정 A)는 unverified라서 미리 통과로 처리하지 않는다. --no-preflight는 이 단계를 건너뛴다. 이미 max_retries에 도달한 태스크가 있으면 --force-retry <task_id>로 그 태스크의 연속 실패 카운터(.gatekit/attempts.json)를 초기화하지 않는 한 시작을 거부한다(종료 코드 3, ADR-0014). redelegate는 현재 spec/04-tasks.md에서 태스크를 다시 읽고, 게이트·지시·쓰기 범위가 바뀌었으면 상태 줄에 task re-read … (gates changed)라고 적으며, 같은 카운터를 확인해 소진됐으면 마찬가지로 거부한다. stop은 이 잡이 띄운 워커만 종료하고(pid와 시작 시각을 함께 확인한다) 실행 중·대기 중 태스크를 stopped로 기록한다.

태스크 상태는 queued / running / gating / passed / failed / timeout / redelegated / stopped / blocked다. blocked는 같은 잡 안의 의존 태스크가 passed가 아니어서 실행하지 않은 것이다. stopped와 blocked는 종료 상태이며 완료가 아니다.

종료 코드뜻
0판정이 fail이 아님
1판정이 fail
2인자 오류, 알 수 없는 명령
3재시도 예산 소진 (build.max_retries 초과)

workers

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" workers list [--json] [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" workers check <name> [--probe] [--json]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" workers set-default <name>
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" workers enable <name>
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" workers set-evaluator <agent|name>

check의 판정 기준이다.

판정조건
okPATH에 있고 argv[0] --version이 0으로 종료
unverifiedPATH에는 있으나 버전 프로브가 실패·오류·시간 초과
failPATH에 없거나, 백엔드가 없거나, argv가 유효하지 않음

enable은 argv에 샌드박스 bypass 플래그가 있는데 설정 항목에 "unsafe": true가 없으면 거부한다.

check --probe는 백엔드의 read_only_argv로 한 문장짜리 프롬프트를 실제로 보낸다. 바이너리는 있는데 답을 못 하는 상태(로그인 안 됨, 샌드박스가 자격증명을 가림)를 빌드 전에 잡는 유일한 검사다. 답하면 ok, 0이 아닌 종료 코드면 출력 꼬리와 함께 fail, 시간 초과면 unverified다. /gatekit:build가 잡을 시작하기 전에 이 검사를 돌린다.

종료 코드뜻
0성공, 또는 check가 ok/unverified
1check가 fail
2이름 누락, 없는 백엔드, unsafe 거부, 알 수 없는 명령

ledger

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" ledger show --session <id> [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" ledger init --session <id> [--root PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" ledger set-pipeline <interview|mockup|tasks|gate|build|verify|none> --session <id> [--root PATH]

--session은 필수다. show는 원장 JSON 전체를, init은 생성된 파일 경로를 출력한다. set-pipeline은 active_pipeline을 기록한다. 평소에는 prompt 게이트가 /gatekit:<파이프라인> 호출을 보고 자동으로 기록하므로 손으로 부를 일은 디버깅뿐이다. 다른 파이프라인으로 바뀌면 질문 예산이 초기화된다.

종료 코드뜻
0성공
1해당 세션의 원장 없음
2set-pipeline에 알 수 없는 이름
2인자 오류

install

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" install --host codex [--root PATH] [--dry-run]

프로젝트에 Codex 호스트 층을 생성한다. .codex/hooks.json, .agents/skills/gatekit-<커맨드>/{SKILL.md,command.md}, AGENTS.md의 관리 블록. 원본은 plugin/이며 생성물은 다시 만들 수 있다. 두 번 실행해도 같은 결과다. --dry-run은 쓸 파일 목록만 보여준다.

종료 코드뜻
0성공
2알 수 없는 호스트 (claude는 플러그인으로 설치하므로 여기서 받지 않는다)

migrate

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" migrate [--root PATH] [--to gatekit|gatebound] [--apply] [--json]

gatekit은 스터디 기수가 끝난 뒤 gatebound로 이름이 바뀐다(ADR-0029). 이 서브커맨드는 프로젝트의 상태 디렉터리(.gatekit/ ↔ .gatebound/)를 대상 이름으로 옮긴다. 기본은 미리보기이고 계획만 출력한다. --apply를 줘야 실제로 바꾼다.

  • 디렉터리 이름을 바꾼다. 그 아래에 git이 추적하는 파일이 있으면 git mv를 쓴다.
  • .gitignore에서 옛 디렉터리를 가리키는 줄을 새 이름으로 고친다.
  • .codex/hooks.json이 있으면 Codex 층을, AGENTS.md에 관리 블록만 있으면 그 블록을 다시 만든다.
  • spec/은 읽지도 쓰지도 않는다. 승인은 05-gate.md의 해시에 묶여 있으므로 gatekit-* 펜스는 그대로 두면 된다. 두 접두사 모두 계속 읽힌다.
  • 이미 대상 이름이면 아무것도 하지 않는다. 두 번 실행해도 같다.
  • .gatekit/과 .gatebound/가 둘 다 있으면 거부한다. 먼저 하나를 정리한다(/gatekit:doctor 3번 축이 훅이 어느 쪽을 쓰는지 알려 준다).

--to의 기본값은 지금 이름(gatekit)이라서, 이름이 바뀌기 전에는 아무것도 하지 않는다. --to gatebound로 미리 연습할 수 있다. 옮긴 뒤에도 지금 플러그인은 .gatebound/를 그대로 읽는다.

종료 코드뜻
0성공, 또는 할 일 없음
1두 디렉터리가 다 있어 거부, 또는 적용 실패
2인자 오류

lang

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" lang "감지할 텍스트"

ko 또는 en 한 단어를 출력한다. 언제나 종료 코드 0이다.

bash
python3 "${CLAUDE_PLUGIN_ROOT}/bin/gatekit.py" lang --spec [--root PATH]

텍스트 대신 프로젝트에서 언어를 고르며, 프롬프트 훅과 같은 우선순위를 따른다. 가장 최근에 갱신된 세션 레저(.gatekit/runs/)의 언어를 사용자가 프롬프트로 정했다면(lang_source가 "prompt") 그 output_lang을 출력한다. 아니면 spec/01-prd.md(없거나 신호가 없으면 spec/00-discovery.md)의 산문 앞부분 40줄을 읽고(프런트매터·코드 블록·표 행·인라인 코드는 건너뜀), 스펙으로도 정해지지 않으면 그 레저의 output_lang, 그것도 없으면 en을 출력한다. 명령 파일들은 프롬프트 훅이 이번 턴에 넣은 output_lang= 컨텍스트 줄을 먼저 쓰고, 그 줄이 없을 때만 이 형식을 실행한다(ADR-0026). 명령은 자기 세션 id를 모르므로 가장 최근 레저를 이 세션의 것으로 본다. 한 프로젝트에서 두 세션이 동시에 프롬프트를 받으면 다른 세션의 레저를 읽을 수 있다. 언제나 종료 코드 0이다.