21 KiB
Harness Framework 동작 과정
이 문서는 자연어 요구사항을 받은 뒤 Harness Framework가 계획을 만들고, 독립된 Codex 세션에서 Step을 실행하고, MSVC로 C++ 프로젝트를 검증하는 전체 과정을 설명한다. 설치 및 설정 예시는 Harness 운영 가이드를 참고한다.
1. 핵심 구조
Harness Framework는 다음 세 계층으로 구성된다.
- 계획 계층: 요구사항을 분석하고 사용자가 승인할 실행 가능한 Step으로 변환한다.
- 실행 계층: Step Executor가 Step마다 독립 Codex 세션을 실행하고 상태와 Git 커밋을 관리한다.
- 검증 계층: PreToolUse 훅이 편집 전 정책을 검사하고, Stop 훅이 종료 전 MSVC 빌드와 테스트를 실행한다.
전체 흐름은 다음과 같다.
사용자 요구사항
↓
프로젝트 탐색 및 요구사항 논의
↓
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.mddocs/PRD.mddocs/ARCHITECTURE.mddocs/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에 정의되어 있다.
각 Step은 다음 조건을 만족해야 한다.
- 하나의 모듈 또는 명확한 한 가지 책임만 다룬다.
- 다른 대화 내용을 참조하지 않아도 실행할 수 있도록 자기완결적으로 작성한다.
- 먼저 읽을 문서와 이전 Step의 관련 파일을 명시한다.
- 클래스와 함수 시그니처 수준으로 작업 범위를 설명한다.
- 실제 실행 가능한 빌드·테스트 명령을 Acceptance Criteria로 사용한다.
- 성공, 오류, 사용자 개입 필요 상태의 판정 기준을 적는다.
- 범위 밖 기능과 기존 테스트 회귀를 명시적으로 금지한다.
예시 Step은 다음과 같은 내용을 포함할 수 있다.
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 사이에 남겨둘 수 없다.
Step 0: 실패하는 테스트만 추가하고 종료
Step 1: 제품 코드를 구현해 테스트 통과
실제 red-green 순서는 하나의 Codex 실행 안에서 완료되어야 한다.
테스트 작성
→ 테스트 실패 확인
→ 최소 제품 코드 구현
→ 테스트 성공 확인
→ Step 종료
즉, 테스트가 구현보다 먼저 작성되는 순서는 지키되 각 Step은 최종적으로 green 상태여야 한다.
4. 사용자 승인 후 생성되는 파일
Step 초안을 사용자가 승인한 뒤에만 다음 파일을 생성한다.
phases/
├── index.json
└── add-division/
├── index.json
├── step0.md
├── step1.md
└── ...
4.1 Top-level index
phases/index.json은 여러 task의 상태를 관리한다.
{
"phases": [
{
"dir": "add-division",
"status": "pending"
}
]
}
4.2 Task index
phases/add-division/index.json은 task 내부 Step의 상태를 관리한다.
{
"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 시작
계획 파일을 생성한 뒤 다음 명령으로 실행한다.
python scripts/execute.py add-division
완료된 브랜치를 원격 저장소에 자동 push하려면 --push를 추가한다.
python scripts/execute.py add-division --push
Step Executor는 시작할 때 다음 작업을 수행한다.
- phase 디렉터리와 task index가 존재하는지 검사한다.
- 이전 실행에서
error또는blocked로 끝난 Step이 있는지 검사한다. feat-{phase-name}브랜치를 생성하거나 checkout한다.AGENTS.md와docs/*.md를 guardrail로 읽는다.- task의
created_at이 없으면 기록한다. - 첫 번째
pendingStep부터 순차 실행한다.
AGENTS.md와 모든 docs/*.md 내용은 각 Codex 프롬프트에 직접 삽입된다. 따라서
이 문서들은 참고 자료가 아니라 실제 실행 입력이다. 서로 충돌하거나 placeholder가
남아 있으면 Codex도 그 모순을 입력으로 받는다.
6. Step마다 독립 Codex 세션 실행
Executor는 각 Step을 다음 형태의 독립 프로세스로 실행한다.
codex exec
--json
--sandbox workspace-write
--dangerously-bypass-hook-trust
--cd <repository-root>
-
Codex에 전달하는 프롬프트는 다음 내용의 조합이다.
AGENTS.md와 docs 문서
+ 이전에 완료된 Step의 summary
+ 이전 시도의 오류(재시도인 경우)
+ Executor 공통 작업 규칙
+ 현재 stepN.md
이전 Step의 전체 대화나 Codex 세션은 전달하지 않는다. task index에 기록한
summary만 다음 Step에 누적한다.
Codex 실행 결과의 exit code, stdout, stderr는 다음 파일에 저장한다.
phases/{task-name}/step{N}-output.json
7. 도구 호출 전 PreToolUse 검사
.codex/hooks.json은 shell 및 파일 편집 도구에
PreToolUse 훅을 등록한다. Codex가 실제 명령이나
편집을 수행하기 전에 이 훅이 요청을 검사한다.
7.1 위험 명령 차단
다음 유형의 명령은 요구사항과 관계없이 차단한다.
git reset --hardgit push --force또는--force-with-leaserm -rfRemove-Item -Recurse -Forcermdir /s /qDROP TABLE
위험 패턴이 발견되면 훅은 차단 이유를 stderr로 출력하고 종료 코드 2를 반환한다. 그러면 해당 도구 호출은 실행되지 않는다.
7.2 C++ TDD 검사
apply_patch, Edit, MultiEdit, Write로 다음 C/C++ 확장자의 파일을 편집하려
하면 TDD 정책을 검사한다.
.c .cc .cpp .cxx .h .hpp .hxx
일반 제품 코드를 수정하려면 대응되는 테스트 파일이 먼저 존재해야 한다. 예를 들어
src/calculator.cpp의 기본 대응 테스트 이름은 다음과 같다.
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 훅이 실행된다. Stop 훅은 변경 파일만이 아니라 발견된 C/C++ 프로젝트 전체를 빌드하고 테스트한다.
8.1 저장소 루트와 재진입 방지
Stop 훅은 git rev-parse --show-toplevel로 프로젝트 루트를 결정한다. Git 저장소를
찾을 수 없으면 현재 디렉터리를 사용한다.
빌드나 테스트의 자식 프로세스에는 CODEX_STOP_VALIDATION_ACTIVE=1을 전달한다.
같은 훅이 자식 프로세스에서 다시 진입하면 즉시 성공 처리하여 검증 재귀를 막는다.
8.2 설정 로드
설정 로더는 .harness/config.json을 읽는다.
파일이 없으면 다음 기본값을 사용한다.
version: 1projectType: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 프로젝트 자동 감지
프로젝트 탐색기는 다음 순서로 프로젝트를 선택한다.
projectType: cmake또는projectType: msbuild명시 설정- 루트의
CMakePresets.json - 루트의
CMakeUserPresets.json - 루트의
CMakeLists.txt - 루트의 단일
.sln - 루트의 단일
.vcxproj
자동 감지 결과는 다음처럼 처리한다.
| 저장소 상태 | 결과 |
|---|---|
| CMake metadata가 있음 | CMake 프로젝트 선택 |
하나의 .sln 또는 .vcxproj가 있음 |
MSBuild 프로젝트 선택 |
| 여러 solution/project가 있음 | 설정으로 하나를 지정하라는 오류 |
| C/C++ 파일과 build metadata가 모두 없음 | 검증할 프로젝트가 없으므로 통과 |
| C/C++ 파일은 있지만 build metadata가 없음 | orphan C++ 프로젝트 오류 |
8.4 MSVC 도구 탐색
도구 탐색기는 vswhere.exe로 다음을
확인한다.
- Visual Studio 설치 경로
- Desktop development with C++ workload
MSBuild.exe
CMake 프로젝트에서는 다음 우선순위로 CMake와 CTest를 선택한다.
- PATH에서 발견한 독립
cmake.exe와ctest.exe - Visual Studio에 번들된 CMake와 CTest
따라서 새로 설치한 CMake의 bin 디렉터리가 PATH에 반영되어 있으면 독립 CMake를
우선 사용한다.
9. 빌드 시스템별 검증 계획
9.1 CMake preset 미사용
CMake adapter는 다음 검증 계획을 만든다.
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을 완전히 지정하면 다음 형태로 실행한다.
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는 다음 순서로 실행한다.
MSBuild.exe <solution-or-vcxproj> /m /nologo `
/p:Configuration=<configuration> `
/p:Platform=<platform>
<msbuild.testCommand>
직접 MSBuild 프로젝트는 표준 테스트 탐색 명령이 없으므로
.harness/config.json의 msbuild.testCommand가 반드시 필요하다. 이 값이 없으면
Stop 검증이 실패한다.
10. 명령 실행 안전성과 제한시간
검증 실행기는 다음 안전 규칙을 적용한다.
- 명령을 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을 다음처럼 기록한 경우:
{
"step": 0,
"name": "division-api",
"status": "completed",
"summary": "divide API와 0 나누기 테스트를 추가함"
}
Executor는 completed_at을 기록하고 변경사항을 커밋한 뒤 다음 pending Step을
실행한다.
11.2 Stop 검증 실패
Stop 훅은 Codex hook protocol에 따라 다음 형태의 응답을 출력한다.
{
"continue": false,
"stopReason": "build failed ...",
"systemMessage": "build failed ..."
}
Codex 프로세스에 대한 훅 자체의 종료 코드는 0이지만 continue: false가 Codex의
응답 종료를 막는다. Codex는 같은 세션에서 오류를 확인하고 수정을 계속한다.
11.3 Executor 재시도
Codex 프로세스가 끝났는데 Step 상태가 completed 또는 blocked가 아니면 Executor가
새 Codex 세션으로 재시도한다.
첫 번째 시도 실패
→ 오류를 다음 프롬프트에 삽입
→ 두 번째 독립 Codex 실행
→ 다시 실패하면 세 번째 독립 Codex 실행
→ 세 번째도 실패하면 error 기록 후 종료
즉, 실패 복구에는 두 층이 있다.
- Stop 훅이 같은 Codex 세션에서 수정하도록 요구한다.
- 세션 자체가 성공하지 못하면 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를 분리해 다음 형식으로 커밋한다.
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++ 프로젝트에 맞게 채워 사용하는 템플릿이다. 실행 전 다음을 확인한다.
AGENTS.md의 프로젝트명, toolset, C++ 표준, 테스트 프레임워크, CRITICAL 규칙을 실제 값으로 교체한다.docs/PRD.md,docs/ARCHITECTURE.md,docs/ADR.md의 placeholder와 예시를 실제 프로젝트 정보로 교체한다.- 기본 자동 감지로 충분하지 않을 때만
.harness/config.example.json을 참고해.harness/config.json을 만든다. - CMake 또는 MSBuild metadata와 테스트 실행 방법을 확인한다.
phases/가 없다면 요구사항 논의와 계획 승인을 거쳐 task 파일을 먼저 만든다.- Executor 실행 전에 Git working tree가 깨끗한지 확인한다.
특히 AGENTS.md와 docs/*.md는 각 Codex 실행에 그대로 주입된다. C++ 프로젝트에서
TypeScript 예시나 미완성 placeholder가 남아 있으면 실제 작업 지시와 충돌할 수 있다.