# taskctl — main 진입 단일화 + 상태 enforcement layer

> **회장 절대 기준**: "taskctl을 거치지 않고는 main을 절대 변경할 수 없다."

`scripts/taskctl.py`는 task 라이프사이클을 강제하는 단일 진입점이다. 봇/사람/CI 모두 이 명령을 통해서만 main에 변경을 반영할 수 있다.

## 명령 목록

| 명령 | 전이 | 설명 |
|---|---|---|
| `taskctl init <id>` | → CREATED | 신규 task state 파일 생성 |
| `taskctl dispatch <id>` | CREATED → DISPATCHED | dispatch 발사 시점 |
| `taskctl ack <id>` | DISPATCHED → ACKED | 봇 수신 확인 |
| `taskctl run <id>` | ACKED → RUNNING | 실행 시작 |
| `taskctl pr-open <id> --pr <n>` | RUNNING → PR_OPEN | PR 생성 후 |
| `taskctl verify <id>` | PR_OPEN → GUARD_PASS | evidence 자동 수집 + guard.sh + qc_report_guard 실행. 한 가지라도 FAIL이면 PR_OPEN 유지 + evidence 기록. |
| `taskctl approve <id>` | GUARD_PASS → HUMAN_APPROVED | 회장 승인 |
| `taskctl merge <id>` | HUMAN_APPROVED → MERGED → DONE | main 진입 단일 경로 |
| `taskctl cancel <id>` | * → CANCELLED | 취소 |
| `taskctl fail <id> --reason <s>` | * → FAILED | 실패 처리 |
| `taskctl status <id>` | (조회) | 현재 상태 + evidence 출력. `--machine`으로 JSON 출력 |

## 상태 다이어그램

```
        ┌──────────────────── cancel / fail (terminal 외 어디서든) ──────────────┐
        │                                                                       ▼
   CREATED ──► DISPATCHED ──► ACKED ──► RUNNING ──► PR_OPEN ──► GUARD_PASS    CANCELLED
                                                       ▲             │         FAILED
                                                       │             ▼
                                                  (re-verify)   HUMAN_APPROVED
                                                                     │
                                                                     ▼
                                                                  MERGED ──► DONE
```

- 정규 forward 전이만 허용. 비정상 전이 (예: CREATED → MERGED) 시 exit 1.
- terminal 상태 (DONE / CANCELLED / FAILED) 진입 후에는 그 어떤 전이도 불가.
- state 파일은 SHA256 checksum으로 무결성 검증. 외부에서 직접 수정하면 다음 명령에서 reject.

## 저장 위치

- state 파일: `.tasks/state/<task-id>.json`
- 한 task당 정확히 하나의 파일. transitions[] 에 모든 전이가 append-only로 누적.

## evidence 자동 수집 (taskctl verify)

verify는 다음을 모두 실행하고 state.evidence에 기록한다:

- `git diff origin/main..HEAD --name-only` (sha + 변경 경로)
- `git rev-parse HEAD` (현재 commit)
- `git rev-parse --abbrev-ref HEAD` (브랜치명)
- `git status --porcelain` (uncommitted lines 수)
- `gh pr view <n> --json state,mergeable,mergeStateStatus,statusCheckRollup`
- 8 required CI checks 매핑: `cancel-kill-switch`, `qc-check`, `hidden-path-audit`, `lock-in-check`, `merge-safety-check`, `gemini-review-gate`, `ci/guard`, `guard`
- `bash scripts/guard.sh pre-push <task-id>` 결과 + exit code
- `python3 scripts/qc_report_guard.py --task-id <task-id>` 결과 + exit code

모두 PASS 시에만 GUARD_PASS 전이.

## merge 단일 경로 (taskctl merge)

내부 순서 (모든 단계 PASS 시에만 다음 진행):

1. state == HUMAN_APPROVED 검사 → FAIL 시 exit 1
2. state ≠ CANCELLED 재검사
3. `bash scripts/guard.sh pre-push <id>` 재실행 → exit 0 강제
4. `python3 scripts/qc_report_guard.py <id>` 재실행 → exit 0 강제
5. `gh pr view <n>` → mergeable=MERGEABLE + 8 required checks SUCCESS 강제
6. `gh pr merge <n> --merge --delete-branch` 호출 (TASKCTL_INVOKED=1 환경변수와 함께 — 이것이 본 코드베이스에서 유일하게 허용된 머지 호출 지점)
7. MERGED → DONE 전이
8. evidence.merge_timestamp 기록

`--dry-run` 옵션: 6단계의 실제 `gh pr merge` 호출만 skip. 1~5단계 검증은 모두 수행.

## ★★★ BYPASS (회장 전용)

`TASKCTL_BYPASS=1` 환경변수 설정 시:
- 1~5단계 검증 모두 skip
- 상태 전이 검증도 skip (force 전이)
- evidence에 강제 기록:
  ```json
  {"bypass": {"used": true, "ts": "2026-05-05T...", "actor": "user <email>"}}
  ```
- stderr에 `★★★ TASKCTL BYPASS USED — Chairman override` 출력

봇이 사용 시도해도 동일하게 동작 (MVP 신뢰 모델 — 봇 차단은 별도 정책).

## 설치 (git pre-push hook)

```bash
git config core.hooksPath scripts/git-hooks
chmod +x scripts/git-hooks/pre-push
```

설치 후 `git push origin main` 직접 시도는 hook에서 즉시 거부 (exit 1).
다른 브랜치에서 `refs/heads/main` 으로 push 시도해도 refspec 검사에서 차단.

## CI guard

`.github/workflows/guard.yml` (별도 workflow — `ci.yml` 보호):

- `pull_request` / `push` 이벤트마다 task-id 추출 → taskctl status 조회
- state == CANCELLED 시 CI FAIL
- guard.sh / qc_report_guard 재실행

기존 8 required CI checks (`ci.yml`)는 절대 수정하지 않으며, ruleset 등록은 회장이 직접 처리.

## 종료 코드

| 코드 | 의미 |
|---|---|
| 0 | 정상 (PASS / 정상 전이) |
| 1 | FAIL (가드 차단, 잘못된 전이, evidence 미달) |
| 2 | internal error (subprocess timeout 등) |
