# Harness Framework 동작 과정

이 문서는 자연어 요구사항을 받은 뒤 Harness Framework가 계획을 만들고, 독립된
Codex 세션에서 Step을 실행하고, MSVC로 C++ 프로젝트를 검증하는 전체 과정을
설명한다. 설치 및 설정 예시는 [Harness 운영 가이드](HARNESS.md)를 참고한다.

## 1. 핵심 구조

Harness Framework는 다음 세 계층으로 구성된다.

1. **계획 계층**: 요구사항을 분석하고 사용자가 승인할 실행 가능한 Step으로 변환한다.
2. **실행 계층**: Step Executor가 Step마다 독립 Codex 세션을 실행하고 상태와 Git
   커밋을 관리한다.
3. **검증 계층**: PreToolUse 훅이 편집 전 정책을 검사하고, Stop 훅이 종료 전 MSVC
   빌드와 테스트를 실행한다.

전체 흐름은 다음과 같다.

```text
사용자 요구사항
    ↓
프로젝트 탐색 및 요구사항 논의
    ↓
Step 초안 작성
    ↓
사용자 승인
    ↓
phases/index.json, task index, stepN.md 생성
    ↓
Step Executor 시작
    ↓
각 Step을 독립 Codex 세션에서 실행
    ├─ 도구 호출 전: PreToolUse 정책 검사
    └─ 응답 종료 전: Stop MSVC 빌드·테스트
    ↓
성공: 커밋 후 다음 Step
실패: 수정 또는 최대 3회 재시도
차단: 사용자 개입을 기다리며 중단
```

자연어 요구사항만으로 Executor가 자동 시작되지는 않는다. 계획을 사용자가 승인하고
phase 파일을 생성한 다음 `scripts/execute.py`를 실행해야 구현 루프가 시작된다.

## 2. 요구사항 탐색과 구체화

예를 들어 사용자가 다음 요구사항을 전달했다고 가정한다.

> CMake 기반 C++20 라이브러리에 `divide()` 함수를 추가하고, 0으로 나누면 예외를
> 발생시키며 GoogleTest 테스트를 작성한다.

계획을 작성하기 전에 다음 자료를 확인한다.

- `AGENTS.md`
- `docs/PRD.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- 관련 제품 코드와 테스트
- `.harness/config.json`

이 탐색을 통해 다음 조건을 구체화한다.

- 사용하는 MSVC toolset과 C++ 표준
- CMake 프로젝트인지 Visual Studio solution/project인지
- 테스트 프레임워크와 테스트 실행 방법
- public header와 implementation의 의존성 방향
- 수정할 모듈과 범위 밖 항목
- 실행 가능한 Acceptance Criteria 명령

요구사항에 결정되지 않은 부분이 있으면 구현 전에 사용자와 논의한다. 위 예에서는
예외 타입, 정수 또는 부동소수점 연산 여부, public API와 ABI 변경 허용 여부가 이에
해당한다.

## 3. 요구사항을 Step으로 분해

사용자가 구현 계획 작성을 요청하면 요구사항을 작은 Step으로 나눈다. Step 설계
규칙은 [Harness Workflow](../.agents/skills/harness/SKILL.md)에 정의되어 있다.

각 Step은 다음 조건을 만족해야 한다.

- 하나의 모듈 또는 명확한 한 가지 책임만 다룬다.
- 다른 대화 내용을 참조하지 않아도 실행할 수 있도록 자기완결적으로 작성한다.
- 먼저 읽을 문서와 이전 Step의 관련 파일을 명시한다.
- 클래스와 함수 시그니처 수준으로 작업 범위를 설명한다.
- 실제 실행 가능한 빌드·테스트 명령을 Acceptance Criteria로 사용한다.
- 성공, 오류, 사용자 개입 필요 상태의 판정 기준을 적는다.
- 범위 밖 기능과 기존 테스트 회귀를 명시적으로 금지한다.

예시 Step은 다음과 같은 내용을 포함할 수 있다.

```text
Step 0: division-api

읽어야 할 파일
- AGENTS.md
- include/calculator.hpp
- src/calculator.cpp
- tests/calculator_test.cpp

작업
- divide(double lhs, double rhs)의 실패 테스트를 먼저 추가한다.
- rhs가 0이면 std::invalid_argument가 발생하도록 최소 구현한다.

Acceptance Criteria
- CMake/MSBuild 빌드가 성공한다.
- 전체 테스트가 성공한다.
- 새로운 컴파일러 경고가 없다.
```

### TDD와 Step 경계

프로젝트 규칙은 실패하는 테스트를 먼저 요구하지만 Stop 훅은 Codex가 Step을 종료할
때 전체 테스트 성공을 요구한다. 따라서 다음처럼 실패 상태를 Step 사이에 남겨둘 수
없다.

```text
Step 0: 실패하는 테스트만 추가하고 종료
Step 1: 제품 코드를 구현해 테스트 통과
```

실제 red-green 순서는 하나의 Codex 실행 안에서 완료되어야 한다.

```text
테스트 작성
→ 테스트 실패 확인
→ 최소 제품 코드 구현
→ 테스트 성공 확인
→ Step 종료
```

즉, 테스트가 구현보다 먼저 작성되는 순서는 지키되 각 Step은 최종적으로 green
상태여야 한다.

## 4. 사용자 승인 후 생성되는 파일

Step 초안을 사용자가 승인한 뒤에만 다음 파일을 생성한다.

```text
phases/
├── index.json
└── add-division/
    ├── index.json
    ├── step0.md
    ├── step1.md
    └── ...
```

### 4.1 Top-level index

`phases/index.json`은 여러 task의 상태를 관리한다.

```json
{
  "phases": [
    {
      "dir": "add-division",
      "status": "pending"
    }
  ]
}
```

### 4.2 Task index

`phases/add-division/index.json`은 task 내부 Step의 상태를 관리한다.

```json
{
  "project": "Calculator",
  "phase": "add-division",
  "steps": [
    {
      "step": 0,
      "name": "division-api",
      "status": "pending"
    }
  ]
}
```

상태별 기록은 다음과 같이 나뉜다.

| 상태 | Codex가 기록 | Executor가 기록 |
|---|---|---|
| `completed` | `summary` | `completed_at` |
| `error` | `error_message` | `failed_at` |
| `blocked` | `blocked_reason` | `blocked_at` |

Executor는 task의 `created_at`과 Step의 `started_at`도 기록한다. `summary`는 다음
독립 Codex 세션이 이전 Step의 핵심 산출물과 결정을 이해할 수 있도록 한 줄로
작성한다.

### 4.3 Step 파일

각 `stepN.md`에는 다음 내용이 들어간다.

- 읽어야 할 파일
- 작업 범위와 인터페이스
- 핵심 동작 및 불변 조건
- Acceptance Criteria 명령
- 아키텍처·ADR·CRITICAL 규칙 확인 절차
- 성공, 오류, 차단 상태 기록 방법
- 범위 밖 변경 금지사항

Step은 독립 Codex 실행의 전체 작업 지시서이므로 이전 대화만 참조하는 표현을 넣지
않는다.

## 5. Step Executor 시작

계획 파일을 생성한 뒤 다음 명령으로 실행한다.

```powershell
python scripts/execute.py add-division
```

완료된 브랜치를 원격 저장소에 자동 push하려면 `--push`를 추가한다.

```powershell
python scripts/execute.py add-division --push
```

[Step Executor](../scripts/execute.py)는 시작할 때 다음 작업을 수행한다.

1. phase 디렉터리와 task index가 존재하는지 검사한다.
2. 이전 실행에서 `error` 또는 `blocked`로 끝난 Step이 있는지 검사한다.
3. `feat-{phase-name}` 브랜치를 생성하거나 checkout한다.
4. `AGENTS.md`와 `docs/*.md`를 guardrail로 읽는다.
5. task의 `created_at`이 없으면 기록한다.
6. 첫 번째 `pending` Step부터 순차 실행한다.

`AGENTS.md`와 모든 `docs/*.md` 내용은 각 Codex 프롬프트에 직접 삽입된다. 따라서
이 문서들은 참고 자료가 아니라 실제 실행 입력이다. 서로 충돌하거나 placeholder가
남아 있으면 Codex도 그 모순을 입력으로 받는다.

## 6. Step마다 독립 Codex 세션 실행

Executor는 각 Step을 다음 형태의 독립 프로세스로 실행한다.

```text
codex exec
  --json
  --sandbox <workspace-write|danger-full-access>
  --dangerously-bypass-hook-trust
  --cd <repository-root>
  -
```

기본값은 `workspace-write`다. `FESA_HARNESS_CODEX_SANDBOX` 환경 변수는
`workspace-write` 또는 `danger-full-access`만 허용한다. Windows native sandbox에서
MSVC compiler-id의 `cl.exe` 정지가 재현되고 동일 명령이 sandbox 밖에서 통과하는
환경에서는, 사용자 승인을 받은 격리된 clean worktree 실행에 한해서
`danger-full-access` fallback을 사용할 수 있다. 이 override는 hook trust 또는 hook
등록을 끄지 않으며 PreToolUse와 Stop 검증은 동일하게 실행된다.

Codex에 전달하는 프롬프트는 다음 내용의 조합이다.

```text
AGENTS.md와 docs 문서
+ 이전에 완료된 Step의 summary
+ 이전 시도의 오류(재시도인 경우)
+ Executor 공통 작업 규칙
+ 현재 stepN.md
```

이전 Step의 전체 대화나 Codex 세션은 전달하지 않는다. task index에 기록한
`summary`만 다음 Step에 누적한다.

Codex 실행 결과의 exit code, stdout, stderr는 다음 파일에 저장한다.

```text
phases/{task-name}/step{N}-output.json
```

## 7. 도구 호출 전 PreToolUse 검사

[`.codex/hooks.json`](../.codex/hooks.json)은 shell 및 파일 편집 도구에
[PreToolUse 훅](../scripts/hooks/pre_tool_use.py)을 등록한다. Codex가 실제 명령이나
편집을 수행하기 전에 이 훅이 요청을 검사한다.

### 7.1 위험 명령 차단

다음 유형의 명령은 요구사항과 관계없이 차단한다.

- `git reset --hard`
- `git push --force` 또는 `--force-with-lease`
- `rm -rf`
- `Remove-Item -Recurse -Force`
- `rmdir /s /q`
- `DROP TABLE`

위험 패턴이 발견되면 훅은 차단 이유를 stderr로 출력하고 종료 코드 2를 반환한다.
그러면 해당 도구 호출은 실행되지 않는다.

### 7.2 C++ TDD 검사

`apply_patch`, `Edit`, `MultiEdit`, `Write`로 다음 C/C++ 확장자의 파일을 편집하려
하면 [TDD 정책](../scripts/msvc_harness/tdd_policy.py)을 검사한다.

```text
.c .cc .cpp .cxx .h .hpp .hxx
```

일반 제품 코드를 수정하려면 대응되는 테스트 파일이 먼저 존재해야 한다. 예를 들어
`src/calculator.cpp`의 기본 대응 테스트 이름은 다음과 같다.

```text
calculator_test.cpp
calculator_tests.cpp
test_calculator.cpp
calculator.test.cpp
```

테스트는 다음 위치에서 검색한다.

- `.harness/config.json`의 `tdd.testRoots`
- 제품 파일과 같은 디렉터리 아래 `tests/`
- 제품 파일과 같은 디렉터리 아래 `test/`

다음 파일과 디렉터리는 대응 테스트 존재 검사가 면제된다.

- 테스트 파일 자체
- `main.cpp`
- `.harness/build/**`, `build/**`, `out/**`
- `cmake-build-*/**`
- `third_party/**`, `external/**`, `vendor/**`
- `generated/**`
- `tdd.exclude`에 추가한 경로

`tdd.exclude`는 기본 제외 항목을 대체하지 않고 추가한다.

### 7.3 TDD 검사가 보장하는 범위

현재 TDD 훅이 직접 보장하는 것은 대응되는 이름의 테스트 파일이 존재한다는
사실이다. 다음 항목까지 증명하지는 않는다.

- 테스트가 이번 요구사항을 실제로 검증하는가
- 구현 전에 테스트가 실제로 실패했는가
- 테스트의 assertion과 경계 조건이 충분한가
- 기존 테스트 파일을 이번 변경과 함께 수정했는가

또한 shell 명령의 리다이렉션 등으로 C++ 파일을 쓰는 경우 shell 위험 패턴 검사는
적용되지만 경로 기반 TDD 검사는 적용되지 않는다. 따라서 이 훅은 완전한 TDD
증명기가 아니라 테스트 우선 편집을 유도하는 guardrail이다.

## 8. Codex 종료 전 Stop 검증

Codex가 Step 작업을 끝내고 응답을 종료하려 하면
[Stop 훅](../scripts/hooks/stop_validation.py)이 실행된다. Stop 훅은 변경 파일만이
아니라 발견된 C/C++ 프로젝트 전체를 빌드하고 테스트한다.

### 8.1 저장소 루트와 재진입 방지

Stop 훅은 `git rev-parse --show-toplevel`로 프로젝트 루트를 결정한다. Git 저장소를
찾을 수 없으면 현재 디렉터리를 사용한다.

빌드나 테스트의 자식 프로세스에는 `CODEX_STOP_VALIDATION_ACTIVE=1`을 전달한다.
같은 훅이 자식 프로세스에서 다시 진입하면 즉시 성공 처리하여 검증 재귀를 막는다.

### 8.2 설정 로드

[설정 로더](../scripts/msvc_harness/config.py)는 `.harness/config.json`을 읽는다.
파일이 없으면 다음 기본값을 사용한다.

- `version`: 1
- `projectType`: `auto`
- CMake source: 저장소 루트
- preset 미사용 시 binary directory: `.harness/build`
- configuration: `Debug`
- platform: `x64`
- 테스트 루트: `tests`, `test`
- 기본 테스트 이름 패턴 네 개

설정은 다음 조건을 엄격하게 검사한다.

- 알 수 없는 필드를 거부한다.
- `version`은 숫자 1만 허용한다.
- `projectType`은 `auto`, `cmake`, `msbuild`만 허용한다.
- 저장소 상대 경로만 허용한다.
- 저장소 밖으로 해석되는 경로를 거부한다.
- CMake preset을 사용하면 `configurePreset`, `buildPreset`, `testPreset`,
  `binaryDir`를 모두 요구한다.
- 모든 `tdd.testPatterns`에 `{stem}`을 요구한다.

### 8.3 프로젝트 자동 감지

[프로젝트 탐색기](../scripts/msvc_harness/discovery.py)는 다음 순서로 프로젝트를
선택한다.

1. `projectType: cmake` 또는 `projectType: msbuild` 명시 설정
2. 루트의 `CMakePresets.json`
3. 루트의 `CMakeUserPresets.json`
4. 루트의 `CMakeLists.txt`
5. 루트의 단일 `.sln`
6. 루트의 단일 `.vcxproj`

자동 감지 결과는 다음처럼 처리한다.

| 저장소 상태 | 결과 |
|---|---|
| CMake metadata가 있음 | CMake 프로젝트 선택 |
| 하나의 `.sln` 또는 `.vcxproj`가 있음 | MSBuild 프로젝트 선택 |
| 여러 solution/project가 있음 | 설정으로 하나를 지정하라는 오류 |
| C/C++ 파일과 build metadata가 모두 없음 | 검증할 프로젝트가 없으므로 통과 |
| C/C++ 파일은 있지만 build metadata가 없음 | orphan C++ 프로젝트 오류 |

### 8.4 MSVC 도구 탐색

[도구 탐색기](../scripts/msvc_harness/toolchain.py)는 `vswhere.exe`로 다음을
확인한다.

- Visual Studio 설치 경로
- Desktop development with C++ workload
- `MSBuild.exe`

CMake 프로젝트에서는 다음 우선순위로 CMake와 CTest를 선택한다.

1. PATH에서 발견한 독립 `cmake.exe`와 `ctest.exe`
2. Visual Studio에 번들된 CMake와 CTest

따라서 새로 설치한 CMake의 `bin` 디렉터리가 PATH에 반영되어 있으면 독립 CMake를
우선 사용한다.

## 9. 빌드 시스템별 검증 계획

### 9.1 CMake preset 미사용

[CMake adapter](../scripts/msvc_harness/adapters/cmake.py)는 다음 검증 계획을 만든다.

```powershell
cmake -S <source> -B .harness/build -A x64
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```

명령 성공 외에 다음 결과도 검사한다.

- 생성된 CMake compiler metadata의 `CMAKE_CXX_COMPILER_ID`가 `MSVC`인가
- CTest JSON에 한 개 이상의 테스트가 있는가

따라서 빌드가 성공해도 MinGW 등 다른 컴파일러를 사용했거나 CTest가 테스트를 한
개도 발견하지 못하면 실패한다.

### 9.2 CMake preset 사용

`.harness/config.json`에 preset을 완전히 지정하면 다음 형태로 실행한다.

```powershell
cmake --preset <configurePreset>
cmake --build --preset <buildPreset>
ctest --preset <testPreset> --show-only=json-v1
ctest --preset <testPreset> --output-on-failure
```

이 경우 모든 명령은 `cmake.sourceDir`에서 실행하고 compiler metadata 검사는 설정한
`binaryDir`에서 수행한다.

### 9.3 직접 MSBuild

[MSBuild adapter](../scripts/msvc_harness/adapters/msbuild.py)는 다음 순서로 실행한다.

```powershell
MSBuild.exe <solution-or-vcxproj> /m /nologo `
  /p:Configuration=<configuration> `
  /p:Platform=<platform>

<msbuild.testCommand>
```

직접 MSBuild 프로젝트는 표준 테스트 탐색 명령이 없으므로
`.harness/config.json`의 `msbuild.testCommand`가 반드시 필요하다. 이 값이 없으면
Stop 검증이 실패한다.

## 10. 명령 실행 안전성과 제한시간

[검증 실행기](../scripts/msvc_harness/process.py)는 다음 안전 규칙을 적용한다.

- 명령을 shell 문자열이 아닌 argv 배열로 실행한다.
- `shell=False`를 사용한다.
- 각 명령의 working directory가 저장소 내부인지 검사한다.
- 명령별 제한시간과 Stop 전체 제한시간 중 더 짧은 값을 적용한다.
- 종료 코드가 0이 아니면 즉시 해당 stage를 실패 처리한다.

Stop 훅의 전체 제한시간은 저장소 탐색, toolchain 탐색, configure, build, test discovery,
test를 모두 포함해 1,800초다. `.codex/hooks.json`의 Stop command timeout도 1,800초다.

실패 메시지에는 다음 진단 정보를 포함한다.

- 실패 stage
- 안전하게 표현한 argv 배열
- working directory
- 종료 코드
- stdout과 stderr의 마지막 8,000자

Harness는 별도 빌드 로그 파일을 생성하지 않는다.

## 11. 성공, 실패, 차단 처리

### 11.1 Stop 검증 성공

빌드와 테스트가 모두 성공하면 Stop 훅은 출력 없이 종료한다. Codex가 정상 종료하면
Executor가 task index를 다시 읽는다.

Codex가 Step을 다음처럼 기록한 경우:

```json
{
  "step": 0,
  "name": "division-api",
  "status": "completed",
  "summary": "divide API와 0 나누기 테스트를 추가함"
}
```

Executor는 `completed_at`을 기록하고 변경사항을 커밋한 뒤 다음 `pending` Step을
실행한다.

### 11.2 Stop 검증 실패

Stop 훅은 Codex hook protocol에 따라 다음 형태의 응답을 출력한다.

```json
{
  "continue": false,
  "stopReason": "build failed ...",
  "systemMessage": "build failed ..."
}
```

Codex 프로세스에 대한 훅 자체의 종료 코드는 0이지만 `continue: false`가 Codex의
응답 종료를 막는다. Codex는 같은 세션에서 오류를 확인하고 수정을 계속한다.

### 11.3 Executor 재시도

Codex 프로세스가 끝났는데 Step 상태가 `completed` 또는 `blocked`가 아니면 Executor가
새 Codex 세션으로 재시도한다.

```text
첫 번째 시도 실패
→ 오류를 다음 프롬프트에 삽입
→ 두 번째 독립 Codex 실행
→ 다시 실패하면 세 번째 독립 Codex 실행
→ 세 번째도 실패하면 error 기록 후 종료
```

즉, 실패 복구에는 두 층이 있다.

1. Stop 훅이 같은 Codex 세션에서 수정하도록 요구한다.
2. 세션 자체가 성공하지 못하면 Executor가 새 세션으로 최대 3회 재시도한다.

### 11.4 사용자 개입 필요

인증, API 키, 수동 설치처럼 Codex가 자동으로 해결할 수 없는 문제가 있으면 Step을
`blocked`로 기록한다. Executor는 `blocked_at`과 top-level 상태를 갱신하고 종료 코드
2로 중단한다.

재개하려면 원인을 해결하고 해당 Step을 `pending`으로 되돌린 뒤
`blocked_reason`을 제거하고 다시 실행한다. `error`도 같은 방식으로 `pending`으로
되돌리고 `error_message`를 제거한 뒤 재실행한다.

## 12. Git 커밋과 phase 완료

Step이 성공하면 제품 변경과 Harness metadata를 분리해 다음 형식으로 커밋한다.

```text
feat(add-division): step 0 — division-api
chore(add-division): step 0 output
```

두 번째 커밋의 `output`은 task index의 Step 상태와 summary 같은 Harness metadata를
뜻한다. 원시 Codex 실행 기록인 `stepN-output.json`은 `.gitignore` 대상이며 커밋에
포함되지 않는다.

모든 Step이 완료되면 Executor는 다음 작업을 수행한다.

- task의 `completed_at` 기록
- `phases/index.json`의 task 상태를 `completed`로 변경
- 최종 metadata 커밋
- `--push` 사용 시 `origin/feat-{phase-name}`으로 push

커밋 과정은 `git add -A`를 사용한다. 실행 전에 작업 트리에 관련 없는 사용자
변경사항이 남아 있으면 그 변경도 Step 커밋에 포함될 수 있다. 따라서 깨끗한
worktree 또는 별도 Git worktree에서 실행하는 것이 안전하다.

## 13. 요구사항 종류별 동작

| 받은 요구사항 또는 변경 | PreToolUse 동작 | Stop 동작 |
|---|---|---|
| 새 C++ 제품 파일 추가 | 대응 테스트가 먼저 없으면 차단 | 전체 빌드·테스트 |
| 기존 C++ 구현 또는 header 수정 | 대응 테스트 파일 존재 여부 검사 | 전체 빌드·테스트 |
| 테스트 파일 추가 | TDD 차단 없이 허용 | 모든 테스트가 성공해야 종료 |
| `main.cpp` 수정 | TDD 대응 테스트 검사 면제 | 전체 빌드·테스트 |
| 문서, JSON, Python 수정 | C++ TDD 검사 없음 | C++ 프로젝트가 있으면 전체 검증 |
| 위험한 Git 또는 삭제 명령 | 즉시 차단 | 도달하지 않음 |
| C++ 파일은 있지만 build metadata 없음 | 편집은 허용될 수 있음 | orphan 프로젝트 오류 |
| 직접 MSBuild인데 `testCommand` 없음 | 편집은 허용될 수 있음 | 설정 오류로 종료 차단 |
| C/C++가 전혀 없는 저장소 | 관련 편집 검사 없음 | 검증할 프로젝트가 없어 통과 |

## 14. 적용 전 준비사항

이 저장소는 대상 C++ 프로젝트에 맞게 채워 사용하는 템플릿이다. 실행 전 다음을
확인한다.

1. `AGENTS.md`의 프로젝트명, toolset, C++ 표준, 테스트 프레임워크, CRITICAL 규칙을
   실제 값으로 교체한다.
2. `docs/PRD.md`, `docs/ARCHITECTURE.md`, `docs/ADR.md`의 placeholder와 예시를 실제
   프로젝트 정보로 교체한다.
3. 기본 자동 감지로 충분하지 않을 때만 `.harness/config.example.json`을 참고해
   `.harness/config.json`을 만든다.
4. CMake 또는 MSBuild metadata와 테스트 실행 방법을 확인한다.
5. `phases/`가 없다면 요구사항 논의와 계획 승인을 거쳐 task 파일을 먼저 만든다.
6. Executor 실행 전에 Git working tree가 깨끗한지 확인한다.

특히 `AGENTS.md`와 `docs/*.md`는 각 Codex 실행에 그대로 주입된다. C++ 프로젝트에서
TypeScript 예시나 미완성 placeholder가 남아 있으면 실제 작업 지시와 충돌할 수 있다.
