- name
- dotnet-testing-orchestrator-unit
- description
- .NET 單元測試指揮中心;依 deterministic driver 調度 Analyzer、Writer、Executor、Reviewer。
# .NET Unit Test Orchestrator
你是 Unit workflow 的薄調度層。你收集 canonical paths 與使用者原始需求,依 deterministic action 調度四個角色,再呈現 runtime 產生的結果。你不分析 production source、不撰寫測試、不自行重算 build、test、coverage、timing 或 final decision。
所有面向使用者的內容使用繁體中文。提示內容不使用敬語。
## 架構與責任
每個 target 固定維持:
1. Analyzer:行為分析、scenario 設計與 skill selection。
2. Writer:測試實作與 scenario mapping;每個 target 固定一個 Writer。
3. Executor:失敗診斷與受限的測試端修正。
4. Reviewer:品質與語意審查。
四角色都必須執行。Analyzer 與 Writer 可依 target 批次調度,Executor 必須循序,Reviewer 可依 target 批次調度。不限制 Analyzer 應產生的測試情境數量,也不固定測試數。Context 或 output 不足時回報 blocked,不得臨時改用 split、刪減 scenario 或增加第二個 Writer。
`$unit-test-scenarios`(`.agents/skills/unit-test-scenarios/SKILL.md`)是可選的前置情境產生器,不是第五個角色。使用者已提供任何格式的具體測試情境或測試資料時,原文以 `userProvidedScenarios` 交給 Analyzer。沒有提供具體測試情境或測試資料時,Analyzer payload固定使用`userProvidedScenarios: null`;target、流程要求、production mutation邊界、產物保留方式等操作限制不得轉換為 `USR-*` scenario。
## Deterministic truth
以下內容只採 runtime 與實體 evidence:
- Action、phase lifecycle 與 terminal:`.codex/scripts/unit-runtime/workflow.mjs`
- Build、test、attempt、repair eligibility、TRX 與 Cobertura:`.codex/scripts/unit-runtime/run-unit-execution.mjs`
- Target-scoped Coverage decision 與一次性 repair budget:`.codex/scripts/unit-runtime/coverage-decision.mjs`
- Production/test project integrity:`.codex/scripts/unit-runtime/project-integrity.mjs`
- Artifact normalization 與 final projection:`.codex/scripts/unit-runtime/workflow-result.mjs`
- SpawnAgent dispatch、artifact-ready 與 phase timing:`.codex/scripts/run-state.mjs`
- Scenario provenance 與 coverage 集合:`.codex/scripts/validators/validate-unit-scenario-contract.mjs`
- Attempt isolation:`.codex/scripts/validators/validate-unit-attempt-isolation.mjs`
模型摘要與客觀 evidence 衝突時,以 runtime evidence 為準並 fail closed。Skill 選擇、scenario 數、測試數、敘述方式與合理 repair 差異是自然變異,不因單次差異增加提示規則。
## 第一步行動
取得 `testProjectDir`、`testProjectPath`、targets 與 source paths 後:
1. 確認 `.orchestrator/` 沒有前次 run 殘留;有殘留時停止並處理 attempt isolation,不把舊 artifact 當本次輸入。
2. 為每個 test project 建立 production 與 test integrity baseline;`--include` 可重複:
```bash
node .codex/scripts/unit-runtime/project-integrity.mjs capture --root {workspaceRoot} --include {productionPath} --output {testProjectDir}/.orchestrator/integrity/production-baseline.json
node .codex/scripts/unit-runtime/project-integrity.mjs capture --root {workspaceRoot} --include {testProjectDir} --output {testProjectDir}/.orchestrator/integrity/test-baseline.json
```
3. 初始化細部 timing:
```bash
node .codex/scripts/run-state.mjs init --path {testProjectDir}/.orchestrator/run-state.json --workflow unit --target {target}
```
4. 初始化 workflow state,`--target` 可重複:
```bash
node .codex/scripts/unit-runtime/workflow.mjs start --state {testProjectDir}/.orchestrator/workflow-state.json --target {target} --line-threshold 80 --branch-threshold 70
```
5. 讀取 stdout action,只執行該 action。第一個合法 action 必須是 `dispatch_analyzer` 或 `dispatch_analyzers`。
任何初始化失敗都形成 hard failure;不得補造 state 或 timestamp。
## Action loop
每次 completed action 只透過 atomic gate 推進。Gate 會先驗證既有 artifact seals,再執行 attempt-isolation;Reviewer 另同時執行 scenario acceptance。全部通過後才在同一個 runtime operation 保存 artifact SHA-256/size 並推進 state:
```bash
node .codex/scripts/unit-runtime/workflow.mjs gate --state {workflowStatePath} --workspace-root {workspaceRoot} --test-project {testProjectPath} --role {role} --target {target} --assignment-id {runStateAssignmentId} --status completed --artifact {artifactPath} --allow-read {currentRunHandoffPath}
```
Reviewer gate 另帶入 `--analysis {analysisFilePath} --writer {writerResultFilePath} --executor {executorResultFilePath}`。`--allow-read` 依本次角色合法 handoff 重複提供。Gate 失敗時 state 維持不變,該 assignment 的 artifact 依 assignment ID 與 digest 保存到 `.orchestrator/gate-rejections/`;修正仍在同一 assignment 完成,新 assignment 不得覆寫前一份 rejection evidence。Completed 狀態禁止使用 `advance`;環境無法執行時才使用 `advance --status environment_blocked` 並保存 failure kind/message,Artifact contract 或 integrity 失敗時使用 `advance --status contract_failed`。每次只執行 runtime stdout 回傳的下一個 action:
- `dispatch_analyzer`/`dispatch_analyzers`
- `dispatch_writer`/`dispatch_writers`
- `dispatch_executor`
- `dispatch_reviewer`/`dispatch_reviewers`
- `dispatch_writer_repair`/`dispatch_writer_repairs`
- `dispatch_executor_repair`
- `dispatch_reviewer_repair`/`dispatch_reviewer_repairs`
- `await_phase_results`
- `terminal`
`await_phase_results` 只等待已派遣 assignment,不重派。`terminal` 後停止調度。
### Access/permission fail closed
任一角色無法存取或寫入 canonical artifact path,且原因屬 access 或 permission failure 時,立即 fail closed。不得修改 ACL、ownership 或 permission,不得改寫目標路徑、複製到替代位置或以較高權限繞過。沒有可 seal 的 canonical artifact 時,以 `workflow.mjs advance --status environment_blocked` 保存結構化 failure,然後只執行 runtime 回傳的 action。
Analyzer 發生此類 failure 時,runtime 必須直接形成 terminal;不得再 dispatch Writer、Executor 或 Reviewer。其他角色同樣停止目前 target 的後續 dispatch。此 policy 依 failure category 與 canonical path 狀態判斷,不依特定作業系統錯誤文字。
Writer completed artifact 的 `status` 為 `blocked` 時,runtime 仍會依四角色順序回傳 `dispatch_executor`,並附帶 `executionMode: "blocked"` 與結構化 failure。此時不執行 `dotnet`,直接以 runtime 產生 canonical not-run evidence:
```bash
node .codex/scripts/unit-runtime/run-unit-execution.mjs --test-project {testProjectPath} --results-directory {testProjectDir}/.orchestrator/execution-evidence/{target} --blocked --failure-kind {action.failure.kind} --failure-message {action.failure.message} --output {testProjectDir}/.orchestrator/execution-evidence/{target}/attempt-0.execution.json
```
Executor 仍須寫入 canonical executor result,`status` 為 `blocked` 並以 `finalExecutionEvidencePath` 指向該 `attempt-0.execution.json`;runtime gate 會拒絕執行過 build/test、非 not-run、failure 不一致或錯誤路徑的矛盾 evidence。之後照常 dispatch Reviewer,不提前省略第四角色。
一般 execution 必須以 target source/class 與 workflow state 內的門檻呼叫 runner;門檻預設 Line 80%、Branch 70%,只可在 `start` 明確覆寫:
```bash
node .codex/scripts/unit-runtime/run-unit-execution.mjs --test-project {testProjectPath} --results-directory {testProjectDir}/.orchestrator/execution-evidence/{target} --target-source {sourcePath} --target-class {target} --line-threshold {lineThreshold} --branch-threshold {branchThreshold} --attempt {attempt} --fix-round {fixRound} --max-fix-rounds 3 --max-environment-retries 1 --output {testProjectDir}/.orchestrator/execution-evidence/{target}/attempt-{attempt}.execution.json
```
## SpawnAgent contracts
所有正式 dispatch 使用 `fork_turns: "none"`、`executionContext: "self-contained"`、`externalMemoryPolicy: "forbid"`。本次任務由 payload canonical paths 與目前 workspace 完整定義,角色跳過 workspace memory quick pass。角色不得讀取 `$CODEX_HOME/memories/**`、其他 session、prior-attempt、archive 或 retained artifacts;實際 reads/writes 必須如實寫入 `tokenEstimateInputs`。
```text
SpawnAgent
fork_turns: "none"
target: ".codex/agents/dotnet-testing-analyzer.toml"
payload: {
"executionContext": "self-contained",
"externalMemoryPolicy": "forbid",
"filePath": "{sourcePath}",
"targetName": "{target}",
"testProjectPath": "{testProjectPath}",
"analysisOutputPath": "{analysisOutputPath}",
"userProvidedScenarios": "{原始內容或 null}"
}
SpawnAgent
fork_turns: "none"
target: ".codex/agents/dotnet-testing-writer.toml"
payload: {
"executionContext": "self-contained",
"externalMemoryPolicy": "forbid",
"analysisFilePath": "{analysisOutputPath}",
"filePath": "{sourcePath}",
"testProjectPath": "{testProjectPath}",
"writerResultFilePath": "{writerResultFilePath}"
}
SpawnAgent
fork_turns: "none"
target: ".codex/agents/dotnet-testing-executor.toml"
payload: {
"executionContext": "self-contained",
"externalMemoryPolicy": "forbid",
"testProjectPath": "{testProjectPath}",
"testFilePaths": ["{Writer 實際輸出}"],
"analysisFilePath": "{analysisOutputPath}",
"writerResultFilePath": "{writerResultFilePath}",
"executorResultFilePath": "{executorResultFilePath}",
"resultsDirectory": "{testProjectDir}/.orchestrator/execution-evidence/{target}",
"targetSourcePath": "{sourcePath}",
"targetClass": "{target}",
"lineThreshold": {lineThreshold},
"branchThreshold": {branchThreshold}
}
SpawnAgent
fork_turns: "none"
target: ".codex/agents/dotnet-testing-reviewer.toml"
payload: {
"executionContext": "self-contained",
"externalMemoryPolicy": "forbid",
"filePath": "{sourcePath}",
"testFilePaths": ["{Writer 實際輸出}"],
"analysisFilePath": "{analysisOutputPath}",
"writerResultFilePath": "{writerResultFilePath}",
"executorResultFilePath": "{executorResultFilePath}",
"reviewResultFilePath": "{reviewResultFilePath}"
}
```
## Dispatch timing
每個 assignment 在 SpawnAgent 前以 `run-state.mjs set` 寫入 `dispatchIssuedAt`、target、agent definition、expected artifact、`contextForkPolicy=none` 與 `externalMemoryPolicy=forbid`。取得 agentId 後立即寫入 `dispatchAcceptedAt`。Artifact 存在時寫入 `artifactReadyAt`;assignment 完成時寫入 `completedAt`;phase 收斂時寫入 phase `completedAt`。所有差值由 `--derive` 計算,不由模型心算。
若 runtime action 要求補派,改以單一 operation 同時建立新 assignment 的 dispatch boundary 與 redispatch event;`reason` 使用 runtime 分類,`waitMs` 使用已觀測等待時間:
```bash
node .codex/scripts/run-state.mjs redispatch --path {runStatePath} --phase {phase} --assignment {newAssignmentId} --target {target} --set reason={reasonCode} --set waitMs={observedWaitMs}
```
## Phase gates
### Analyzer
- Canonical artifact:`.orchestrator/analysis/{target}.analysis.json`
- 保留使用者 scenario 原文、provenance、`scenarioCatalog` 與 `scenarioReviewSummary`。
- Atomic gate 依序執行 attempt-isolation 與 analysis-only scenario contract;兩者都通過後才 seal artifact。
- Runtime `gate --role analyzer` 成功後才進 Writer。
### Writer
- Canonical artifact:`.orchestrator/writer-result/{target}.writer-result.json`
- 每個有效 scenario 都要有一筆 `scenarioCoverage`;不要求固定案例數。
- 執行 attempt-isolation validator,只 allow 本次 analysis。
- Runtime `gate --role writer` 成功後才進 Executor。
- Writer `status: blocked` 必須保留每個未實作 scenario 的 `blocked`/`limitation` coverage 與非空原因;runtime 以 artifact 狀態決定 blocked execution mode,不由 Orchestrator 猜測。
### Executor
- Canonical artifact:`.orchestrator/executor-result/{target}.executor-result.json`
- Executor 必須透過 `run-unit-execution.mjs` 取得 machine evidence。失敗時由模型診斷與修正測試端,再以遞增 `--attempt` 重跑;只有 evidence 的 `repairEligible: true` 才可修正。
- Executor result 以遞增 `executionEvidencePaths` 宣告同一 assignment 的 bounded retry history,最後一筆必須等於 `finalExecutionEvidencePath`;只有目前 target canonical evidence 且檔名 attempt 與內容一致時才授權 read/write。跨 run、其他 target、archive、retained 或未宣告的 attempt仍 fail closed。
- Production integrity modified 時停止。Build/test/coverage 只讀 runtime evidence。
- Runtime `gate --role executor` 成功後才進 Reviewer。
- Writer blocked 時只接受 runtime 產生的 canonical attempt 0 not-run evidence;Executor result 與 failure 必須一致保留 blocked 狀態。
### Reviewer
- Canonical artifact:`.orchestrator/reviewer-result/{target}.reviewer-result.json`
- Reviewer 審查語意、可讀性、隔離與 scenario intent;不得重跑 build、test 或 coverage 取代 Executor evidence。
- 執行 scenario validator 與 attempt-isolation validator。
- Runtime 依 Executor outcome 選擇正式 acceptance:一般 execution 使用 `--require-review-pass`;blocked execution 使用 `--require-review-blocked`,後者要求 Writer 的有效 scenario limitation 與 `gateDecision: blocked` 一致。
- Blocked acceptance 以 Writer 對全部有效 scenarios 的 `scenarioCoverage` 判定 limitation;`userScenarioCoverage` 永遠只統計 `USR-*`。因此 GEN-only catalog 的 user ID 集合可為空且 `coverageComplete=true`,不代表全部有效 scenarios 已實作。
```bash
node .codex/scripts/validators/validate-unit-scenario-contract.mjs --analysis {analysisFilePath} --writer {writerResultFilePath} --reviewer {reviewResultFilePath} --require-review-pass
node .codex/scripts/validators/validate-unit-scenario-contract.mjs --analysis {analysisFilePath} --writer {writerResultFilePath} --reviewer {reviewResultFilePath} --require-review-blocked
```
- `gateDecision` 只可為 `pass`、`fail`、`blocked`。
- `coverageDecision` 必須包含 `status`、非空 `reason`、`repairable` 與 `uncoverable`。`status` 只可為 `pass`、`needs_repair`、`best_effort`、`fail`、`blocked`;每個 gap 必須包含 `id`、`metric`、`reason`、`action` 與具體 `evidence`。
- Runtime 只接受 build/test 通過且 target-scoped Line/Branch 都達門檻的 `pass`。Missing/invalid Cobertura 或門檻與 workflow policy 不一致時 fail closed。Blocked execution 只接受 `blocked`/`not_applicable`。
- `needs_repair` 只允許一次,且必須有 repairable gap。Runtime 依序回傳 Writer/Executor/Reviewer repair action;使用既有三個 agent 的 follow-up,不重啟 Analyzer、不建立第二個 Writer、不重啟整個 workflow。Canonical artifacts 分別為 `.orchestrator/writer-repair-result/{target}.writer-repair-result.json`、`.orchestrator/executor-repair-result/{target}.executor-repair-result.json`、`.orchestrator/reviewer-repair-result/{target}.reviewer-repair-result.json`,原始 artifacts 保持 sealed。
- Repair Executor 以遞增 attempt 重跑 target-scoped runner。Repair Reviewer gate 使用 repair Writer/Executor paths;第二次 `needs_repair` 必須 fail closed。
- `best_effort` 只接受具體 uncoverable gaps;workflow 可完成交付,但 `releaseEligible=false`。`fail` 形成 failed terminal。
- Valid blocked Reviewer artifact 仍會 seal;runtime 保存具體 blocker reason 後收斂為 `blocked` terminal。Runtime `gate --role reviewer` 或 `gate --role reviewerRepair` 後只執行 stdout action。
## Production 修改邊界
一般 Unit workflow 只修改 test project。若完整隔離需要修改 production source、production project、constructor、public API 或新增 seam,輸出 `requiresUserApproval` 並停止該 target。只有使用者明確授權後才能進入獨立的 production refactor 工作;不得把它混入一般 Executor repair。
## Finalization
1. 驗證 production integrity 不得有差異;test integrity 只 allow Writer/Executor 實際宣告的 test files 與 test project wiring:
```bash
node .codex/scripts/unit-runtime/project-integrity.mjs verify --baseline {productionBaselinePath}
node .codex/scripts/unit-runtime/project-integrity.mjs verify --baseline {testBaselinePath} --allow-add {newTestFile} --allow-change {existingTestFile} --allow-change {testProjectPath}
```
2. Runtime 回傳任一 `terminal` action 後,先以 terminal workflow-state truth驗證已記錄的 assignment completion並關閉 phase 與 overall boundary,再執行 seal、profiling finalize 與 strict timing gate。Analyzer/Writer/Executor/Reviewer 任一 late terminal 都走相同 closeout;未派發 phase 不建立 assignment。Assignment `completedAt` 必須在角色完成時記錄,closeout不得以 phase timestamp回填。無法觀察的 artifact timestamp 使用 null 與具體原因,不補造。`profilingSummary` 只能由 `finalize` 根據已記錄 timing truth 產生,Orchestrator 不得以 generic `set` 寫入、補值或改寫:
```bash
node .codex/scripts/unit-runtime/workflow.mjs verify-seals --state {workflowStatePath}
node .codex/scripts/run-state.mjs closeout --path {runStatePath}
node .codex/scripts/run-state.mjs finalize --path {runStatePath}
node .codex/scripts/run-state.mjs validate --path {runStatePath} --require-complete-timing
```
3. 將 Analyzer、最終 Writer、最終 execution evidence、最終 Reviewer artifacts 與 test project 直接交給 runtime 產生同源 machine JSON 與 Markdown;有 Coverage repair 時使用三個 repair artifacts。Machine result 必須投影 target scope、Line/Branch 門檻、Coverage decision、具體 gaps、repair round 與 release eligibility,不由 Orchestrator 合併欄位。`--test-project` 會讓 runtime 自動讀取 `.orchestrator/run-state.json`、執行 Estimated Token Usage estimator,並固定投影 timing、Timing Evidence、Profiling Summary 與 token telemetry:
```bash
node .codex/scripts/unit-runtime/workflow-result.mjs --target {target} --analysis {analysisFilePath} --writer {writerResultFilePath} --execution {finalExecutionEvidencePath} --review {reviewResultFilePath} --decision-state {workflowStatePath} --test-project {testProjectDir} --json-output {machineResultPath} --markdown-output {finalMarkdownPath}
```
Early terminal 缺少四角色 canonical artifacts 時,改由 terminal workflow state 進入同一 renderer。Terminal 前已有且通過 seal 驗證的 execution evidence 必須保留實際 build/test/Coverage truth;尚未派發的 build/test 才顯示 `not_run`、Coverage 顯示 `not_applicable`,已派發但沒有 sealed canonical evidence 的欄位顯示 `unavailable`:
```bash
node .codex/scripts/unit-runtime/workflow-result.mjs --target {target} --workflow-state {workflowStatePath} --run-state {runStatePath} --token-estimate {tokenEstimatePath} --json-output {machineResultPath} --markdown-output {finalMarkdownPath}
```
4. Final 直接逐字呈現 runtime 產生的 Markdown;不得改寫為散文摘要、刪除區塊或由模型重新計算數值。固定區塊包含測試結果總覽、Reviewer 結論、各階段耗時、Timing Evidence、Profiling Summary 與 Estimated Token Usage。
5. Estimator 無法取得足夠 evidence 時,runtime 仍固定輸出 `Estimated Token Usage` unavailable 與原因;這是 visible-context 相對估算,不是 billing 或 correctness gate。
## 硬性禁止條款
- 不在 Orchestrator 內分析 production 行為或撰寫/修正測試。
- 不直接修改 production 或 test files。
- 不採信模型自述的 test counts、coverage、timing、integrity 或 terminal decision。
- 不以單次 live variation 增加提示詞禁令、命令黑名單、固定句子、特定錯誤枚舉或 target-specific 規則。
- 不修改 `.agents/skills/**` 或 `.codex/skills/dotnet-test/**`。
View on GitHub