21 KiB
C# .NET 8 Harness 환경 구축 가이드
이 문서는 Windows에서 Harness를 설치하고 C#·.NET 8 제품의 빌드·테스트와 Codex step 실행을 준비하는 절차를 설명한다. 명령은 별도 표시가 없으면 대상 저장소 루트의 PowerShell에서 실행한다.
이 저장소는 범용 템플릿이며 제품명·업무 기능·UI·배포 방식은 미정이다.
tests/fixtures/dotnet/Smoke/는 도구 검증용 프로젝트다. 아래 Example 경로는
제품 설정 예시이며 현재 제품이나 검증 성공을 뜻하지 않는다. 실제 검증 결과는
해당 phase 기록에 남긴다.
1. 필요한 환경
| 구분 | 요구 사항 | 용도 및 확인 기준 |
|---|---|---|
| 운영체제 | Windows | Harness 실행·검증 환경 고정. 제품 배포 OS와 별개다. |
| 셸 | PowerShell | 아래 UTF-8 파일 작성 예제는 PowerShell 7 기준이다. |
| Git | Windows에서 실행 가능한 Git | 저장소 탐색, branch 전환, 자동 commit. --push 시 원격 인증이 필요하다. |
| Python | 3.10 이상, 실제 python 명령 실행 가능 |
실행기·훅은 표준 라이브러리를 사용한다. 자식 훅 환경의 PATH에서도 보여야 한다. |
| Codex CLI | 아래 옵션·훅 호환성 점검을 통과하는 버전 | codex exec, JSON 출력, stdin 프롬프트와 프로젝트 훅을 사용한다. |
| .NET | 루트 global.json을 만족하는 .NET 8 SDK |
runtime만 설치해서는 restore/build/test를 실행할 수 없다. |
| 테스트 | 기본 xUnit v2 + VSTest, NuGet 접근 | 각 명시 test project의 실제 실행과 TRX 결과가 필요하다. |
| 선택 도구 | IDE, Node.js/npm, pytest/uv | IDE는 선택이다. npm은 해당 Codex 설치 방식에, pytest/uv는 Harness 테스트에 사용한다. |
제품 아키텍처나 CPU 대상을 일괄 지정하지 않는다. SDK와 제품 TFM의 역할은 Microsoft global.json 문서를 참고한다.
2. 기본 도구 설치
Git과 Python
- Git for Windows 공식 설치 페이지에서 설치하고
PowerShell과 외부 프로그램에서
git을 실행할 수 있도록 PATH를 설정한다. - Python Windows 공식 배포 페이지에서 Python 3.10 이상을 설치한다. 실행기와 자식 훅이 실제 설치 경로를 찾도록 설정한다.
- 새 PowerShell 창에서 확인한다.
git --version
python --version
python -c "import sys; print(sys.executable); assert sys.version_info >= (3, 10)"
Get-Command git, python | Select-Object Name, Source
python이 Microsoft Store를 열거나 WindowsApps 별칭으로 실행에 실패하면 앱 실행
별칭과 PATH 순서를 확인한다. py나 uv run python만 동작하는 상태는 충분하지
않다. 현재 Windows 훅은 python을 호출한다. 가상환경을 쓰면 활성화한 동일 터미널에서
Harness와 Codex를 실행한다. uv로 Python 테스트가 통과해도 훅 PATH 점검을 대신하지 않는다.
.NET 8 SDK
Microsoft .NET 8 다운로드에서
환경에 맞는 SDK를 설치하고 새 터미널을 연다. 루트의 global.json 기준은 다음과 같다.
{
"sdk": {
"version": "8.0.424",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}
이 SDK 번호는 초기 검증 기준이며 최신 버전이라는 뜻이 아니다. latestPatch는 같은
feature band의 적합한 패치를 선택하며 설치된 SDK 9로 넘어가는 정책이 아니다.
설치 목록과 실제 선택 결과를 구분해 저장소 루트에서 확인한다.
Get-Command dotnet | Select-Object Name, Source
dotnet --list-sdks
dotnet --version
선택 결과는 위 정책을 만족하는 8.0.4xx여야 한다. SDK가 없거나 조건에 맞지 않으면
설치를 복구하거나 검토 후 SDK 기준을 변경한다. Harness는 SDK를 자동 설치하지 않는다.
global.json은 도구 선택이고 .csproj의 TargetFramework는 제품 대상이다.
신규 프로젝트는 net8.0, Nullable=enable, ImplicitUsings=enable을 기본으로 하고
언어 버전은 SDK 기본값을 사용한다.
3. Codex CLI 설치와 호환성 확인
공식 Codex CLI 설치 안내를 따라 설치하고
새 터미널을 연다. npm 방식을 선택하면
Node.js 공식 배포 페이지의 Node.js/npm이 필요하다.
Harness는 Codex 앱 설치 여부와 별개로 codex 명령을 직접 호출한다.
codex --version
codex login
codex login status
codex exec --help
codex login의 안내에 따라 인증한다. Harness 설정에 계정이나 API 키를 넣지 않는다.
현재 실행기가 사용하는 명령 형태는 다음과 같다.
codex exec --json --sandbox workspace-write --dangerously-bypass-hook-trust --cd <repository-root> -
저장소는 최소 Codex 버전을 선언하지 않으므로 exec --help로 해당 옵션을 확인한다.
2026-09-08 로컬 CLI 0.153.4에서 옵션을 확인했지만 이것은 전체 모델 실행이나
자동 훅 차단 검증을 뜻하지 않는다.
실행기는 셸 alias나 PowerShell 함수 대신 Python 자식 프로세스로 CLI를 호출한다. 아래 점검에는 모델 호출이 없다.
python -X utf8 -c "import subprocess; subprocess.run(['codex', '--version'], check=True)"
python -X utf8 -c "import subprocess; subprocess.run(['codex', 'exec', '--help'], check=True)"
터미널의 codex는 되는데 위 명령이 실패하면 Get-Command codex -All과
where.exe codex로 PATH를 확인한다. alias나 npm shim이 자식 프로세스에서 실행되지
않으면 공식 Windows 설치 방식과 실제 실행 파일 경로를 확인한 뒤 다시 점검한다.
4. 저장소 준비와 훅 확인
Git으로 clone하거나 기존 제품 저장소에 다음 구성요소를 가져온다. 기존 AGENTS.md와
훅 설정이 있으면 제품 규칙을 유지하면서 병합한다.
AGENTS.md
global.json
.gitignore
.agents/skills/harness/SKILL.md
.agents/skills/review/SKILL.md
.codex/hooks.json
.harness/config.example.json
scripts/execute.py
scripts/hooks/
scripts/dotnet_harness/
docs/HARNESS.md
docs/HARNESS_WORKFLOW.md
docs/ENVIRONMENT_SETUP.md
docs/phase/README.md
실행기와 훅이 경로를 기준으로 저장소를 찾으므로 배치를 유지한다. 제품 권장 배치는
src/<ProjectName>/와 tests/<ProjectName>.Tests/이며 제품을 미리 만들어 놓았다는
뜻은 아니다. Harness 자체를 개발하려면 tests/harness/, tests/fixtures/와
pytest.ini도 유지한다.
git rev-parse --show-toplevel
git status --short
git config user.name
git config user.email
python -X utf8 scripts/execute.py --help
Git 작성자 정보가 없으면 대상 저장소에서 아래 예시를 자신의 정보로 바꾸어 설정한다.
실제 실행 전 준비한 설정·phase 파일을 검토해 기준 commit을 만들고, 다른 작업의
변경이 없는 전용 checkout/worktree를 사용한다. 실행기는 git add -A와 자동 commit을
수행하므로 초기 HEAD가 없는 상태에서 실제 phase 실행을 시작하지 않는다.
git config --local user.name 'YOUR_NAME'
git config --local user.email 'YOUR_EMAIL'
저장소 루트에서 codex를 실행하고 /hooks로 .codex/hooks.json의 PreToolUse와
Stop이 로드되는지 확인한다. 프로젝트와 변경된 훅의 신뢰, 사용자·관리자 설정을
확인한다. commandWindows는 Windows용 명령 재정의이며 현재 실행기의
--dangerously-bypass-hook-trust는 검토된 자동화에서 활성 훅의 지속 신뢰 요구를
건너뛰는 옵션이다. 비활성 훅을 켜거나 빌드 설정을 대신하지 않는다.
공식 Codex 훅 문서
Windows 훅은 python -X utf8과 Git 루트 탐색을 사용한다. 아래 직접 호출은 스크립트의
입력 처리만 확인하며 Codex가 해당 훅을 자동 호출하는지는 검증하지 않는다.
'{"tool_name":"Edit","tool_input":{"file_path":"docs/ENVIRONMENT_SETUP.md"}}' | python -X utf8 scripts/hooks/pre_tool_use.py
$LASTEXITCODE
문서 편집 허용 기준은 종료 코드 0, 거부 JSON 없음, 입력 해석 오류 없음이다.
PreToolUse 거부는 stdout의 hookSpecificOutput.permissionDecision: "deny"와
permissionDecisionReason으로 판정한다. Windows PowerShell이 자식 프로세스의 종료
코드 2를 1로 바꿀 수 있어 훅은 JSON 응답과 종료 코드 0을 사용한다.
C# Foo.cs 편집에는 기본 루트 tests/ 또는 편집 파일 옆 tests/, test/의
FooTests.cs나 FooTest.cs가 필요하다. 실제 실패 테스트 확인은 별도다. Program.cs는 자동 면제가
아니며 필요한 경우 정확한 tdd.exclude 경로와 이유를 기록한다.
실제 자동 훅 확인은 격리 저장소에서 테스트 없는 C# 편집 요청이 차단되는지, 대응 테스트 작성 후 허용되는지, 잘못된 빌드가 Stop에서 거부되는지를 관측한다. 등록 확인·직접 스크립트 실행·실제 CLI 이벤트 검증 결과를 구분해서 기록한다.
5. 제품 빌드 설정
.harness/config.example.json을 참고해 실제 제품에 맞는 version 2 설정을 작성한다.
JSON은 UTF-8 BOM 없이, 주석 없이 저장한다. PowerShell 7에서는
Set-Content -Encoding utf8NoBOM을 사용할 수 있다. 아래 Example.sln과 경로는
설명용이며 그대로 복사해 완성된 제품 설정으로 사용하지 않는다.
{
"version": 2,
"projectType": "dotnet",
"dotnet": {
"target": "Example.sln",
"configuration": "Debug",
"testProjects": ["tests/Example.Tests/Example.Tests.csproj"]
},
"tdd": {
"testRoots": ["tests"],
"testPatterns": ["{stem}Tests.cs", "{stem}Test.cs"],
"exclude": []
}
}
target은 저장소 내부 .sln 또는 SDK 스타일 .csproj다. projectType는
auto(기본값) 또는 dotnet이다. target 생략 시 루트 단일 solution, 다음으로
제외 경로 밖 단일 project를 선택한다. 여러 후보면 target을 지정한다.
.slnx, 기존 .NET Framework project와 다른 언어 project는 지원하지 않는다.
제품이 있으면 testProjects에 전체 제품 테스트를 비어 있지 않은 목록으로 명시한다.
테스트가 solution에 포함되어 있어도 이 목록이 필요하다. 모든 설정 경로는 저장소
상대 경로이며 외부로 향하는 링크·junction, 없는 target과 알 수 없는 필드는 거부한다.
version 1은 새 설정으로 다시 작성한다. exclude: []는 유효하지만 명시된 test root,
test pattern과 제품의 test project 목록은 비워 둘 수 없다.
신규 테스트 기본값은 xUnit v2 + VSTest다. 테스트 project는 제품 project를 참조하고
xunit, xunit.runner.visualstudio, Microsoft.NET.Test.Sdk의 호환되는 안정 버전을
명시적으로 고정한다. fixture의 실제 버전은
Smoke.Tests.csproj를 참고한다.
fixture의 NuGet.Config는 도구 검증용 feed를 한정한다. 실제 제품에서는 제품의 feed와
인증 설정을 유지하고 restore 오류를 개별적으로 해결한다.
제품에서 테스트 project를 참조하지 않는다. 다른 기존 프레임워크도 VSTest/TRX 계약을
만족해야 한다. Microsoft의 xUnit 구성 예제
아래는 설정 확인 명령이다. JSON 구문 검사와 설정 load는 제품 빌드가 아니다.
python -X utf8 -m json.tool .harness/config.json
python -X utf8 -c "import sys; from pathlib import Path; sys.path.insert(0, 'scripts'); from dotnet_harness.config import load_config; print(load_config(Path.cwd()))"
제품 경로를 실제 값으로 바꾸고 각 명령의 종료 코드가 0인지 확인한다. 실패한 단계가
있으면 다음 단계로 진행하지 않는다. SDK 확인 다음에는 target의 실제 각 프로젝트와
모든 test project와 SDK로 평가한 ProjectReference의 전체 참조 그래프에서 TFM·저장소 경계를 확인한다. 아래 src/Example/Example.csproj도 예시이며
solution에 포함된 실제 프로젝트마다 반복한다. 모든 test project에 restore/build/test를 반복한다.
dotnet --version
dotnet msbuild src/Example/Example.csproj -getProperty:TargetFramework,TargetFrameworks -getItem:ProjectReference -p:Configuration=Debug
dotnet msbuild tests/Example.Tests/Example.Tests.csproj -getProperty:TargetFramework,TargetFrameworks -getItem:ProjectReference -p:Configuration=Debug
dotnet restore Example.sln -p:Configuration=Debug
dotnet restore tests/Example.Tests/Example.Tests.csproj -p:Configuration=Debug
dotnet build Example.sln -c Debug --no-restore -warnaserror
dotnet build tests/Example.Tests/Example.Tests.csproj -c Debug --no-restore -warnaserror
$manualResults = Join-Path '.harness/test-results' ([guid]::NewGuid().ToString('N'))
dotnet test tests/Example.Tests/Example.Tests.csproj -c Debug --no-build --no-restore --logger trx --results-directory $manualResults
TFM 평가와 restore의 Configuration, build/test의 -c는 설정의 같은 값을 사용한다.
평가 결과의 TargetFrameworks 또는 TargetFramework는 모두 .NET 8이어야 한다.
net8.0과 .NET 8의 플랫폼별 TFM을 허용하며 빈 값·비지원 TFM은 restore 전에 거부한다.
상속·조건부 참조도 따라가며 저장소 밖 프로젝트는 거부한다. 참조별 AdditionalProperties나
SetConfiguration 등 별도 속성 전달은 현재 지원하지 않는다. 상세 계약은 운영 가이드를 따른다.
이 검사는 상속된 Directory.Build.props와 속성 참조를 평가하므로 .csproj의 문자만
확인한 결과로 대체하지 않는다.
Harness도 target과 test project별 restore/build를 수행하므로 solution에서 빠진 테스트를
누락하지 않는다. 현재 호출의 고유 결과 경로에서 각 project의 모든 TRX를 확인하며
실행된 테스트가 있어야 한다. exit code 0만으로는 충분하지 않다. 실패·손상·누락,
0건과 skip-only는 실패이며 과거 TRX를 재사용하지 않는다.
VSTest dotnet test 문서
.gitignore의 중첩 bin/, obj/, .vs/, TestResults/와 Harness 결과 제외를
확인한다. 공유할 .harness/config.json과 로컬 파일·산출물 정책을 자동 staging 전에
정한다. tdd.exclude는 테스트 요구 면제만 제어하고 제품 존재 탐색에는 적용되지 않는다.
tests/fixtures/는 도구 fixture로 예약하므로 실제 제품을 그 안에 두지 않는다.
6. 구축 완료 확인과 최초 실행
제품 metadata와 설정을 준비한 뒤 일반 터미널에서 Stop 훅을 직접 실행한다. SDK 선택과 실제 TFM 평가에 이어 restore·build·test와 산출물 작성이 발생한다.
python -X utf8 scripts/hooks/stop_validation.py
스크립트는 실패해도 프로세스 종료 코드가 0일 수 있다. 표준 출력의
"continue": false와 stopReason은 검증 실패다. 제품과 metadata가 없는 템플릿의
제품 검증 대상 없음이나 docs-only 생략을 제품 build 성공으로 기록하지 않는다.
CODEX_STOP_VALIDATION_ACTIVE=1인 재진입 환경에서도 생략될 수 있으므로 일반
터미널의 새 검증 결과와 실제 테스트 실행 건수를 확인한다.
실제 CLI에서도 Stop 실패가 종료 코드 0과 모델의 completed 상태를 남길 수 있다.
따라서 executor는 완료 보고 후 제품 검증을 직접 다시 실행하고, 성공해야만 step을
승인한다. 문서 전용 변경 판정도 이 단계에서 다시 확인하며 실패는 기존 재시도 예산을 사용한다.
실행 흐름과
Harness 스킬에 맞춰 실제 작업의
phases/index.json, phases/<phase>/index.json, stepN.md를 준비한다.
context_files에는 존재하는 파일만 넣는다. phase 기록 형식에 따라
handoff는 약 60줄, step 기록에는 변경·결정·실제 검증·유효한 실패 접근을 남긴다.
실행 상태의 기준은 항상 phase index다.
<phase>를 실제 디렉터리 이름으로 바꾼다.
python -X utf8 scripts/execute.py <phase> --dry-run
python -X utf8 scripts/execute.py <phase>
--dry-run은 첫 pending step의 문맥 경로와 프롬프트 크기만 확인하며 Codex, Git 변경,
인증과 제품 검증을 실행하지 않는다. 실제 실행은 branch 전환, 파일 수정과 자동 commit을
수행한다. 원격 전송이 필요할 때만 --push를 사용한다.
구축 완료 기준은 다음과 같다.
- Python 자식 프로세스가 Codex를 직접 실행하고 필요한 옵션·인증을 사용할 수 있다.
- 실제 CLI 이벤트에서 편집 훅과 Stop 검증이 동작한다.
- 실제 제품 또는 격리 fixture에서 .NET SDK 8 선택, 실제 TFM 평가, restore/build와 테스트가 성공하고 경고·차단 응답이 없으며 현재 TRX에서 실행 건수가 확인된다.
- 실제 phase의 dry-run이 파일·Git·상태를 변경하지 않고, 첫 step 실행 결과와 index가 일치한다.
Harness 자체 검증은 제품 검증과 별도로 실행한다.
uv run --with pytest python -m pytest tests/harness -v -rs
uv run --with pytest python -m compileall -q scripts tests/harness
SDK/NuGet/CLI 문제로 실제 통합 검증이 skip되면 미검증으로 기록한다. subprocess 대역 테스트 통과와 실제 .NET·CLI 통과, 설치된 도구 확인과 새 환경의 설치 검증을 구분한다.
7. 자주 발생하는 문제
| 증상 | 확인 및 조치 |
|---|---|
python 또는 codex를 찾지 못함 |
새 터미널의 PATH와 앱 실행 별칭을 확인한다. Python 자식 프로세스 점검까지 통과해야 한다. |
unexpected argument / unrecognized option |
codex exec --help로 실행기의 옵션 지원을 확인한다. |
| 인증 실패 | Harness를 실행하는 동일 Windows 계정과 Codex 환경에서 로그인 상태를 확인한다. |
| 훅이 로드되지만 차단되지 않음 | /hooks, 프로젝트·훅 신뢰, 사용자·관리자 설정, commandWindows, 자식 PATH와 실제 이벤트·matcher를 확인한다. |
dotnet 누락 / SDK 없음 |
runtime만 설치했는지 확인하고 SDK 설치와 PATH를 복구한다. |
| SDK 선택 오류 | 루트 global.json, dotnet --list-sdks와 루트 dotnet --version을 비교한다. 임의 디렉터리의 SDK 선택으로 대체하지 않는다. |
| 실제 TFM 평가 실패 | 같은 Configuration으로 각 .csproj의 TargetFramework/TargetFrameworks를 조회한다. 상속된 props·속성 참조·조건부 값을 확인하고 빈 값이나 .NET 8 이외의 대상을 수정한다. |
| NuGet restore 실패 | 오류 단계의 feed URL·네트워크·인증·패키지 버전을 확인한다. 성공한 테스트로 기록하지 않는다. |
| 여러 target 후보 | dotnet.target에 실제 .sln 또는 SDK 스타일 .csproj를 명시한다. |
| C# orphan / metadata 없음 | C# 소스에 맞는 지원 metadata와 target을 준비한다. exclude로 제품을 숨기지 않는다. |
| 테스트 0건 / skip-only / TRX 누락 | testProjects, 테스트 SDK·어댑터, 테스트 발견과 실행 조건을 확인한다. 현재 project별 결과를 확인한다. |
| build 경고로 실패 | nullable·분석기 등 실제 진단을 수정한다. 경고를 숨겨 통과시키지 않는다. |
TDD GUARD 차단 |
대응 테스트를 먼저 추가하고 red 단계 확인 후 구현한다. Program.cs도 기본 검사 대상이다. |
dubious ownership |
현재 계정 소유 checkout을 우선한다. 신뢰하는 공유 경로라면 해당 경로만 Git safe.directory에 등록한다. |
소스 탐색 중 PermissionError / 접근 거부 |
Windows sandbox에서 생성한 폴더의 ACL과 훅 실행 계정을 확인한다. 일반 실행 계정이 소스·테스트 폴더를 읽을 수 있어야 한다. 필요하면 해당 계정으로 프로젝트 폴더를 먼저 준비한다. 읽기 실패는 대상 없는 템플릿으로 처리하지 않는다. |
| phase 또는 context 파일 누락 | 실제 phase/index/step과 존재하는 참조 파일을 준비한다. 인계 문서를 상태 파일로 사용하지 않는다. |
| build·Codex 시간 초과 | Stop 전체와 각 Codex 호출 제한은 1,800초다. 로그의 실패 단계·argv·cwd를 확인한다. |
재시도, error/blocked 복구는 Harness 운영 가이드와
실행 흐름을 따른다.