modify harness framework

This commit is contained in:
김경종
2026-09-08 10:30:07 +09:00
parent 1239d5b9f6
commit 1a0f3c9aaf
68 changed files with 6729 additions and 957 deletions
@@ -0,0 +1,56 @@
# Architecture Decision Records
제품명, 사용자, 업무 기능, UI와 배포 구조는 미정이다. 아래 결정은 제품 기술 경계와
신규 프로젝트 기본값을 정하며 구체적인 도메인 설계를 대신하지 않는다.
## ADR-001: MSVC 기반 C++ 제품
- **상태:** 대체됨 (superseded by ADR-003)
- **이전 결정:** 대상 제품을 MSVC 기반 C++로 개발한다.
- **대체 이유:** 사용자가 C# 전용 .NET 8 템플릿 전환을 승인했다.
- **결과:** 이전 C++·MSVC 지원과 호환 계층을 유지하지 않는다.
## ADR-002: public header 경계
- **상태:** 대체됨 (superseded by ADR-004)
- **이전 결정:** 공개 header와 implementation의 의존성 방향을 유지하고
`src/`, `include/`, `tests/`로 배치한다.
- **대체 이유:** C#의 공개 API 접근성과 프로젝트 참조로 경계를 표현한다.
## ADR-003: C#·.NET 8 범용 제품 템플릿
- **상태:** 승인
- **결정:** 제품 언어는 C#, 신규 제품 기본 TFM은 `net8.0`이다.
`global.json`은 SDK `8.0.424`, `rollForward=latestPatch`,
`allowPrerelease=false`를 기준으로 한다.
- **이유:** 승인된 기술 전환 방향과 설치가 확인된 SDK 기준을 명시한다. 해당 SDK를
최신 버전이라고 주장하지 않는다.
- **대안·trade-off:** 다중 언어 호환 계층과 .NET Framework 4.x 지원은 제외한다.
SDK 패치 갱신과 향후 런타임 전환은 별도로 검증한다.
- **영향 경로:** `global.json`, 신규 제품 `.csproj`, `AGENTS.md`, 제품 문서.
## ADR-004: 공개 API와 프로젝트 참조 경계
- **상태:** 승인
- **결정:** 공개 API의 접근성·nullability 계약과 제품이 정한 참조 방향을 유지한다.
제품 프로젝트는 테스트 프로젝트를 참조하지 않는다. 권장 배치는
`src/<ProjectName>/`와 `tests/<ProjectName>.Tests/`이다.
- **이유:** 제품 소비자가 구현 세부사항이나 테스트 의존성을 부담하지 않도록 한다.
- **대안·trade-off:** 별도 Contracts, DI와 계층을 템플릿에 미리 만들지 않는다.
공개 계약 분리가 필요해질 때 제품 요구에 따라 결정한다.
- **영향 경로:** 신규 제품·테스트 `.csproj`, 공개 C# API, `docs/ARCHITECTURE.md`.
## ADR-005: 신규 프로젝트의 테스트와 컴파일 기본값
- **상태:** 승인
- **결정:** 신규 테스트는 xUnit v2 + VSTest를 기본으로 하고, nullable와 implicit
usings를 활성화한다. C# 언어 버전은 SDK 기본값을 사용하고 빌드 경고는 오류로 처리한다.
- **이유:** 실제 실행된 테스트와 컴파일 진단을 기본 검증 근거로 삼는다.
- **대안·trade-off:** 기존 테스트 프레임워크는 VSTest/TRX 계약을 만족하면 사용할 수
있다. MTP 전용 어댑터, 대규모 스타일 규칙과 커버리지 임계값은 도입하지 않는다.
- **영향 경로:** 신규 제품·테스트 `.csproj`, `AGENTS.md`.
## 새 결정 형식
새 ADR에는 상태, 결정, 이유, 대안 또는 trade-off, 영향을 받는 경로를 기록한다.
Harness 사용법과 실행 상태는 제품 ADR 대신 Harness 문서와 phase index에 기록한다.
@@ -0,0 +1,25 @@
# 제품 아키텍처
## 확정된 기술 경계
제품은 C#·.NET 8을 사용한다. 실제 업무 프로젝트, entry point, 런타임 데이터 흐름과
배포 구조는 아직 미정이다. 특정 UI 프레임워크, 계층, DI나 async 구조를 강제하지 않는다.
다음은 제품 생성 시의 **권장 배치**이며 현재 구현된 제품 구조를 뜻하지 않는다.
```text
src/<ProjectName>/ 제품 C# 코드와 SDK 스타일 .csproj
tests/<ProjectName>.Tests/ 제품 테스트와 테스트 .csproj
```
- 공개 API의 접근성, 타입과 nullability 계약을 유지한다.
- 프로젝트 참조는 제품에서 정한 방향을 따른다. 테스트가 제품을 참조하며 제품은
테스트 프로젝트를 참조하지 않는다.
- 별도 Contracts 프로젝트나 추가 계층은 실제 필요가 확인될 때만 도입한다.
- 신규 프로젝트는 `TargetFramework=net8.0`, `Nullable=enable`,
`ImplicitUsings=enable`을 기본으로 한다. C# 언어 버전은 SDK 기본값을 따른다.
- 루트 `global.json`은 빌드에 사용할 SDK 선택을 제어하며 제품 TFM을 대신하지 않는다.
실제 제품과 metadata가 추가되면 확인된 구조와 참조 방향만 이 문서에 기록한다.
`tests/fixtures/dotnet/Smoke/`는 개발 도구 검증용이며 제품이 아니다. Harness의 Python
모듈 구조와 phase 운영 절차는 [Harness 문서](HARNESS.md)에 둔다.
@@ -0,0 +1,354 @@
# C# .NET 8 Harness 환경 구축 가이드
이 문서는 Windows에서 Harness를 설치하고 C#·.NET 8 제품의 빌드·테스트와 Codex
step 실행을 준비하는 절차를 설명한다. 명령은 별도 표시가 없으면 **대상 저장소
루트의 PowerShell**에서 실행한다.
이 저장소는 범용 템플릿이며 제품명·업무 기능·UI·배포 방식은 미정이다.
`tests/fixtures/dotnet/Smoke/`는 도구 검증용 프로젝트다. 아래 `Example` 경로는
제품 설정 예시이며 현재 제품이나 검증 성공을 뜻하지 않는다. 실제 검증 결과는
해당 [phase 기록](phase/README.md)에 남긴다.
## 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 문서](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json)를 참고한다.
## 2. 기본 도구 설치
### Git과 Python
1. [Git for Windows 공식 설치 페이지](https://git-scm.com/install/windows)에서 설치하고
PowerShell과 외부 프로그램에서 `git`을 실행할 수 있도록 PATH를 설정한다.
2. [Python Windows 공식 배포 페이지](https://www.python.org/downloads/windows/)에서
Python 3.10 이상을 설치한다. 실행기와 자식 훅이 실제 설치 경로를 찾도록 설정한다.
3. 새 PowerShell 창에서 확인한다.
```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 다운로드](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)에서
환경에 맞는 **SDK**를 설치하고 새 터미널을 연다. 루트의 `global.json` 기준은 다음과 같다.
```json
{
"sdk": {
"version": "8.0.424",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}
```
이 SDK 번호는 초기 검증 기준이며 최신 버전이라는 뜻이 아니다. `latestPatch`는 같은
feature band의 적합한 패치를 선택하며 설치된 SDK 9로 넘어가는 정책이 아니다.
설치 목록과 실제 선택 결과를 구분해 **저장소 루트에서** 확인한다.
```powershell
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 설치 안내](https://learn.chatgpt.com/docs/codex/cli)를 따라 설치하고
새 터미널을 연다. npm 방식을 선택하면
[Node.js 공식 배포 페이지](https://nodejs.org/en/download)의 Node.js/npm이 필요하다.
Harness는 Codex 앱 설치 여부와 별개로 `codex` 명령을 직접 호출한다.
```powershell
codex --version
codex login
codex login status
codex exec --help
```
`codex login`의 안내에 따라 인증한다. Harness 설정에 계정이나 API 키를 넣지 않는다.
현재 실행기가 사용하는 명령 형태는 다음과 같다.
```text
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를 호출한다.
아래 점검에는 모델 호출이 없다.
```powershell
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`와
훅 설정이 있으면 제품 규칙을 유지하면서 병합한다.
```text
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`도 유지한다.
```powershell
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 실행을 시작하지 않는다.
```powershell
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 훅 문서](https://learn.chatgpt.com/docs/hooks)
Windows 훅은 `python -X utf8`과 Git 루트 탐색을 사용한다. 아래 직접 호출은 스크립트의
입력 처리만 확인하며 Codex가 해당 훅을 자동 호출하는지는 검증하지 않는다.
```powershell
'{"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`과 경로는
설명용이며 그대로 복사해 완성된 제품 설정으로 사용하지 않는다.
```json
{
"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](../tests/fixtures/dotnet/Smoke/tests/Smoke.Tests/Smoke.Tests.csproj)를 참고한다.
fixture의 `NuGet.Config`는 도구 검증용 feed를 한정한다. 실제 제품에서는 제품의 feed와
인증 설정을 유지하고 restore 오류를 개별적으로 해결한다.
제품에서 테스트 project를 참조하지 않는다. 다른 기존 프레임워크도 VSTest/TRX 계약을
만족해야 한다. [Microsoft의 xUnit 구성 예제](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-csharp-with-xunit)
아래는 설정 확인 명령이다. JSON 구문 검사와 설정 load는 제품 빌드가 아니다.
```powershell
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를 반복한다.
```powershell
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 등 별도 속성 전달은 현재 지원하지 않는다. 상세 계약은 [운영 가이드](HARNESS.md)를 따른다.
이 검사는 상속된 `Directory.Build.props`와 속성 참조를 평가하므로 `.csproj`의 문자만
확인한 결과로 대체하지 않는다.
Harness도 target과 test project별 restore/build를 수행하므로 solution에서 빠진 테스트를
누락하지 않는다. 현재 호출의 고유 결과 경로에서 **각 project의 모든 TRX**를 확인하며
실행된 테스트가 있어야 한다. exit code 0만으로는 충분하지 않다. 실패·손상·누락,
0건과 skip-only는 실패이며 과거 TRX를 재사용하지 않는다.
[VSTest dotnet test 문서](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test-vstest)
`.gitignore`의 중첩 `bin/`, `obj/`, `.vs/`, `TestResults/`와 Harness 결과 제외를
확인한다. 공유할 `.harness/config.json`과 로컬 파일·산출물 정책을 자동 staging 전에
정한다. `tdd.exclude`는 테스트 요구 면제만 제어하고 제품 존재 탐색에는 적용되지 않는다.
`tests/fixtures/`는 도구 fixture로 예약하므로 실제 제품을 그 안에 두지 않는다.
## 6. 구축 완료 확인과 최초 실행
제품 metadata와 설정을 준비한 뒤 일반 터미널에서 Stop 훅을 직접 실행한다.
SDK 선택과 실제 TFM 평가에 이어 restore·build·test와 산출물 작성이 발생한다.
```powershell
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_WORKFLOW.md)과
[Harness 스킬](../.agents/skills/harness/SKILL.md)에 맞춰 실제 작업의
`phases/index.json`, `phases/<phase>/index.json`, `stepN.md`를 준비한다.
`context_files`에는 존재하는 파일만 넣는다. [phase 기록 형식](phase/README.md)에 따라
handoff는 약 60줄, step 기록에는 변경·결정·실제 검증·유효한 실패 접근을 남긴다.
실행 상태의 기준은 항상 phase index다.
`<phase>`를 실제 디렉터리 이름으로 바꾼다.
```powershell
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 자체 검증은 제품 검증과 별도로 실행한다.
```powershell
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 운영 가이드](HARNESS.md)와
[실행 흐름](HARNESS_WORKFLOW.md)을 따른다.
@@ -0,0 +1,165 @@
# Harness 운영 가이드
Harness는 Windows에서 C#·.NET 8 제품 개발의 step 실행, 편집 정책과 build/test를
지원하는 Python 도구다. 제품 목표와 기능은 [PRD](PRD.md)에 별도로 둔다.
상세 실행 흐름은 [HARNESS_WORKFLOW.md](HARNESS_WORKFLOW.md)를 참고한다.
## 요구 환경과 설정
설치, PATH·인증·훅 점검과 첫 검증은 [환경 구축 가이드](ENVIRONMENT_SETUP.md)를 따른다.
- Windows, Git, 실제 `python` 명령으로 실행 가능한 Python 3.10 이상, 호환 Codex CLI
- 루트 `global.json`을 만족하는 .NET 8 **SDK**와 NuGet restore 접근
- 신규 테스트 기본값 xUnit v2 + VSTest; IDE는 선택 사항
- Harness 자체 테스트에는 pytest/uv 사용
`global.json`의 기준은 SDK `8.0.424`, `latestPatch`, `allowPrerelease=false`다.
SDK 선택과 `.csproj`의 제품 대상 TFM은 별개이며 신규 프로젝트 기본 TFM은 `net8.0`이다.
다음 경로는 실제 제품이 아닌 설정 예시다. `.harness/config.example.json`을 참고해
실제 제품에 맞는 `.harness/config.json`을 작성한다. 제품이 없는 템플릿에는 가상의
target을 넣은 설정 파일을 만들지 않는다.
```json
{
"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": []
}
}
```
`projectType`는 `auto`(기본값) 또는 `dotnet`이다. `dotnet.target`은 선택 사항이며,
명시한 저장소 내부 `.sln` 또는 SDK 스타일 `.csproj`를 우선한다. 생략하면 루트의
단일 solution, 다음으로 제외 경로 밖의 단일 C# project를 찾는다. 후보가 여러 개면
target을 명시한다. C# 소스가 있지만 지원 metadata가 없으면 오류다.
제품이 있으면 `dotnet.testProjects`는 전체 제품 테스트를 담은 비어 있지 않은 명시
목록이어야 한다. `configuration`의 기본값은 `Debug`다. 모든 설정 경로는 저장소
상대 경로이며 외부 symlink/junction 대상, 없는 target, 알 수 없는 필드와 version 1은
거부한다. `tdd.testRoots`와 `testPatterns`를 명시하면 빈 배열을 허용하지 않는다.
`tdd.exclude: []`는 유효하다. 테스트 패턴은 `{stem}`을 포함하는 `.cs` 파일명이며
경로 구분자를 넣지 않는다. SDK·TFM·패키지 버전을 Harness 설정에 중복하지 않는다.
## Phase 실행
```powershell
python -X utf8 scripts/execute.py <phase-name> --dry-run
python -X utf8 scripts/execute.py <phase-name>
python -X utf8 scripts/execute.py <phase-name> --push
```
먼저 `--dry-run`으로 첫 pending step이 읽을 파일과 프롬프트 문자 수를 확인한다. 이
모드는 Codex 호출, branch 전환, timestamp, 파일 쓰기, commit과 push를 하지 않는다.
`--push`와 함께 사용할 수 없다. 실제 실행은 자동 staging·commit을 수행하므로
검토한 기준 commit이 있는 전용 checkout/worktree에서 시작한다.
## 선택 문맥과 호환성
step index의 `context_files`에 필요한 기존 문서를 저장소 상대 경로로 적는다.
```json
{
"step": 1,
"name": "implement-parser",
"status": "pending",
"context_files": ["docs/ARCHITECTURE.md"]
}
```
필드를 생략하거나 빈 배열로 두면 `AGENTS.md`, 자동 인계와 step 이외의 문서는
추가되지 않는다. glob, URL, 절대 경로와 저장소 밖 경로는 지원하지 않는다.
이전에 모든 문서를 자동 주입하던 동작에 의존하는 phase는 필요한 문서를 명시한다.
현재 phase의 `docs/phase/{phase}/handoff.md`가 있으면 매 호출 다시 읽는다. 없을 때만
현재 step보다 앞선 완료 step 중 가장 최근 하나의 summary를 최대 1,000자로 전달한다.
상세 기록 형식은 [phase README](phase/README.md)를 따른다.
## 검증 모드
```json
{"step": 2, "name": "update-docs", "status": "pending", "validation": "docs-only"}
```
`validation` 생략 또는 `full`은 전체 제품 검증을 수행한다. `docs-only`도 실제 Git
변경이 루트 `AGENTS.md`, `docs/**/*.md`, 현재 phase의 `index.json`과
`stepN-output.json`에만 있고 실제 문서 변경이 있을 때만 제품 검증을 생략한다.
Git 조회 실패, 빈 변경, bookkeeping만 있는 변경, 다른 코드·설정·스킬 파일이 섞인
변경은 전체 검증으로 돌아간다. rename은 이전·새 경로를 모두 확인하며 symlink나
junction 경로는 문서 전용으로 인정하지 않는다. 제품 build가 Markdown을 입력으로
사용하면 `full`을 선택한다.
## .NET 검증
Stop 훅은 `scripts/dotnet_harness/`를 통해 저장소 루트에서 다음 순서로 검증한다.
1. SDK 선택 확인.
2. target·test project와 평가된 `ProjectReference`로 연결되는 모든 프로젝트의 저장소 내부 경로·C# SDK 형식·실제 .NET 8 TFM 확인.
3. target과 모든 test project의 중복 경로를 제거한 뒤 같은 Configuration으로 각각 restore.
4. 각 대상을 `--no-restore -warnaserror`로 build.
5. 각 test project를 `--no-build --no-restore --logger trx`로 실행.
6. 현재 호출의 project별 고유 결과 디렉터리에 생성된 모든 TRX 검사.
TFM과 참조 확인은 각 `.csproj`에 `dotnet msbuild -getProperty:TargetFramework,TargetFrameworks
-getItem:ProjectReference -p:Configuration=<configuration>`을 실행한다. 다중 TFM은
각 `TargetFramework`의 조건부 참조도 평가한다. 프로젝트 XML의 문자만 확인하지 않고
상속된 props와 속성 참조를 평가한 결과를 검사한다. 값이 없거나 .NET 8 이외의 TFM이
포함되면 restore 전에 실패한다. `net8.0`과 .NET 8의 플랫폼별 TFM을 허용한다.
참조별 `AdditionalProperties`, `GlobalPropertiesToRemove`, `SetConfiguration`,
`SetPlatform`, `SetTargetFramework`로 평가 문맥을 바꾸는 구성은 현재 지원하지 않으며
명확한 오류로 거부한다. 프로젝트는 공유 Configuration과 각 프로젝트의 TFM을 사용한다.
solution에 빠진 test project도 직접 restore/build한다. 한 단계라도 실패하면 이후
단계를 멈춘다. 전체 deadline은 1,800초이며 명령은 shell 확장 없이 argv 배열로
실행한다. NuGet 오류는 restore 실패로 보고한다.
실패·손상·누락된 TRX, 실행된 테스트 0건, skip-only 결과는 검증 실패다. 여러 결과
중 하나라도 실패하면 성공 결과로 덮지 않는다. 오래된 TRX는 현재 실행의 근거가
아니다. 기본 xUnit 이외의 기존 테스트도 VSTest/TRX 계약을 만족해야 한다.
C# 제품과 지원 metadata가 모두 없는 템플릿은 `제품 검증 대상 없음`으로 구분한다.
`tests/fixtures/`는 도구 검증용이므로 제품 자동 탐색에서 제외한다. 대상 없음과
docs-only 생략을 제품 빌드 성공으로 기록하지 않는다.
## TDD와 복구
`Foo.cs` 편집에는 기본 test root인 저장소 `tests/`와 편집 파일 옆 `tests/`, `test/`에서
`FooTests.cs` 또는 `FooTest.cs`가 먼저 있어야 한다. 설정으로 테스트 루트·패턴을
조정할 수 있다. 테스트 루트의 보조 코드, 중첩된 `bin/`, `obj/`, `.git/`, `.vs/`,
`.harness/`, `TestResults/`, 외부 의존성·명시 generated 경로는 정책에서 제외한다.
`Program.cs`, DTO, interface, `.Designer.cs`는 파일명만으로 면제하지 않는다.
필요하면 예를 들어 `src/Example/Program.cs`처럼 정확한 `tdd.exclude` 경로와
composition root 등 실제 이유를 기록한다. 이 설정은 편집 전 테스트 요구만 면제하며
제품 존재 탐색을 숨기지 않는다. 수동 작성 partial 파일은 각 stem에 대응 테스트가
필요하다. 파일 존재 검사는 테스트 품질이나 실제 red-green 실행을 대신하지 않는다.
Executor는 Codex를 최초 호출 포함 최대 3회 호출한다. 재시도 입력은 marker를 포함해
4,000자로 제한하며 앞 1,500자와 끝부분 최대 2,500자를 보존한다. 원본
`stepN-output.json`은 온전히 저장되지만 다음 시도에서 덮어쓴다. exit code 0과
`completed` 상태와 executor의 독립 제품 검증 성공이 모두 있어야 성공한다.
Stop 실패도 CLI exit 0으로 끝날 수 있어 executor가 완료 보고 후 검증을 다시 실행한다.
독립 검증은 별도의 1,800초 제한을 공유하며 실패하면 같은 호출 예산 안에서 재시도한다.
`docs-only` 판정도 실제 변경을 다시 확인한다. `blocked`는 즉시 멈춘다.
`error` 또는 `blocked`를 재개할 때는 원인을 해결하고 step을 `pending`으로 바꾼 뒤
오류 이유와 해당 timestamp를 제거한다. 인증이나 CLI 환경 오류는 즉시 중단한다.
Harness 자체 Python 검증:
```powershell
uv run --with pytest python -m pytest tests/harness -v -rs
uv run --with pytest python -m compileall -q scripts tests/harness
```
실제 SDK 통합 테스트의 skip과 subprocess 대역 테스트 성공을 구분한다. 검증한
OS·SDK·패키지 버전, 실행 건수, 경고와 남은 미검증 항목은 phase 기록에 남긴다.
@@ -0,0 +1,171 @@
# Harness 실행 흐름
Harness는 승인된 phase를 독립 Codex 세션으로 실행하고 C#·.NET 8 제품 규칙을
검증한다. 설정 예시는 [HARNESS.md](HARNESS.md), phase 기록 형식은
[phase README](phase/README.md)를 참고한다.
## 1. 계획
`AGENTS.md`와 변경에 관련된 제품 문서·코드·테스트를 확인하고 작업을 한 가지 책임의
step으로 나눈다. 각 step은 목표와 범위, 관련 파일, 핵심 제약, 실제 Acceptance
Criteria를 가진다. 공개 계약이 필요한 경우에만 시그니처를 적는다. 이전 대화에
의존하지 않으며 문서 전체를 step 본문에 복사하지 않는다.
제품 기능의 red-green 작업은 한 step 안에서 끝낸다.
```text
실패 테스트 작성·확인 → 최소 구현 → 전체 관련 테스트 성공 → step 종료
```
## 2. Phase 파일
```text
phases/
index.json
<phase>/
index.json
step0.md
docs/phase/<phase>/
handoff.md
step0.md
```
`phases/{phase}/index.json`이 실행 상태의 기준이다. `context_files`는 기존 참고 문서,
`validation`은 `full` 또는 `docs-only`를 선택한다.
```json
{
"project": "<제품명 또는 미정>",
"phase": "add-parser",
"steps": [
{
"step": 0,
"name": "implement-parser",
"status": "pending",
"context_files": ["docs/ARCHITECTURE.md"],
"validation": "full"
}
]
}
```
## 3. 읽기 전용 확인
```powershell
python scripts/execute.py <phase> --dry-run
```
첫 pending step의 현재 주입 경로, 경로별 문자 수와 전체 프롬프트 문자 수를
출력한다. Codex, Git, timestamp와 파일을 변경하지 않는다. `--push`와 동시 사용은
오류다.
## 4. 프롬프트 구성
각 호출 직전에 index, `context_files`와 handoff를 다시 읽고 다음 순서로 한 번만
결합한다.
```text
AGENTS.md
→ 공통 작업 규칙
→ context_files
→ 현재 phase handoff 또는 호환 summary
→ 직전 실패 요약
→ 현재 step
```
`context_files` 생략은 추가 문서 없음이다. 잘못된 배열, 없는 파일, UTF-8로 읽을 수
없는 파일, 절대 경로, 저장소 밖 경로와 외부를 가리키는 symlink/junction은 Codex나 Git 변경 전에
거부한다. 경로 표기와 대소문자만 다른 중복도 한 번만 넣는다.
handoff가 없을 때만 현재 step보다 앞선 완료 step 중 가장 최근 하나의 summary를
최대 1,000자로 전달한다. 이후 step이나 모든 과거 summary를 누적하지 않는다.
## 5. 실행과 상태
Executor는 `feat-{phase}` branch에서 각 pending step을 독립 `codex exec` 프로세스로
실행한다. Codex는 종료 전에 현재 `docs/phase/{phase}/stepN.md`, handoff와 phase index를
갱신한다.
| 상태 | 의미 |
|---|---|
| `completed` | exit code 0과 executor 독립 검증 통과 후 timestamp와 commit 처리 |
| `error` | 자동 시도 예산 소진, 이유와 timestamp 기록 후 중단 |
| `blocked` | 사용자 또는 외부 환경 조치 필요, 추가 호출 없이 중단 |
프로세스가 nonzero로 끝나면 index에 `completed`가 있어도 성공으로 처리하지 않는다.
Stop 실패가 exit code 0으로 끝나는 경우도 있으므로 완료 보고 직후 executor가 제품을
다시 검증한다. 실패하면 성공 상태를 취소하고 같은 최대 3회 호출 예산에서 재시도한다.
독립 검증에도 실제 docs-only 판정과 별도의 총 1,800초 검증 제한을 적용한다.
## 6. 재시도와 로그
Executor의 호출 예산은 최초 호출을 포함해 총 3회다. 한 Codex 세션 안의 일반적인
red-green 수정 횟수를 별도로 3회로 제한하지 않는다.
재시도 입력의 오류 본문은 총 4,000자로 제한한다. 앞 1,500자와 끝부분 최대 2,500자를
남기고 중간 생략 marker를 제한 안에 포함한다. 전체 stdout/stderr 결과는
`phases/{phase}/stepN-output.json`에 저장하지만 다음 시도에서 같은 파일을 덮어쓴다.
장기 보관할 시행착오는 step 기록에 요약한다. timeout도 같은 호출 예산의 실패이며
인증·CLI 옵션 같은 환경 오류는 즉시 중단한다.
## 7. 편집 전 정책
PreToolUse 훅은 위험한 삭제, 강제 push, hard reset과 SQL 파괴 명령을 차단한다.
Windows 셸의 종료 코드 변환에 의존하지 않도록 JSON `permissionDecision: "deny"`로
거부한다. 실제 Codex 편집 payload의 `tool_input.command`에서 patch 경로를 읽는다.
C# 제품 `.cs` 편집에는 대응 테스트 파일 존재 여부를 검사한다. 기본 루트는
저장소 `tests/`이며 편집 파일 옆 `tests/`, `test/`도 대응 테스트를 찾는다.
`Foo.cs`의 대응 이름은 `FooTests.cs` 또는 `FooTest.cs`다.
테스트 루트의 보조 코드, 중첩 `bin/`·`obj/` 등 산출물, 외부 의존성·명시 generated
경로는 제외한다. `Program.cs`나 `.Designer.cs`라는 이름만으로 면제하지 않는다.
필요한 면제는 정확한 `tdd.exclude` 경로와 이유를 기록한다. 이 설정은 제품 존재
탐색을 우회하지 않는다. 테스트 파일 존재와 실제 red-green 증거를 구분한다.
`tdd.testPatterns`는 `{stem}`을 포함하는 C# 파일명 패턴이다. 경로 구분자는 허용하지
않으며 테스트 위치는 `testRoots`로 지정한다. 한 검사 호출 안에서 후보 목록을 재사용하고
다음 호출에는 파일 추가·삭제를 반영한다. 제품 탐색에서 제외한 산출물·fixture 경로의
파일을 대응 테스트 증거로 사용하지 않는다.
## 8. 종료 전 검증
`validation` 생략 또는 `full`이면 Stop 훅이 발견한 C# 제품 전체를 검증한다.
- 저장소 루트 `global.json`에 따른 .NET 8 SDK 선택 확인
- target의 실제 각 프로젝트와 모든 test project의 `TargetFramework`/`TargetFrameworks`를
SDK로 평가해 .NET 8인지 확인; 상속된 props·속성 및 ProjectReference 그래프도 평가하며 빈 값·비지원 TFM·저장소 밖 참조는 거부
- version 2 설정의 target과 명시된 모든 `testProjects`를 restore/build;
build 경고는 오류로 처리하고 solution에 빠진 test project도 직접 build
- 각 test project를 VSTest로 실행하고 현재 호출의 고유 디렉터리에 생성된 모든 TRX 검사
- 실행 0건·skip-only·누락·손상·실패 TRX는 실패; 과거 결과를 성공 근거로 사용하지 않음
- 지원 metadata 없음: 제외되지 않은 C# 소스가 있으면 orphan 오류
- 제품과 metadata 모두 없음: 제품 검증 대상 없음; 제품 build 성공으로 기록하지 않음
전체 검증은 1,800초 deadline을 공유하며 SDK·TFM 평가·restore·build·test 중 실패한 단계에서
중단한다. `tests/fixtures/`는 제품 자동 탐색에서 제외하는 도구 검증용이다.
Stop 프로세스 종료 코드만으로 통과를 판정하지 않는다. `continue: false`와
`stopReason` 응답은 검증 실패이며 실제 자동 훅 호출은 별도로 확인한다.
`docs-only`이면 executor가 현재 phase를 자식 프로세스 환경으로 전달하고 Stop 훅이
실제 Git 변경을 다시 검사한다. 루트 `AGENTS.md`, `docs/**/*.md`, 현재 phase의
`index.json`과 `stepN-output.json`만 허용하며 실제 문서 변경이 있어야 생략한다.
rename은 원래 경로와 새 경로를 모두 검사한다. Git 실패, 빈 변경, bookkeeping만 있는
변경, untracked C# 또는 다른 파일이 하나라도 있으면 전체 검증으로 돌아간다.
symlink나 junction으로 해석되는 변경 경로도 보수적으로 전체 검증한다.
## 9. Commit, 완료와 재개
step 성공 시 executor는 제품 변경과 metadata를 분리해 commit한다. 모든 step이
성공하면 phase와 top-level 상태를 `completed`로 바꾸고 `--push`가 있으면
`origin/feat-{phase}`에 push한다. 실행기는 광범위한 staging을 할 수 있으므로 깨끗한
전용 worktree에서 실행한다.
재개 절차:
1. `error` 또는 `blocked` 원인을 해결한다.
2. 해당 step status를 `pending`으로 바꾼다.
3. `error_message`/`failed_at` 또는 `blocked_reason`/`blocked_at`을 제거한다.
4. handoff, 직전 원본 출력과 index를 확인한 뒤 다시 실행한다.
강제 종료로 handoff가 낡았을 수 있으므로 index 상태를 우선한다. 이 저장소의 fixture
검증은 제품 요구사항이나 실제 제품 build 완료를 의미하지 않는다. 실행한 명령과
결과, SDK·테스트 건수와 남은 미검증 항목은 해당 phase 기록에서 확인한다.
@@ -0,0 +1,37 @@
# C# .NET 8 제품 PRD
Harness 운영 요구사항은 이 문서의 제품 요구사항에 포함하지 않는다.
## 목표
미정 — 제품 요구사항 정의 시 확정.
## 사용자
미정 — 제품 요구사항 정의 시 확정.
## 핵심 기능
미정 — 제품 요구사항 정의 시 확정.
## 제약
- C#·.NET 8 제품으로 개발하며 신규 프로젝트 기본 TFM은 `net8.0`이다.
- 제품의 UI, 배포 OS·방식, 성능·보안·호환성 조건은 미정이다.
Harness의 Windows 실행 제약으로 제품 배포 OS를 확정하지 않는다.
- 공개 API 계약과 프로젝트 참조 방향을 유지하며 제품이 테스트를 참조하지 않는다.
- 신규 프로젝트의 nullable 검사를 활성화하고 빌드 경고는 오류로 처리한다.
- 새 기능과 버그 수정은 실패하는 테스트를 먼저 확인한다.
- 신규 테스트 기본값은 xUnit v2 + VSTest다. 제품의 전체 테스트 범위는 요구사항과
함께 정하고 명시적인 `testProjects`에 반영한다.
## 완료 조건
미정 — 제품 기능별 실행 가능한 build/test 명령과 수용 조건을 정의할 때 확정.
## 제외 범위
- Harness 실행기, 훅, phase 관리와 문맥 경량화는 개발 도구의 책임이며 제품 기능이
아니다.
- 구체적인 제품 기능, API, UI, 계층과 배포 범위는 요구사항이 정해질 때까지 확정하지 않는다.
- .NET Framework 4.x와 Microsoft.Testing.Platform 전용 실행은 이번 템플릿 범위 밖이다.