sync: from iop-s0 v1.1.196
This commit is contained in:
parent
f264087fc4
commit
4843f7320e
14 changed files with 668 additions and 76 deletions
|
|
@ -1 +1 @@
|
|||
1.1.195
|
||||
1.1.196
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Review the implementation phase of the plan-code-review loop:
|
|||
```text
|
||||
plan skill -> finalize-task-routing -> implementation -> code-review skill
|
||||
^ |
|
||||
+----- WARN/FAIL: invoke plan skill with raw findings -+
|
||||
+----- WARN/FAIL: materialize closed reviewer findings -+
|
||||
```
|
||||
|
||||
Implementation agents never decide or request user review. They record implementation, verification, deviation, and blocker evidence in implementation-owned review fields. The official code-review agent alone evaluates the review-agent-owned gate and, when justified, writes `USER_REVIEW.md` from `agent-ops/skills/common/code-review/templates/user-review-template.md`.
|
||||
|
|
@ -24,8 +24,9 @@ Implementation agents never decide or request user review. They record implement
|
|||
- Next state: `PASS` writes `complete.log` and moves the task under `agent-task/archive/YYYY/MM/`; if the task group is `m-<milestone-slug>`, report completion metadata for the runtime event. `WARN` or `FAIL` normally invokes `agent-ops/skills/common/plan/SKILL.md`, which must run `finalize-task-routing` before writing the next active pair; if the user-review gate triggers, write `USER_REVIEW.md` instead. A completed `USER_REVIEW.md` uses the same terminal `complete.log` and archive path as `PASS`.
|
||||
- The user-review gate is review-agent-owned and triggers only when current evidence proves either that a concrete selected Milestone `구현 잠금 > 결정 필요` item blocks the next safe implementation step or that required external verification cannot proceed without a user-controlled capability or authorization. Generic status fields or blocker text written by implementation are never a user-review request.
|
||||
- Do not replace `USER_REVIEW.md` with an inline user question. When the user-review gate triggers, write the file-based stop state and report its path.
|
||||
- Do not ask for confirmation before WARN/FAIL follow-up files. If the user-review gate triggers, write `USER_REVIEW.md`; otherwise invoke the plan skill with the current raw findings and let it write the smallest concrete follow-up after fresh routing.
|
||||
- Recovery: if a prior turn appended a verdict without archive or next-state files, do not append another verdict; resume Step 5 preparation/archive from that verdict. If a pre-existing verdict predates stable finding ids, assign `R1..` and `S1..` once in displayed order in the recovery handoff and record that mapping without appending a second verdict. If exactly one member of the pair was archived after both archive destinations had been preflighted, verify the archived member and remaining source/destination, finish that archive, then use the post-archive recovery below. If both logs exist with a verdict but the required next state is absent, reconstruct it from those exact logs: PASS resumes `complete.log`; WARN/FAIL reruns the plan skill in `write` mode with raw archived findings and `isolated-reassessment`; a valid user-review gate rerenders `USER_REVIEW.md`. If a prior turn resolved `USER_REVIEW.md` without `complete.log`, resume at the matching finalization step.
|
||||
- Do not ask for confirmation before WARN/FAIL follow-up files. If the user-review gate triggers, write `USER_REVIEW.md`; otherwise finish the reviewer-owned evidence/root-cause/fix packet and let the plan skill materialize the smallest concrete follow-up after fresh routing.
|
||||
- Verification, defect analysis, and fix selection are review-agent-owned at verdict time. When implementation is present but its recorded verification output is missing, incomplete, stale, or only summarized, run the applicable plan/repository commands directly, record the fresh output in the active review artifact, and judge the implementation from that result. When fresh evidence exposes a defect, finish the root-cause analysis and choose the concrete fix before creating the follow-up plan; do not delegate diagnosis or solution selection to the plan skill or implementing agent. Do not create a finding or another plan solely because the implementing agent left insufficient verification evidence.
|
||||
- Recovery: if a prior turn appended a verdict without archive or next-state files, do not append another verdict; resume Step 5 preparation/archive from that verdict. If a pre-existing verdict predates stable finding ids, assign `R1..` and `S1..` once in displayed order in the recovery handoff and record that mapping without appending a second verdict. If exactly one member of the pair was archived after both archive destinations had been preflighted, verify the archived member and remaining source/destination, finish that archive, then use the post-archive recovery below. If both logs exist with a verdict but the required next state is absent, reconstruct it from those exact logs: PASS resumes `complete.log`; WARN/FAIL first reconstructs and, when necessary, completes the reviewer-owned evidence/root-cause/selected-fix packet from those exact logs and current source, then reruns the plan skill in `write` mode with `isolated-reassessment`; a valid user-review gate rerenders `USER_REVIEW.md`. If a prior turn resolved `USER_REVIEW.md` without `complete.log`, resume at the matching finalization step.
|
||||
|
||||
## User Review Gate
|
||||
|
||||
|
|
@ -100,16 +101,16 @@ Follow-up routing boundary:
|
|||
- This skill records current source, actual verification output, and findings, but it must not estimate or recommend the next lane/G.
|
||||
- Decide each Required/Suggested disposition here and validate it directly; dispatcher use is optional. Default repository-fixable work to `direct-fix`, expanding stale exclusions when required by original acceptance. Allow `verified-dependency` only with the exact PLAN/task ordering proof, or `complete.log` plus fresh proof that the precondition is satisfied; vague owners and `complete.log` alone are invalid. Set `ownership_closed=true` only after every id has that proof.
|
||||
- Never send an unchanged-precondition verification packet. For an unordered dependency, keep the verdict pair and return `status=blocked`, `blocked_reason=dependency-wait`, `next-task`, and resume condition; do not redispatch it or request user review.
|
||||
- On WARN/FAIL, invoke the plan skill in `prepare-follow-up` mode with the selected task path and raw current evidence before archiving the current pair.
|
||||
- On WARN/FAIL, invoke the plan skill in `prepare-follow-up` mode with the selected task path and closed reviewer-owned evidence/root-cause/selected-fix packet before archiving the current pair.
|
||||
- Do not pass the archived lane, grade, routing score, rationale, or filename as plan-routing input. Archive paths remain evidence pointers, and actual logs/findings remain raw evidence.
|
||||
- The plan skill must complete its full analysis and mandatory `finalize-task-routing` step before it writes the next pair. Code-review must not create a routed follow-up pair directly.
|
||||
- The plan skill must validate and materialize the reviewer-owned root cause, selected fix, scope, evidence, and acceptance commands, then complete mandatory `finalize-task-routing` before it writes the next pair. It must not reopen diagnosis or solution selection. Code-review must not create a routed follow-up pair directly.
|
||||
- Repair non-behavioral review artifact drift during review instead of failing solely for it when implementation correctness, tests, and contracts remain judgeable.
|
||||
|
||||
Directory states:
|
||||
|
||||
| State | Meaning |
|
||||
|-------|---------|
|
||||
| `PLAN-*-G??.md` + unfilled `CODE_REVIEW-*-G??.md` stub/placeholders | Implementation is not judgeable; review should fail completeness if invoked |
|
||||
| `PLAN-*-G??.md` + wholly unfilled `CODE_REVIEW-*-G??.md` stub/placeholders and no implementation-readiness evidence | Implementation was not submitted for review and is not judgeable. Do not use this state for an implemented change whose recorded verification output alone is incomplete; the reviewer verifies that change directly. |
|
||||
| `PLAN-*-G??.md` + filled `CODE_REVIEW-*-G??.md` without verdict | Ready for code-review skill |
|
||||
| `PLAN-*-G??.md` + `CODE_REVIEW-*-G??.md` with appended verdict | Review finalization pending or `dependency-wait`; do not append another verdict. Resume Step 5 immediately for unfinished finalization, or only after the recorded dependency changes for a wait. |
|
||||
| Exactly one active pair member + its newly archived counterpart | Partial archive after a preflighted finalization; verify both identities, finish the remaining archive, then resume post-archive recovery |
|
||||
|
|
@ -163,6 +164,7 @@ The diff is the starting point, not the boundary. Follow behavior and API connec
|
|||
Review scope control:
|
||||
|
||||
- Use the plan's commands and checkpoints as the primary evidence. Add one focused, possibly table-driven reproducer only when needed to prove a suspected blocking defect; do not build speculative exhaustive probe matrices.
|
||||
- Execute the applicable plan verification commands and any focused reproducer needed for the verdict. Treat implementation-owned output as a handoff and comparison source, not as a substitute for fresh reviewer verification. If recorded output is absent or insufficient but the command is available and safe in the current authorized environment, run it and repair `Verification Results` before classifying findings. If a check fails, collect enough source/runtime data to establish the root cause and one implementable fix; never emit a diagnostic-only finding that asks the next worker to investigate or choose among alternatives.
|
||||
- In a follow-up review, keep Required findings within the current plan, inherited Required findings, direct regressions from the fix, and concrete violations of the original SDD or contract acceptance criteria. Exclude unrelated pre-existing work from the verdict and Required/Suggested/Nit counts; mention it only in the final report as an out-of-scope task candidate.
|
||||
- Before adding a new Required that the current plan did not state, cite the exact original plan/SDD/contract criterion it violates or provide a concrete failing case. Do not require a preferred test shape when existing deterministic evidence proves the same behavior.
|
||||
- When one invariant fails in multiple already-observed variants, report that known set together instead of revealing one variant per follow-up.
|
||||
|
|
@ -173,11 +175,11 @@ Before writing the verdict:
|
|||
|
||||
- Compare actual source files against every planned checklist item.
|
||||
- Compare the plan `Implementation Checklist` and review stub `Implementation Checklist` (legacy: `구현 체크리스트`); repair non-behavioral drift when implementation remains judgeable.
|
||||
- When the active artifacts use an `m-*` task header, require identical non-empty `milestone-task` ids in PLAN and CODE_REVIEW, resolve the active Milestone by slug, and verify every id exists. If the Milestone has `SDD: 필요`, read only that Milestone and its SDD, compare implementation evidence against the SDD Acceptance Scenarios/Evidence Map for those ids, and fail completeness or verification trust when evidence is insufficient.
|
||||
- When the active artifacts use an `m-*` task header, require identical non-empty `milestone-task` ids in PLAN and CODE_REVIEW, resolve the active Milestone by slug, and verify every id exists. If the Milestone has `SDD: 필요`, read only that Milestone and its SDD, compare the implementation and fresh reviewer verification against the SDD Acceptance Scenarios/Evidence Map for those ids. Missing implementation-recorded evidence alone is not a finding; obtain it directly when an authorized runner is available.
|
||||
- Directly repair obvious non-behavioral source nits when safe: typos, stale comments, docs, or formatting only, with no behavior/test/API contract change.
|
||||
- If a checklist item contains integrated verification for a feature, treat that feature item as incomplete until both implementation evidence and the matching verification output are present. Do not accept a separate unchecked completion-criteria item as a substitute.
|
||||
- If a checklist item contains integrated verification for a feature, run its applicable verification before the verdict. When the implementation exists and fresh verification passes, fill or correct the matching output and checklist state instead of treating the missing handoff evidence as incomplete implementation. Do not accept a separate unchecked completion-criteria item as a substitute.
|
||||
- Confirm the implementation marked the matching checklist items in the active review file, including the mandatory `CODE_REVIEW-*-G??.md` evidence item; repair clear artifact drift when evidence supports completion.
|
||||
- Treat review artifact gaps as failures only when they prevent judging implementation correctness, tests, contracts, or verification trust.
|
||||
- Repair review artifact gaps from fresh reviewer evidence. Treat a gap as a finding only when it is itself a required durable deliverable or remains impossible to reconstruct after the reviewer exhausts every safe, authorized repository-declared verification route.
|
||||
- Treat every generic `상태` field and implementation blocker record as ordinary evidence, never as a request to stop for the user. Evaluate both user-review gate types independently only after a WARN/FAIL finding requires a next state.
|
||||
- Grep renamed/removed symbols for stale references.
|
||||
- Confirm every required test exists, name matches, and assertions are meaningful.
|
||||
|
|
@ -192,8 +194,8 @@ Required fields for canonical English active pairs:
|
|||
|
||||
- `Overall Verdict`: exactly `PASS`, `WARN`, or `FAIL`.
|
||||
- `Dimension Assessment`: Pass/Warn/Fail for correctness, completeness, test coverage, API contract, code quality, implementation deviation, verification trust. If SDD Evidence Map applies through `milestone-task`, also include spec conformance.
|
||||
- `Findings`: `None`, or bullets using stable `Required R1`, `Required R2`, `Suggested S1`, or `Suggested S2` ids with `file:line` and a concrete fix; Nit findings do not need ids. Keep every Required/Suggested id unchanged in the follow-up handoff and plan.
|
||||
- `Routing Signals`: calculate once and append `review_rework_count=<N>` and `evidence_integrity_failure=true|false`. Set rework count to archived same-task `WARN|FAIL` verdicts plus one only when the current verdict is non-PASS. Set integrity failure to true only when a claimed test, command, exit code, or production path is absent, unexecuted, or contradicted by fresh reviewer evidence.
|
||||
- `Findings`: `None`, or bullets using stable `Required R1`, `Required R2`, `Suggested S1`, or `Suggested S2` ids. Every Required/Suggested finding must contain `Evidence` with the reviewer-run command/observation, `Root Cause` with exact source/runtime ownership, and `Selected Fix` with concrete file/symbol/behavior/test changes. Diagnostic-only findings, unresolved alternatives, and instructions for the worker to investigate are invalid. Nit findings do not need ids. Keep every Required/Suggested id and its reviewer-owned decision unchanged in the follow-up handoff and plan.
|
||||
- `Routing Signals`: calculate once and append `review_rework_count=<N>` and `evidence_integrity_failure=true|false`. Set rework count to archived same-task `WARN|FAIL` verdicts plus one only when the current verdict is non-PASS. Set integrity failure to true only when a claimed test, command, exit code, or production path is contradicted by fresh reviewer evidence, or required evidence remains unavailable or unverifiable after reviewer-owned verification. Missing implementation-recorded output that the reviewer successfully reconstructs does not set this signal.
|
||||
- `Next Step`: keep only the matching PASS, WARN/FAIL follow-up, or USER_REVIEW line.
|
||||
|
||||
For legacy active pairs, use the equivalent legacy field labels: `종합 판정`, `차원별 평가`, `발견된 문제`, `라우팅 신호`, `다음 단계`.
|
||||
|
|
@ -210,7 +212,7 @@ Severity semantics:
|
|||
|
||||
Issue severity:
|
||||
|
||||
- `Required`: correctness, API contract, missing required test, missing integrated verification, plan-completeness issue, or review artifact gaps that prevent judging implementation quality.
|
||||
- `Required`: correctness, API contract, a missing required test implementation, a failing reviewer-run required verification, a plan-completeness issue, or a required durable evidence gap that remains after reviewer-owned verification. Missing or incomplete implementation-recorded verification output alone is not `Required`.
|
||||
- `Suggested`: useful improvement that should enter the loop but does not block correctness.
|
||||
- `Nit`: tiny cleanup; directly repair obvious non-behavioral cases when safe, otherwise record without forcing WARN.
|
||||
|
||||
|
|
@ -231,11 +233,11 @@ Do not archive WARN/FAIL files until the next-state content is fully prepared in
|
|||
|
||||
Reuse the routing signals appended in Step 4; do not recount verdict history for routing. Separately count the existing logs once for archive identity: set `current_review_archive_number=count(code_review_*.log)` and `current_plan_archive_number=count(plan_*.log)`, then derive both archive names from the current active files' own lane/grade. These archive values describe the pair being closed, not the next route.
|
||||
|
||||
The follow-up handoff contains the selected `{task_name}`, revalidated outcome/acceptance/exclusions, current verdict and stable Required/Suggested ids, affected files, actual verification output, each id's `direct-fix` or `verified-dependency` disposition and exact evidence, roadmap carryover, routing signals, `REVIEW_<PARENT_TAG>`, and predicted current-pair archive names. Keep current active paths only as evidence pointers. Omit prior lane, grade, routing score, rationale, filename, and preferred next route from routing input. The plan may use current archive names only after routing to render `Archive Evidence Snapshot`.
|
||||
The follow-up handoff contains the selected `{task_name}`, revalidated outcome/acceptance/exclusions, current verdict and stable Required/Suggested ids, reviewer-collected evidence and actual verification output, reviewer-determined root cause, one selected fix with exact files/symbols/behavior/tests, each id's `direct-fix` or `verified-dependency` disposition, acceptance commands, roadmap carryover, routing signals, `REVIEW_<PARENT_TAG>`, and predicted current-pair archive names. Keep current active paths only as evidence pointers. Omit prior lane, grade, routing score, rationale, filename, and preferred next route from routing input. Do not invoke the plan skill until every id has this closed reviewer decision; collect missing data and finish the analysis in the current review pass. The plan may use current archive names only after routing to render `Archive Evidence Snapshot`.
|
||||
|
||||
- `prepare-follow-up` must return `status: routed`, the exact routed basenames, `prepared_plan`, `prepared_review`, `plan_number`, `current_plan_archive_name`, `current_plan_archive_number`, `current_review_archive_name`, `current_review_archive_number`, `plan_log_number`, `review_log_number`, and `gitignore_repair_needed`. It must have executed `finalize-task-routing` in `isolated-reassessment` mode.
|
||||
- Verify that the returned current archive names/numbers equal the values derived before preparation, and that `plan_log_number` / `review_log_number` are the post-archive counts embedded in the new review stub for its future archive.
|
||||
- Before archiving either active file, inspect `prepared_plan` directly. Require one non-empty `Finding Resolution Map` that maps every Required/Suggested id exactly once; every `direct-fix` target appears in `Modified Files Summary`; every `verified-dependency` has the exact task-protocol/current-state proof above; and a planned change or satisfied dependency makes repeated verification meaningful. Reuse the existing review/plan analysis; do not add a separate model pass.
|
||||
- Before archiving either active file, inspect `prepared_plan` directly. Require one non-empty `Finding Resolution Map` that maps every Required/Suggested id exactly once and preserves its reviewer-collected evidence, root cause, selected fix, and acceptance commands; every `direct-fix` target appears in `Modified Files Summary`; every `verified-dependency` has the exact task-protocol/current-state proof above; and a planned change or satisfied dependency makes repeated verification meaningful. Reject any plan that asks the worker to diagnose, investigate, choose an approach, or recover evidence the reviewer could collect. Reuse the existing review analysis; do not add a separate model pass.
|
||||
- If preparation returns `needs_evidence`, collect all named new evidence and rerun after the input changes; never rerun with unchanged evidence. If the evidence cannot be obtained in the current scope, leave the verdict-appended pair in place and report the exact finalization blocker.
|
||||
- If preparation returns `blocked` or the direct prepared-PLAN check fails, correct the handoff/plan from already collected evidence in the same review pass. If exact new evidence is genuinely required, leave the verdict-appended active PLAN/CODE_REVIEW pair in place, do not check archive/next-state items, and report a resumable internal finalization blocker. A later code-review invocation resumes this step without appending another verdict; this condition is not user review by itself.
|
||||
- For `dependency-wait`, keep one `### Finalization State` under the verdict with status, next task, resume condition, and checked state. Do no work while unchanged; when satisfied, mark it resolved with evidence and resume without another verdict.
|
||||
|
|
@ -304,7 +306,7 @@ Report Required/Suggested counts, archive names, the final task archive path for
|
|||
| Dimension | Check |
|
||||
|-----------|-------|
|
||||
| Correctness | Logic, edge cases, concurrency, errors |
|
||||
| Completeness | Planned implementation/verification items are done; review artifact drift is repaired when judgeable |
|
||||
| Completeness | Planned implementation is done; the reviewer runs applicable verification and repairs review artifact drift when judgeable |
|
||||
| Test coverage | Required tests present and meaningful |
|
||||
| API contract | Call sites, compatibility, docs |
|
||||
| Code quality | No debug prints, dead code, leftover TODOs |
|
||||
|
|
@ -318,6 +320,8 @@ Report Required/Suggested counts, archive names, the final task archive path for
|
|||
- Name exact stale symbols or missing tests.
|
||||
- Do not write vague praise or style opinions without a rule.
|
||||
- Every dimension gets Pass/Warn/Fail.
|
||||
- Run applicable required verification before assigning a verdict, and record the fresh command/output in `Verification Results`. Do not forward reviewer-executable evidence collection to another plan.
|
||||
- For every Required/Suggested finding, close `Evidence`, `Root Cause`, and one `Selected Fix` before invoking the plan skill. The worker implements that decision; it does not perform review analysis or choose the remedy.
|
||||
- For follow-up plans about verification trust, specify deterministic commands, for example `rg --sort path`, and forbid repo-local tool artifacts.
|
||||
|
||||
## Final Checklist
|
||||
|
|
@ -326,6 +330,7 @@ For `status=blocked`, keep the verdict pair and persist/report blocker, next tas
|
|||
|
||||
- `{current_review_archive_name}` exists with the verdict appended and was derived from the archived active review's own route.
|
||||
- `{current_plan_archive_name}` exists and was derived from the archived active plan's own route.
|
||||
- Applicable required verification was run by the reviewer and fresh command/output was recorded in `Verification Results`; any unavailable required route was exhausted and classified through the supported blocker/gate rules rather than forwarded as missing implementer evidence.
|
||||
- `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores local `agent-roadmap/current.md`; generated task artifacts are not ignored by `git check-ignore`.
|
||||
- No active `PLAN-*.md`, `CODE_REVIEW-*.md`, or `USER_REVIEW.md` remains after PASS or user-review-resolved PASS.
|
||||
- PASS or user-review-resolved PASS: `complete.log` written from `agent-ops/skills/common/code-review/templates/complete-log-template.md`, then task artifacts moved under `agent-task/archive/YYYY/MM/` with task-group path preserved for split work.
|
||||
|
|
|
|||
|
|
@ -45,16 +45,19 @@ Each target has:
|
|||
|
||||
- an opaque `agent` identity;
|
||||
- an opaque `model` identity;
|
||||
- optional `reasoning_effort`, stored as a separate opaque catalog value rather than embedded in dispatcher code or a literal command argument;
|
||||
- `execution_class`: `local_model` or `cloud_model`;
|
||||
- optional `selfcheck_required` boolean;
|
||||
- `runtime.command`: a non-empty argv template executed without a shell;
|
||||
- optional `runtime.resume_command`, `preflight_command`, `environment`, `session_path`, `native_session_monitor`, `terminal_success`, and `auxiliary_logs`;
|
||||
- optional `runtime.output_format`: `text` or `jsonl`.
|
||||
|
||||
Command templates may use only `{agent}`, `{model}`, `{target_id}`, `{workspace}`, `{attempt_dir}`, `{session_id}`, `{resume_session}`, `{resume_session_dir}`, and `{prompt}`. `native_session_monitor=true` requires both `resume_command` and `session_path`. `terminal_success=agent_end` requires JSONL output and accepts only a non-retrying final `agent_end` whose last assistant message has `stopReason=stop`; `error`, `aborted`, a missing event, or another stop reason fails closed. The catalog must not embed repository secrets; environment values should refer only to runtime-provided non-secret configuration.
|
||||
Command templates may use only `{agent}`, `{model}`, `{reasoning_effort}`, `{target_id}`, `{workspace}`, `{attempt_dir}`, `{session_id}`, `{resume_session}`, `{resume_session_dir}`, and `{prompt}`. A target with `reasoning_effort` must use `{reasoning_effort}` in its command and resume command when present; a target without the field cannot use that placeholder. `native_session_monitor=true` requires both `resume_command` and `session_path`. `terminal_success=agent_end` requires JSONL output and accepts only a non-retrying final `agent_end` whose last assistant message has `stopReason=stop`; `error`, `aborted`, a missing event, or another stop reason fails closed. The catalog must not embed repository secrets; environment values should refer only to runtime-provided non-secret configuration.
|
||||
|
||||
Each route owns its ordered `candidates` plus optional `rule_id`, `policy_priority`, and `reason_codes`. A route may use catalog-owned `windows` instead of a fixed candidate list; every window supplies an IANA timezone, start/end time, and candidates. Exactly one window must match.
|
||||
|
||||
The bundled review routes vary model and reasoning effort by routed grade instead of fixing every review to one target: G01-G04 use Terra/high, G05-G08 use Sol/high, and G09-G10 use Sol/xhigh. Runtime or project catalog overrides may replace this default tiering.
|
||||
|
||||
Before work starts, the dispatcher:
|
||||
|
||||
1. resolves and validates the entire catalog;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"pi-ornith-high": {
|
||||
"agent": "pi",
|
||||
"model": "ornith:35b",
|
||||
"reasoning_effort": "high",
|
||||
"execution_class": "local_model",
|
||||
"selfcheck_required": true,
|
||||
"runtime": {
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
"--model",
|
||||
"{model}",
|
||||
"--thinking",
|
||||
"high",
|
||||
"{reasoning_effort}",
|
||||
"--session-id",
|
||||
"{session_id}",
|
||||
"--session-dir",
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
"--model",
|
||||
"{model}",
|
||||
"--thinking",
|
||||
"high",
|
||||
"{reasoning_effort}",
|
||||
"--session",
|
||||
"{resume_session}",
|
||||
"--session-dir",
|
||||
|
|
@ -118,6 +119,7 @@
|
|||
"opencode-glm-medium": {
|
||||
"agent": "opencode",
|
||||
"model": "glm-5.2",
|
||||
"reasoning_effort": "medium",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -133,7 +135,7 @@
|
|||
"--model",
|
||||
"iop-glm/glm-5.2",
|
||||
"--variant",
|
||||
"medium",
|
||||
"{reasoning_effort}",
|
||||
"--auto",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -143,6 +145,7 @@
|
|||
"opencode-glm-high": {
|
||||
"agent": "opencode",
|
||||
"model": "glm-5.2",
|
||||
"reasoning_effort": "high",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -158,7 +161,7 @@
|
|||
"--model",
|
||||
"iop-glm/glm-5.2",
|
||||
"--variant",
|
||||
"high",
|
||||
"{reasoning_effort}",
|
||||
"--auto",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -168,6 +171,7 @@
|
|||
"opencode-glm-max": {
|
||||
"agent": "opencode",
|
||||
"model": "glm-5.2",
|
||||
"reasoning_effort": "high",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -183,7 +187,7 @@
|
|||
"--model",
|
||||
"iop-glm/glm-5.2",
|
||||
"--variant",
|
||||
"max",
|
||||
"{reasoning_effort}",
|
||||
"--auto",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -193,6 +197,7 @@
|
|||
"claude-opus-xhigh": {
|
||||
"agent": "claude",
|
||||
"model": "claude-opus-5",
|
||||
"reasoning_effort": "xhigh",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -207,7 +212,7 @@
|
|||
"--model",
|
||||
"{model}",
|
||||
"--effort",
|
||||
"xhigh",
|
||||
"{reasoning_effort}",
|
||||
"--dangerously-skip-permissions",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -217,6 +222,7 @@
|
|||
"codex-spark-xhigh": {
|
||||
"agent": "codex",
|
||||
"model": "gpt-5.3-codex-spark",
|
||||
"reasoning_effort": "xhigh",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -229,7 +235,30 @@
|
|||
"-m",
|
||||
"{model}",
|
||||
"-c",
|
||||
"model_reasoning_effort=\"xhigh\"",
|
||||
"model_reasoning_effort=\"{reasoning_effort}\"",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"{prompt}"
|
||||
],
|
||||
"output_format": "jsonl"
|
||||
}
|
||||
},
|
||||
"codex-sol-high": {
|
||||
"agent": "codex",
|
||||
"model": "gpt-5.6-sol",
|
||||
"reasoning_effort": "high",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
"command": [
|
||||
"codex",
|
||||
"exec",
|
||||
"--json",
|
||||
"-C",
|
||||
"{workspace}",
|
||||
"-m",
|
||||
"{model}",
|
||||
"-c",
|
||||
"model_reasoning_effort=\"{reasoning_effort}\"",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -239,6 +268,7 @@
|
|||
"codex-sol-xhigh": {
|
||||
"agent": "codex",
|
||||
"model": "gpt-5.6-sol",
|
||||
"reasoning_effort": "xhigh",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -251,7 +281,7 @@
|
|||
"-m",
|
||||
"{model}",
|
||||
"-c",
|
||||
"model_reasoning_effort=\"xhigh\"",
|
||||
"model_reasoning_effort=\"{reasoning_effort}\"",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -261,6 +291,7 @@
|
|||
"codex-terra-high": {
|
||||
"agent": "codex",
|
||||
"model": "gpt-5.6-terra",
|
||||
"reasoning_effort": "high",
|
||||
"execution_class": "cloud_model",
|
||||
"selfcheck_required": false,
|
||||
"runtime": {
|
||||
|
|
@ -273,7 +304,7 @@
|
|||
"-m",
|
||||
"{model}",
|
||||
"-c",
|
||||
"model_reasoning_effort=\"high\"",
|
||||
"model_reasoning_effort=\"{reasoning_effort}\"",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"{prompt}"
|
||||
],
|
||||
|
|
@ -435,24 +466,24 @@
|
|||
}
|
||||
},
|
||||
"review": {
|
||||
"local-G01": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g01-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G02": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G03": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G04": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G05": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G06": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G07": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G08": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G01": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g01-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G02": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G03": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G04": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G05": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G06": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G07": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G08": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G09": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"local-G10": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G01": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g01-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G02": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G03": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G04": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G05": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G06": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G07": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G08": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G01": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g01-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G02": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G03": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G04": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G05": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G06": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G07": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G08": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G09": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
|
||||
"cloud-G10": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,11 +101,13 @@ VERDICT_SCHEMA_MATCHERS = tuple(
|
|||
(
|
||||
re.compile(rf"^##\s*{re.escape(heading)}[ \t]*$", re.MULTILINE),
|
||||
re.compile(
|
||||
rf"^(?:-\s*)?(?:\*\*)?{re.escape(label)}(?:\*\*)?\s*:\s*(PASS|WARN|FAIL)[ \t]*$",
|
||||
rf"^(?:-\s*)?(?:\*\*)?{re.escape(label)}(?:\*\*)?\s*:\s*"
|
||||
rf"(?:(PASS|WARN|FAIL)|`(PASS|WARN|FAIL)`)[ \t]*$",
|
||||
re.MULTILINE,
|
||||
),
|
||||
re.compile(
|
||||
rf"^###\s+{re.escape(label)}[ \t]*$\s*^(?:\*\*)?(PASS|WARN|FAIL)(?:\*\*)?[ \t]*$",
|
||||
rf"^###\s+{re.escape(label)}[ \t]*$\s*^(?:\*\*)?"
|
||||
rf"(?:(PASS|WARN|FAIL)|`(PASS|WARN|FAIL)`)(?:\*\*)?[ \t]*$",
|
||||
re.MULTILINE,
|
||||
),
|
||||
)
|
||||
|
|
@ -185,9 +187,8 @@ PROVIDER_TRANSPORT_FAILURES = frozenset(
|
|||
{"provider-connection", "provider-stream-disconnect"}
|
||||
)
|
||||
FAILURE_EVIDENCE_LIMIT = 2000
|
||||
# Used only to reject a stale locator whose dispatcher and agent PIDs are both
|
||||
# gone. A live process is inspected after silence; it is never killed solely by
|
||||
# this fallback clock.
|
||||
# Used to reject stale locators and to bound a live model response that stops
|
||||
# producing both stream and native-session progress outside tool execution.
|
||||
RUNTIME_FAILURE_PATTERNS = {
|
||||
"context-limit": [
|
||||
r"context (?:length|window)", r"maximum context", r"prompt is too long",
|
||||
|
|
@ -203,6 +204,7 @@ RUNTIME_FAILURE_PATTERNS = {
|
|||
"model-unavailable": [
|
||||
r"model.{0,40}(?:not found|unavailable)", r"overloaded",
|
||||
r"temporarily unavailable",
|
||||
r"reasoning[_ ]effort must be one of",
|
||||
],
|
||||
"provider-connection": [
|
||||
r"\bprovider[_ -]?tunnel[_ -]?error\b",
|
||||
|
|
@ -503,6 +505,7 @@ class AgentSpec:
|
|||
target_id: str | None = None
|
||||
execution_class: str = "cloud_model"
|
||||
selfcheck_required: bool = False
|
||||
reasoning_effort: str | None = None
|
||||
runtime: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def agent_spec_from_record(record: dict[str, Any]) -> AgentSpec | None:
|
||||
|
|
@ -522,8 +525,15 @@ def agent_spec_from_record(record: dict[str, Any]) -> AgentSpec | None:
|
|||
selfcheck_required = record.get("selfcheck_required", False)
|
||||
if not isinstance(selfcheck_required, bool):
|
||||
return None
|
||||
reasoning_effort = record.get("reasoning_effort")
|
||||
if reasoning_effort is not None and (
|
||||
not isinstance(reasoning_effort, str) or not reasoning_effort
|
||||
):
|
||||
return None
|
||||
native_resume = bool(runtime.get("native_session_monitor"))
|
||||
display = f"{cli}/{model}"
|
||||
if reasoning_effort is not None:
|
||||
display += f" {reasoning_effort}"
|
||||
return AgentSpec(
|
||||
cli,
|
||||
model,
|
||||
|
|
@ -532,6 +542,7 @@ def agent_spec_from_record(record: dict[str, Any]) -> AgentSpec | None:
|
|||
target_id=target_id,
|
||||
execution_class=execution_class,
|
||||
selfcheck_required=selfcheck_required,
|
||||
reasoning_effort=reasoning_effort,
|
||||
runtime=dict(runtime),
|
||||
)
|
||||
|
||||
|
|
@ -1729,14 +1740,18 @@ def agent_spec_from_decision(decision: dict[str, Any]) -> AgentSpec:
|
|||
f"selector catalog validation 실패: {exc}"
|
||||
) from exc
|
||||
runtime = dict(target.runtime)
|
||||
display = f"{target.agent}/{target.model}"
|
||||
if target.reasoning_effort is not None:
|
||||
display += f" {target.reasoning_effort}"
|
||||
return AgentSpec(
|
||||
target.agent,
|
||||
target.model,
|
||||
f"{target.agent}/{target.model}",
|
||||
display,
|
||||
native_resume=bool(runtime.get("native_session_monitor")),
|
||||
target_id=target.catalog_id,
|
||||
execution_class=target.execution_class,
|
||||
selfcheck_required=target.selfcheck_required,
|
||||
reasoning_effort=target.reasoning_effort,
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
|
|
@ -2307,11 +2322,59 @@ def implementation_review_errors(task: Task) -> list[str]:
|
|||
text = task.review.read_text(encoding="utf-8", errors="replace")
|
||||
checklist = markdown_section(text, IMPLEMENTATION_CHECKLIST_HEADINGS)
|
||||
checkbox_values = IMPLEMENTATION_CHECKBOX_RE.findall(checklist)
|
||||
if not checkbox_values or any(not value.strip() for value in checkbox_values):
|
||||
if not checkbox_values:
|
||||
return ["구현 체크리스트 미완료"]
|
||||
if (
|
||||
any(not value.strip() for value in checkbox_values)
|
||||
and not implementation_blocker_evidence_complete(text)
|
||||
):
|
||||
return ["구현 체크리스트 미완료"]
|
||||
return []
|
||||
|
||||
|
||||
def implementation_blocker_evidence_complete(text: str) -> bool:
|
||||
"""Allow official review to classify an externally blocked implementation.
|
||||
|
||||
Workers must not invent a verdict merely to bypass the checklist gate. The
|
||||
exception therefore requires both an explicit blocked runtime/status field
|
||||
and a concrete, non-placeholder resume condition. The official reviewer
|
||||
still owns USER_REVIEW, follow-up, and terminal-state classification.
|
||||
"""
|
||||
blocked_status = re.search(
|
||||
r"(?im)^\s*(?:status\s*=\s*|authorization/runtime state:\s*`?)"
|
||||
r"(?:blocked|차단)\b",
|
||||
text,
|
||||
)
|
||||
if blocked_status is None:
|
||||
return False
|
||||
for match in re.finditer(
|
||||
r"(?im)^\s*(?:resume condition|재개 조건)\s*:\s*(.+?)\s*$",
|
||||
text,
|
||||
):
|
||||
value = match.group(1).strip().strip("`").strip()
|
||||
lowered = value.lower()
|
||||
if (
|
||||
len(value) < 8
|
||||
or "<" in value
|
||||
or ">" in value
|
||||
or lowered in {"none", "n/a", "na", "없음"}
|
||||
or any(
|
||||
token in lowered
|
||||
for token in (
|
||||
"actual state",
|
||||
"exact condition",
|
||||
"none or",
|
||||
"placeholder",
|
||||
"tbd",
|
||||
"todo",
|
||||
)
|
||||
)
|
||||
):
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def classify_failure_with_evidence(output: str) -> tuple[str, str | None]:
|
||||
lines = output.splitlines()
|
||||
for category, patterns in RUNTIME_FAILURE_PATTERNS.items():
|
||||
|
|
@ -2620,6 +2683,7 @@ def native_session_path(
|
|||
"attempt_dir": str(attempt_dir),
|
||||
"model": spec.model,
|
||||
"prompt": "",
|
||||
"reasoning_effort": str(spec.reasoning_effort or ""),
|
||||
"resume_session": "",
|
||||
"resume_session_dir": "",
|
||||
"session_id": session_id,
|
||||
|
|
@ -3060,6 +3124,7 @@ def build_command(
|
|||
"attempt_dir": str(attempt_dir),
|
||||
"model": spec.model,
|
||||
"prompt": prompt,
|
||||
"reasoning_effort": str(spec.reasoning_effort or ""),
|
||||
"resume_session": str(native_resume_session or ""),
|
||||
"resume_session_dir": (
|
||||
str(native_resume_session.parent)
|
||||
|
|
@ -3094,6 +3159,7 @@ def preflight_execution_catalog(
|
|||
"attempt_dir": str(catalog_path.parent),
|
||||
"model": target.model,
|
||||
"prompt": "",
|
||||
"reasoning_effort": str(target.reasoning_effort or ""),
|
||||
"resume_session": "",
|
||||
"resume_session_dir": "",
|
||||
"session_id": "preflight-session",
|
||||
|
|
@ -3219,6 +3285,7 @@ async def invoke(
|
|||
"target_id": spec.target_id,
|
||||
"execution_class": spec.execution_class,
|
||||
"selfcheck_required": spec.selfcheck_required,
|
||||
"reasoning_effort": spec.reasoning_effort,
|
||||
"runtime": spec.runtime,
|
||||
"agent_process_marker": process_marker,
|
||||
"plan_path": str(task.plan) if task.plan else None,
|
||||
|
|
@ -3240,6 +3307,7 @@ async def invoke(
|
|||
"attempt_dir": str(attempt_dir),
|
||||
"model": spec.model,
|
||||
"prompt": "",
|
||||
"reasoning_effort": str(spec.reasoning_effort or ""),
|
||||
"resume_session": str(native_resume_session or ""),
|
||||
"resume_session_dir": (
|
||||
str(native_resume_session.parent)
|
||||
|
|
@ -3346,6 +3414,7 @@ async def invoke(
|
|||
diagnostics: list[str] = []
|
||||
diagnostic_origins: list[str] = []
|
||||
control_violation: str | None = None
|
||||
session_stall_seconds: float | None = None
|
||||
terminal_success_contract = spec.runtime.get("terminal_success")
|
||||
terminal_success_seen = False
|
||||
try:
|
||||
|
|
@ -3354,6 +3423,7 @@ async def invoke(
|
|||
"attempt_dir": str(attempt_dir),
|
||||
"model": spec.model,
|
||||
"prompt": prompt,
|
||||
"reasoning_effort": str(spec.reasoning_effort or ""),
|
||||
"resume_session": str(native_resume_session or ""),
|
||||
"resume_session_dir": (
|
||||
str(native_resume_session.parent)
|
||||
|
|
@ -3524,8 +3594,9 @@ async def invoke(
|
|||
spec.native_resume
|
||||
and not is_native_tool_execution
|
||||
and native_inactive_seconds >= MODEL_RESPONSE_STALL_SECONDS
|
||||
and "native_silence_inspection" not in record
|
||||
and session_stall_seconds is None
|
||||
):
|
||||
session_stall_seconds = native_inactive_seconds
|
||||
inspection = {
|
||||
"at": now_iso(),
|
||||
"silence_seconds": round(native_inactive_seconds, 3),
|
||||
|
|
@ -3534,13 +3605,14 @@ async def invoke(
|
|||
record["native_silence_inspection"] = inspection
|
||||
diagnostic = (
|
||||
f"native-session {native_phase} stream produced no update for "
|
||||
f"{native_inactive_seconds:.1f}s; recorded stream tail for inspection "
|
||||
"without terminating the model process"
|
||||
f"{native_inactive_seconds:.1f}s; terminating the stalled model "
|
||||
"process for native-session recovery"
|
||||
)
|
||||
heartbeat_log.write(f"[silence-inspection] {diagnostic}\n")
|
||||
heartbeat_log.flush()
|
||||
persist_locator_record()
|
||||
attempt_event(prefix, f"모델응답점검: {diagnostic}")
|
||||
attempt_event(prefix, f"모델응답정지: {diagnostic}")
|
||||
await terminate_process_group(process)
|
||||
non_native_inactive_seconds = loop.time() - max(
|
||||
last_native_progress_at, last_stream_progress_at
|
||||
)
|
||||
|
|
@ -3548,8 +3620,9 @@ async def invoke(
|
|||
not spec.native_resume
|
||||
and non_native_inactive_seconds
|
||||
>= MODEL_RESPONSE_STALL_SECONDS
|
||||
and "stream_silence_inspection" not in record
|
||||
and session_stall_seconds is None
|
||||
):
|
||||
session_stall_seconds = non_native_inactive_seconds
|
||||
inspection = {
|
||||
"at": now_iso(),
|
||||
"silence_seconds": round(non_native_inactive_seconds, 3),
|
||||
|
|
@ -3558,13 +3631,14 @@ async def invoke(
|
|||
record["stream_silence_inspection"] = inspection
|
||||
diagnostic = (
|
||||
f"{spec.cli} emitted no stream output or native-session event for "
|
||||
f"{non_native_inactive_seconds:.1f}s; recorded stream tail for inspection "
|
||||
"without terminating the model process"
|
||||
f"{non_native_inactive_seconds:.1f}s; terminating the stalled model "
|
||||
"process for retry recovery"
|
||||
)
|
||||
heartbeat_log.write(f"[silence-inspection] {diagnostic}\n")
|
||||
heartbeat_log.flush()
|
||||
persist_locator_record()
|
||||
attempt_event(prefix, f"모델응답점검: {diagnostic}")
|
||||
attempt_event(prefix, f"모델응답정지: {diagnostic}")
|
||||
await terminate_process_group(process)
|
||||
heartbeat = (
|
||||
f"작업중... locator={locator_path} "
|
||||
f"native_session={record.get('native_session_path') or 'none'} "
|
||||
|
|
@ -3700,6 +3774,11 @@ async def invoke(
|
|||
if diagnostic_origins[index] == failure_evidence_source:
|
||||
failure_evidence = diagnostics[index]
|
||||
break
|
||||
elif session_stall_seconds is not None:
|
||||
failure_class = "session-stall"
|
||||
failure_source = "dispatcher-stall-timeout"
|
||||
record["session_stall_seconds"] = round(session_stall_seconds, 3)
|
||||
record["termination_initiator"] = "dispatcher"
|
||||
elif return_code != 0 and termination is not None:
|
||||
failure_class = "process-terminated"
|
||||
failure_source = "process-termination"
|
||||
|
|
@ -4349,7 +4428,12 @@ def verdict_from_text(text: str) -> str | None:
|
|||
inline_matches = list(line_re.finditer(section))
|
||||
block_matches = list(block_re.finditer(section))
|
||||
matches = inline_matches + block_matches
|
||||
return matches[0].group(1) if len(matches) == 1 else None
|
||||
if len(matches) != 1:
|
||||
return None
|
||||
return next(
|
||||
(value for value in matches[0].groups() if value in {"PASS", "WARN", "FAIL"}),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
def matching_archive_directories_by_name(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ ALLOWED_TEMPLATE_FIELDS = {
|
|||
"attempt_dir",
|
||||
"model",
|
||||
"prompt",
|
||||
"reasoning_effort",
|
||||
"resume_session",
|
||||
"resume_session_dir",
|
||||
"session_id",
|
||||
|
|
@ -44,6 +45,7 @@ class RouteTarget:
|
|||
catalog_id: str
|
||||
agent: str
|
||||
model: str
|
||||
reasoning_effort: str | None
|
||||
execution_class: str
|
||||
selfcheck_required: bool
|
||||
runtime: dict[str, Any]
|
||||
|
|
@ -191,6 +193,7 @@ def _validate_target(target_id: str, value: object) -> RouteTarget:
|
|||
unknown = set(value) - {
|
||||
"agent",
|
||||
"model",
|
||||
"reasoning_effort",
|
||||
"execution_class",
|
||||
"selfcheck_required",
|
||||
"runtime",
|
||||
|
|
@ -206,13 +209,51 @@ def _validate_target(target_id: str, value: object) -> RouteTarget:
|
|||
selfcheck_required = value.get("selfcheck_required", False)
|
||||
if not isinstance(selfcheck_required, bool):
|
||||
raise CatalogError(f"{label}.selfcheck_required must be a boolean")
|
||||
reasoning_effort_value = value.get("reasoning_effort")
|
||||
reasoning_effort = (
|
||||
None
|
||||
if reasoning_effort_value is None
|
||||
else _require_string(reasoning_effort_value, f"{label}.reasoning_effort")
|
||||
)
|
||||
runtime = _validate_runtime(value.get("runtime"), f"{label}.runtime")
|
||||
command_uses_effort = any(
|
||||
"{reasoning_effort}" in part for part in runtime["command"]
|
||||
)
|
||||
effort_templates = list(runtime["command"])
|
||||
for field in ("resume_command", "preflight_command", "auxiliary_logs"):
|
||||
effort_templates.extend(runtime.get(field, []))
|
||||
effort_templates.extend(runtime.get("environment", {}).values())
|
||||
if runtime.get("session_path") is not None:
|
||||
effort_templates.append(runtime["session_path"])
|
||||
runtime_uses_effort = any(
|
||||
"{reasoning_effort}" in part for part in effort_templates
|
||||
)
|
||||
if reasoning_effort is None and runtime_uses_effort:
|
||||
raise CatalogError(
|
||||
f"{label}.runtime uses reasoning_effort without "
|
||||
f"{label}.reasoning_effort"
|
||||
)
|
||||
if reasoning_effort is not None and not command_uses_effort:
|
||||
raise CatalogError(
|
||||
f"{label}.reasoning_effort requires "
|
||||
f"{{reasoning_effort}} in {label}.runtime.command"
|
||||
)
|
||||
resume_command = runtime.get("resume_command")
|
||||
if reasoning_effort is not None and resume_command is not None and not any(
|
||||
"{reasoning_effort}" in part for part in resume_command
|
||||
):
|
||||
raise CatalogError(
|
||||
f"{label}.reasoning_effort requires "
|
||||
f"{{reasoning_effort}} in {label}.runtime.resume_command"
|
||||
)
|
||||
return RouteTarget(
|
||||
catalog_id=target_id,
|
||||
agent=_require_string(value.get("agent"), f"{label}.agent"),
|
||||
model=_require_string(value.get("model"), f"{label}.model"),
|
||||
reasoning_effort=reasoning_effort,
|
||||
execution_class=execution_class,
|
||||
selfcheck_required=selfcheck_required,
|
||||
runtime=_validate_runtime(value.get("runtime"), f"{label}.runtime"),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -152,13 +152,16 @@ def _work_unit_id(header: tuple[str, int, str, str | None]) -> str:
|
|||
|
||||
|
||||
def _target_snapshot(target) -> dict:
|
||||
return {
|
||||
snapshot = {
|
||||
"target_id": target.catalog_id,
|
||||
"agent": target.agent,
|
||||
"model": target.model,
|
||||
"execution_class": target.execution_class,
|
||||
"selfcheck_required": target.selfcheck_required,
|
||||
}
|
||||
if target.reasoning_effort is not None:
|
||||
snapshot["reasoning_effort"] = target.reasoning_effort
|
||||
return snapshot
|
||||
|
||||
|
||||
def _candidate_snapshot(target, rank: int) -> dict:
|
||||
|
|
@ -189,6 +192,13 @@ def _validate_target_snapshot(value: object, prefix: str) -> dict:
|
|||
)
|
||||
if not isinstance(value["selfcheck_required"], bool):
|
||||
raise SelectorInputError(code, f"{prefix}.selfcheck_required must be a boolean")
|
||||
reasoning_effort = value.get("reasoning_effort")
|
||||
if reasoning_effort is not None and (
|
||||
not isinstance(reasoning_effort, str) or not reasoning_effort
|
||||
):
|
||||
raise SelectorInputError(
|
||||
code, f"{prefix}.reasoning_effort must be a non-empty string"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -149,11 +149,13 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase):
|
|||
"opaque-model",
|
||||
"opaque-agent/opaque-model",
|
||||
target_id="opaque-id",
|
||||
reasoning_effort="high",
|
||||
runtime={
|
||||
"command": ["runner", "{workspace}", "{model}", "{session_id}", "{attempt_dir}", "{prompt}"],
|
||||
"command": ["runner", "{workspace}", "{model}", "{reasoning_effort}", "{session_id}", "{attempt_dir}", "{prompt}"],
|
||||
"resume_command": [
|
||||
"runner",
|
||||
"resume",
|
||||
"{reasoning_effort}",
|
||||
"{resume_session}",
|
||||
"{resume_session_dir}",
|
||||
"{prompt}",
|
||||
|
|
@ -175,18 +177,64 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase):
|
|||
Path("/attempt"),
|
||||
native_resume_session=Path("/attempt/session.jsonl"),
|
||||
)
|
||||
self.assertEqual(command, ["runner", "/workspace", "opaque-model", "session-1", "/attempt", "do work"])
|
||||
self.assertEqual(command, ["runner", "/workspace", "opaque-model", "high", "session-1", "/attempt", "do work"])
|
||||
self.assertEqual(
|
||||
resumed,
|
||||
[
|
||||
"runner",
|
||||
"resume",
|
||||
"high",
|
||||
"/attempt/session.jsonl",
|
||||
"/attempt",
|
||||
"continue",
|
||||
],
|
||||
)
|
||||
|
||||
def test_json_only_reasoning_effort_change_controls_command(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
value = catalog_value()
|
||||
primary = value["targets"]["primary"]
|
||||
primary["reasoning_effort"] = "low"
|
||||
primary["runtime"]["command"].insert(-1, "{reasoning_effort}")
|
||||
primary["runtime"]["resume_command"].insert(
|
||||
-1, "{reasoning_effort}"
|
||||
)
|
||||
catalog = write_catalog(root, value)
|
||||
plan = write_plan(root)
|
||||
selector = dispatch._selector_module()
|
||||
|
||||
low_decision = selector.select_execution_target(
|
||||
plan, catalog_path=catalog
|
||||
)
|
||||
low_spec = dispatch.agent_spec_from_decision(low_decision)
|
||||
low_command = dispatch.build_command(
|
||||
low_spec,
|
||||
"work",
|
||||
root,
|
||||
"session-1",
|
||||
root / "attempt",
|
||||
)
|
||||
|
||||
primary["reasoning_effort"] = "high"
|
||||
catalog.write_text(json.dumps(value), encoding="utf-8")
|
||||
high_decision = selector.select_execution_target(
|
||||
plan, catalog_path=catalog
|
||||
)
|
||||
high_spec = dispatch.agent_spec_from_decision(high_decision)
|
||||
high_command = dispatch.build_command(
|
||||
high_spec,
|
||||
"work",
|
||||
root,
|
||||
"session-2",
|
||||
root / "attempt",
|
||||
)
|
||||
|
||||
self.assertIn("low", low_command)
|
||||
self.assertNotIn("high", low_command)
|
||||
self.assertIn("high", high_command)
|
||||
self.assertNotIn("low", high_command)
|
||||
|
||||
def test_preflight_checks_executable_and_optional_probe(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
|
|
@ -443,6 +491,61 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase):
|
|||
self.assertEqual(record["status"], "succeeded")
|
||||
self.assertIn("succeeded:0", work_log)
|
||||
|
||||
def test_silent_native_session_is_terminated_and_classified_as_stall(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
plan = write_plan(root)
|
||||
task = task_from_plan(root, plan)
|
||||
runner = root / "silent_runner.py"
|
||||
runner.write_text(
|
||||
"import time\n"
|
||||
"time.sleep(30)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
agent = dispatch.AgentSpec(
|
||||
"silent-runner",
|
||||
"silent-model",
|
||||
"silent-runner/silent-model",
|
||||
native_resume=True,
|
||||
target_id="silent-target",
|
||||
runtime={
|
||||
"command": [sys.executable, str(runner)],
|
||||
"native_session_monitor": True,
|
||||
"session_path": "sessions/{session_id}.jsonl",
|
||||
},
|
||||
)
|
||||
with (
|
||||
mock.patch.dict(
|
||||
os.environ,
|
||||
{"XDG_STATE_HOME": str(root / "state")},
|
||||
),
|
||||
mock.patch.object(dispatch, "STREAM_HEARTBEAT_SECONDS", 0.01),
|
||||
mock.patch.object(dispatch, "MODEL_RESPONSE_STALL_SECONDS", 0.05),
|
||||
):
|
||||
store = dispatch.StateStore(root)
|
||||
try:
|
||||
return_code, failure, locator = asyncio.run(
|
||||
dispatch.invoke(
|
||||
root,
|
||||
store,
|
||||
task,
|
||||
"worker",
|
||||
agent,
|
||||
"fake prompt",
|
||||
)
|
||||
)
|
||||
record = json.loads(locator.read_text(encoding="utf-8"))
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
self.assertLess(return_code, 0)
|
||||
self.assertEqual(failure, "session-stall")
|
||||
self.assertEqual(record["status"], "failed")
|
||||
self.assertEqual(record["failure_source"], "dispatcher-stall-timeout")
|
||||
self.assertEqual(record["termination_initiator"], "dispatcher")
|
||||
self.assertGreaterEqual(record["session_stall_seconds"], 0.05)
|
||||
self.assertIn("native_silence_inspection", record)
|
||||
|
||||
def test_catalog_source_is_in_runtime_audit_evidence(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
|
|
@ -456,6 +559,90 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase):
|
|||
|
||||
|
||||
class GenericDispatcherContractTests(unittest.TestCase):
|
||||
def test_official_review_owns_reconstructable_verification_evidence(self):
|
||||
common_skills = Path(__file__).parents[2]
|
||||
review_skill = (common_skills / "code-review" / "SKILL.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
review_template = (
|
||||
common_skills / "plan" / "templates" / "review-stub-template.md"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
self.assertIn(
|
||||
"Verification, defect analysis, and fix selection are review-agent-owned at verdict time.",
|
||||
review_skill,
|
||||
)
|
||||
self.assertIn(
|
||||
"Missing or incomplete implementation-recorded verification output alone is not `Required`.",
|
||||
review_skill,
|
||||
)
|
||||
self.assertIn(
|
||||
"do not delegate diagnosis or solution selection to the plan skill or implementing agent",
|
||||
review_skill,
|
||||
)
|
||||
self.assertIn(
|
||||
"Run applicable required verification and record fresh command/output",
|
||||
review_template,
|
||||
)
|
||||
plan_skill = (common_skills / "plan" / "SKILL.md").read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"treat the reviewer's closed finding packet as the decision authority",
|
||||
plan_skill,
|
||||
)
|
||||
self.assertIn("contains no worker-owned diagnosis", plan_skill)
|
||||
|
||||
def _implementation_review_errors(self, text: str) -> list[str]:
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
plan = write_plan(root)
|
||||
task = task_from_plan(root, plan)
|
||||
review = plan.parent / "CODE_REVIEW-cloud-G05.md"
|
||||
review.write_text(text, encoding="utf-8")
|
||||
task.review = review
|
||||
return dispatch.implementation_review_errors(task)
|
||||
|
||||
def test_explicit_blocker_evidence_can_reach_official_review(self):
|
||||
errors = self._implementation_review_errors(
|
||||
"## Overview\n\n"
|
||||
"status=BLOCKED — external runtime input is missing\n\n"
|
||||
"## Implementation Checklist\n\n"
|
||||
"- [x] Captured available evidence.\n"
|
||||
"- [ ] Run the external verification after access is ready.\n\n"
|
||||
"## Verification Results\n\n"
|
||||
"Resume condition: Register the missing route and provide the named runtime input.\n"
|
||||
)
|
||||
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
def test_blocker_placeholder_does_not_bypass_checklist_gate(self):
|
||||
errors = self._implementation_review_errors(
|
||||
"## Overview\n\n"
|
||||
"status=BLOCKED\n\n"
|
||||
"## Implementation Checklist\n\n"
|
||||
"- [x] Captured available evidence.\n"
|
||||
"- [ ] Run the external verification.\n\n"
|
||||
"## Verification Results\n\n"
|
||||
"Resume condition: <none or exact condition>\n"
|
||||
)
|
||||
|
||||
self.assertEqual(errors, ["구현 체크리스트 미완료"])
|
||||
|
||||
def test_verdict_parser_accepts_inline_code_value(self):
|
||||
text = "## Code Review Result\n\n- Overall Verdict: `FAIL`\n"
|
||||
|
||||
self.assertEqual(dispatch.verdict_from_text(text), "FAIL")
|
||||
|
||||
def test_invalid_reasoning_effort_is_target_unavailable(self):
|
||||
output = (
|
||||
'{"type":"error","error":{"data":{"message":'
|
||||
'"reasoning_effort must be one of none, low, medium, or high"}}}'
|
||||
)
|
||||
|
||||
failure, evidence = dispatch.classify_failure_with_evidence(output)
|
||||
|
||||
self.assertEqual(failure, "model-unavailable")
|
||||
self.assertIn("reasoning_effort", evidence or "")
|
||||
|
||||
def test_selfcheck_work_log_uses_worker_plan_artifact(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
directory = Path(tmp) / "agent-task" / "group" / "01_task"
|
||||
|
|
|
|||
|
|
@ -146,6 +146,46 @@ class ExecutionTargetPolicyTests(unittest.TestCase):
|
|||
with self.assertRaisesRegex(policy.CatalogError, "unsupported template field"):
|
||||
policy.load_catalog(write_catalog(root, bad_template))
|
||||
|
||||
def test_reasoning_effort_is_catalog_owned_and_template_bound(self):
|
||||
valid = catalog_value()
|
||||
valid["targets"]["target-b"]["reasoning_effort"] = "high"
|
||||
valid["targets"]["target-b"]["runtime"]["command"] = [
|
||||
"runner-b",
|
||||
"--effort",
|
||||
"{reasoning_effort}",
|
||||
"{prompt}",
|
||||
]
|
||||
missing_value = catalog_value()
|
||||
missing_value["targets"]["target-b"]["runtime"]["command"] = [
|
||||
"runner-b",
|
||||
"{reasoning_effort}",
|
||||
"{prompt}",
|
||||
]
|
||||
missing_environment_value = catalog_value()
|
||||
missing_environment_value["targets"]["target-b"]["runtime"][
|
||||
"environment"
|
||||
] = {"MODEL_EFFORT": "{reasoning_effort}"}
|
||||
unused_value = catalog_value()
|
||||
unused_value["targets"]["target-b"]["reasoning_effort"] = "high"
|
||||
with TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
loaded = policy.load_catalog(write_catalog(root, valid))
|
||||
self.assertEqual(loaded.targets["target-b"].reasoning_effort, "high")
|
||||
with self.assertRaisesRegex(
|
||||
policy.CatalogError, "uses reasoning_effort without"
|
||||
):
|
||||
policy.load_catalog(write_catalog(root, missing_value))
|
||||
with self.assertRaisesRegex(
|
||||
policy.CatalogError, "uses reasoning_effort without"
|
||||
):
|
||||
policy.load_catalog(
|
||||
write_catalog(root, missing_environment_value)
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
policy.CatalogError, "reasoning_effort requires"
|
||||
):
|
||||
policy.load_catalog(write_catalog(root, unused_value))
|
||||
|
||||
def test_command_executable_must_be_literal_for_preflight(self):
|
||||
value = catalog_value()
|
||||
value["targets"]["target-a"]["runtime"]["command"] = [
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class SelectorTests(unittest.TestCase):
|
|||
"opencode-glm-max",
|
||||
"claude-opus-xhigh",
|
||||
"codex-spark-xhigh",
|
||||
"codex-sol-high",
|
||||
"codex-sol-xhigh",
|
||||
"codex-terra-high",
|
||||
}
|
||||
|
|
@ -136,6 +137,13 @@ class SelectorTests(unittest.TestCase):
|
|||
)
|
||||
for lane in ("local", "cloud"):
|
||||
for grade in range(1, 11):
|
||||
expected = (
|
||||
["codex-terra-high"]
|
||||
if grade <= 4
|
||||
else ["codex-sol-high"]
|
||||
if grade <= 8
|
||||
else ["codex-sol-xhigh"]
|
||||
)
|
||||
decision = selector.policy.select_policy(
|
||||
catalog=catalog,
|
||||
stage="review",
|
||||
|
|
@ -145,7 +153,7 @@ class SelectorTests(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(
|
||||
[target.catalog_id for target in decision.candidates],
|
||||
["codex-sol-xhigh"],
|
||||
expected,
|
||||
)
|
||||
night = selector.policy.select_policy(
|
||||
catalog=catalog,
|
||||
|
|
@ -162,8 +170,10 @@ class SelectorTests(unittest.TestCase):
|
|||
catalog = selector.load_runtime_catalog()
|
||||
pi = catalog.targets["pi-ornith-high"]
|
||||
self.assertEqual((pi.agent, pi.model), ("pi", "ornith:35b"))
|
||||
self.assertEqual(pi.reasoning_effort, "high")
|
||||
self.assertTrue(pi.selfcheck_required)
|
||||
self.assertIn("--thinking", pi.runtime["command"])
|
||||
self.assertIn("{reasoning_effort}", pi.runtime["command"])
|
||||
self.assertTrue(pi.runtime["native_session_monitor"])
|
||||
self.assertEqual(pi.runtime["terminal_success"], "agent_end")
|
||||
self.assertIn("--session", pi.runtime["resume_command"])
|
||||
|
|
@ -173,11 +183,22 @@ class SelectorTests(unittest.TestCase):
|
|||
self.assertEqual(agy.runtime["auxiliary_logs"], ["{attempt_dir}/agy-cli.log"])
|
||||
opencode = catalog.targets["opencode-glm-max"]
|
||||
self.assertIn("iop-glm/glm-5.2", opencode.runtime["command"])
|
||||
self.assertIn("max", opencode.runtime["command"])
|
||||
self.assertEqual(opencode.reasoning_effort, "high")
|
||||
self.assertIn("{reasoning_effort}", opencode.runtime["command"])
|
||||
claude = catalog.targets["claude-opus-xhigh"]
|
||||
self.assertEqual((claude.agent, claude.model), ("claude", "claude-opus-5"))
|
||||
self.assertEqual(claude.reasoning_effort, "xhigh")
|
||||
self.assertIn("{reasoning_effort}", claude.runtime["command"])
|
||||
terra = catalog.targets["codex-terra-high"]
|
||||
self.assertIn('model_reasoning_effort="high"', terra.runtime["command"])
|
||||
self.assertEqual(terra.reasoning_effort, "high")
|
||||
self.assertIn(
|
||||
'model_reasoning_effort="{reasoning_effort}"',
|
||||
terra.runtime["command"],
|
||||
)
|
||||
sol_high = catalog.targets["codex-sol-high"]
|
||||
self.assertEqual(sol_high.reasoning_effort, "high")
|
||||
sol_xhigh = catalog.targets["codex-sol-xhigh"]
|
||||
self.assertEqual(sol_xhigh.reasoning_effort, "xhigh")
|
||||
|
||||
def test_initial_decision_contains_catalog_evidence_and_no_quota(self):
|
||||
with TemporaryDirectory() as tmp:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ This skill intentionally uses routed active files under an active task directory
|
|||
Invocation modes:
|
||||
|
||||
- `write` is the default for initial plans and explicit replans. After routing, this skill archives any prior active pair and writes the new pair.
|
||||
- `prepare-follow-up` is used only when code-review has appended WARN/FAIL but has not archived the active pair. This skill completes analysis, final routing, and exact PLAN/review-stub rendering in memory without mutating repository files. It returns `prepared_plan`, `prepared_review`, their routed basenames, the current pair's predicted archive names/numbers, and the post-archive log counts used by the new pair.
|
||||
- `prepare-follow-up` is used only when code-review has appended WARN/FAIL but has not archived the active pair. This skill validates the reviewer-owned evidence, root cause, selected fix, scope, and acceptance commands against current source, then completes final routing and exact PLAN/review-stub rendering in memory without mutating repository files. It must not redo diagnosis, choose another solution, or leave either decision to the implementing agent. It returns `prepared_plan`, `prepared_review`, their routed basenames, the current pair's predicted archive names/numbers, and the post-archive log counts used by the new pair.
|
||||
- Both modes execute the same mandatory Step 3. `prepare-follow-up` is not a route-only shortcut.
|
||||
|
||||
Optional context:
|
||||
|
|
@ -54,7 +54,7 @@ Filename rules:
|
|||
|
||||
Role boundary rules:
|
||||
|
||||
- Keep root cause, scope, ownership, and next-state decisions under plan/review, and validate their artifacts directly; dispatcher use is optional. Let implementing agents execute the packet without reinterpreting findings or changing its owner/write boundary.
|
||||
- First-pass analysis belongs to plan. After WARN/FAIL, evidence collection, root cause, solution selection, scope, ownership, and next-state decisions belong to code-review; `prepare-follow-up` only validates and materializes that closed reviewer packet. Let implementing agents execute it without investigating the cause, choosing among remedies, reinterpreting findings, or changing its owner/write boundary.
|
||||
- Implementing agents fill implementation-owned `CODE_REVIEW-*-G??.md` sections, keep active files in place, and report ready for review.
|
||||
- If implementation cannot continue, implementing agents record the exact blocker, attempted commands/output, and resume condition only in `Verification Results` or `Deviations from Plan` (legacy: `검증 결과` or `계획 대비 변경 사항`), then leave the active files in place for official review.
|
||||
- During implementation, do not ask the user directly, present choices, call user-input tools, or create control-plane stop files. The official reviewer owns all next-state classification.
|
||||
|
|
@ -192,16 +192,16 @@ Before choosing plan files or task directory names, apply the split decision pol
|
|||
|
||||
## Step 2 - Analyze Before Writing
|
||||
|
||||
Complete all items below before creating active plan/review files. Work through them in order; do not proceed to the next step until every checkbox is done. Keep the user request as the scope anchor and reconcile derived acceptance conditions before the split decision; do not create a separate routing summary. The only allowed file edits before writing plan/review files are local `agent-roadmap/current.md` creation or `.gitignore` block repair needed for roadmap routing.
|
||||
Complete all items below before creating active plan/review files. Work through them in order; do not proceed to the next step until every checkbox is done. Keep the user request as the scope anchor and reconcile derived acceptance conditions before the split decision; do not create a separate routing summary. In `prepare-follow-up`, treat the reviewer's closed finding packet as the decision authority: repository reads validate its consistency and supply implementation mechanics, but do not reopen root cause or solution selection. If required evidence, root cause, or a selected fix is missing or contradicted, return `needs_evidence` to code-review so the reviewer corrects it in the same review pass; never pass investigation or alternatives to the worker. The only allowed file edits before writing plan/review files are local `agent-roadmap/current.md` creation or `.gitignore` block repair needed for roadmap routing.
|
||||
|
||||
- [ ] **Resolve verification context** — because implementation plans include verification, consume supplied `verification_context` when present and confirm its source paths, commands, expected results, preconditions, constraints, gaps, and confidence still apply. When it is absent or incomplete, derive the missing facts from repository manifests, scripts, workflows, domain rules, related tests, user-provided environment facts, and safe read-only probes. Record which facts came from the handoff and which came from repository-native fallback evidence. A missing optional handoff is not a user-review blocker.
|
||||
- [ ] **Resolve verification context** — because implementation plans include verification, consume supplied `verification_context` when present and confirm its source paths, commands, expected results, preconditions, constraints, gaps, and confidence still apply. On first pass, derive missing facts from repository manifests, scripts, workflows, domain rules, related tests, user-provided environment facts, and safe read-only probes. In `prepare-follow-up`, require the reviewer to have collected every fact needed for diagnosis and fix selection; derive only mechanical command/path details, and return `needs_evidence` rather than performing missing review analysis. Record which facts came from the handoff and which came from repository-native validation. A missing optional first-pass handoff is not a user-review blocker.
|
||||
- [ ] **Read all source files in full** — read every source file the change will touch, whole file. No partial reads.
|
||||
- [ ] **Preflight external verification** — when any required verification leaves the current checkout, including remote runner, field/bootstrap, external provider, Docker/code-server, emulator/device, or shared long-running runtime, confirm or derive a read-only preflight before writing final verification commands. Record runner, repo root/workdir, branch/HEAD/dirty state, source sync status, binary/artifact paths, command help/version output needed by the verification, config path, runtime identity, ports/process state, external hosts, and OS/arch assumptions. If the preflight shows stale artifacts, dirty/divergent checkout, wrong identity, missing command, closed ports, host OS mismatch, or unsynced source, add an explicit setup/sync/rebuild step or report the blocker.
|
||||
- [ ] **Read all test files in full** — read every test file that exercises the changed behavior, including files identified by the verification context and repository test layout.
|
||||
- [ ] **Assess test coverage** — for each behavior change, explicitly record whether existing tests cover it.
|
||||
- [ ] **Assess split boundaries once** — reconcile request acceptance with source/tests, then split only where every child has a stable contract and independent PASS verification. Otherwise keep the invariant together; do not gather extra evidence solely to lower routing risk.
|
||||
- [ ] **Capture recovery signals once** — first-pass uses `review_rework_count=0` and `evidence_integrity_failure=false`. In `prepare-follow-up`, reuse the values already validated and appended by code-review; do not recount verdict history. For another isolated replan, derive them once from the same-task state already loaded for planning, without a routing-only log pass.
|
||||
- [ ] **Resolve follow-up findings once** — in `prepare-follow-up`, map every inherited Required/Suggested id. Default repository-fixable work to `direct-fix` with exact root-cause files, overriding stale verification-only exclusions. Allow `verified-dependency` only when an exact active PLAN claims those files and task-protocol ordering applies, or when `complete.log` plus fresh evidence proves the failed precondition is satisfied; vague owners or `complete.log` alone are invalid. Set `ownership_closed=true` only after all mappings are proven. Reject unchanged-precondition verification loops. Reuse the existing analysis; add no model, sub-agent, or routing-only pass.
|
||||
- [ ] **Resolve follow-up findings once** — in `prepare-follow-up`, require every inherited Required/Suggested id to carry reviewer-collected evidence, exact root cause, one selected fix, affected files/symbols/behavior/tests, acceptance commands, and a `direct-fix` or `verified-dependency` disposition. Validate those decisions against current source without replacing them. Allow `verified-dependency` only when an exact active PLAN claims those files and task-protocol ordering applies, or when `complete.log` plus fresh evidence proves the failed precondition is satisfied; vague owners or `complete.log` alone are invalid. Set `ownership_closed=true` only after all mappings are proven. Reject diagnostic-only packets, unresolved alternatives, unchanged-precondition verification loops, and any worker-facing investigation step. Reuse the reviewer analysis; add no model, sub-agent, or routing-only pass.
|
||||
- [ ] **Resolve split predecessor completion** — if the selected or proposed subtask directory has `NN+PP[,QQ...]_...`, resolve each predecessor index under the same task group. Check only the active and archive candidate patterns defined in the task directory naming rules. Record found active/archive paths, missing predecessors, or ambiguous matches in `Analysis > Split Judgment` (legacy: `분석 결과 > 분할 판단`) and, when order matters, `Dependencies and Execution Order` (legacy: `의존 관계 및 구현 순서`).
|
||||
- [ ] **Grep all symbol references** — for any renamed or removed symbol, find every call site and import chain.
|
||||
- [ ] **Check dependency manifests** — before adding any dependency, inspect the repository's relevant dependency manifest and lockfile, confirm whether it already exists, and follow the repository-native version and update policy.
|
||||
|
|
@ -257,8 +257,8 @@ Required sections:
|
|||
- `For the Implementing Agent`: warn that filling implementation-owned `CODE_REVIEW-*-G??.md` sections is mandatory. Tell the implementer to run verification, fill actual notes/output, keep active files in place, and report ready for review; finalization is code-review-skill only. If blocked, the implementer records only exact blocker evidence, attempted commands/output, and resume conditions in implementation-owned evidence fields. It must not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.
|
||||
- `Background`: 2-4 sentences explaining why the work is needed.
|
||||
- `Archive Evidence Snapshot`: include this section only when the plan resumes from `USER_REVIEW.md`, a prior archived review, or any archive evidence. Omit it for first-pass plans with no archive evidence. The section must contain only the archive facts needed to implement without rereading archive by default: prior task/archive paths, verdict, Required/Suggested/Nit summary, affected files, verification evidence, and any roadmap carryover. If exact prior context is still required, cite the specific archive file paths allowed to read; do not ask the implementer to search `agent-task/archive/**` broadly.
|
||||
- `Finding Resolution Map`: for WARN/FAIL follow-ups only, use one row per Required/Suggested id: mode, exact fix/dependency evidence, and changed/satisfied precondition. Put every `direct-fix` file in `Modified Files Summary`. For an unordered dependency, emit no worker packet; return `status=blocked`, `blocked_reason=dependency-wait`, `next-task`, and resume condition.
|
||||
- `Analysis`: record the findings from Step 2 and the final routed output from Step 3. This section is the written output of the analysis — not a summary, but the actual findings that justify the plan's scope and decisions. Must include all of the following subsections:
|
||||
- `Finding Resolution Map`: for WARN/FAIL follow-ups only, use one row per Required/Suggested id: reviewer evidence, exact root cause, selected fix, mode, changed/satisfied precondition, and acceptance commands. Put every `direct-fix` file in `Modified Files Summary`. Do not include investigation tasks or solution alternatives. For an unordered dependency, emit no worker packet; return `status=blocked`, `blocked_reason=dependency-wait`, `next-task`, and resume condition.
|
||||
- `Analysis`: on first pass, record the findings from Step 2 and the final routed output from Step 3. On WARN/FAIL follow-up, preserve the reviewer's analysis and selected fix, adding only source-consistency validation and routing facts; do not replace the diagnosis or propose alternatives. This section contains the actual findings that justify the plan's scope and decisions. Must include all of the following subsections:
|
||||
- `Files Read`: list every source and test file read during analysis, with path. List verification-context source files only when they were actually present and read.
|
||||
- `SDD Criteria`: for `SDD: 필요` Milestones, list the SDD path, status, first-line `milestone-task` ids, targeted Acceptance Scenario ids, and the Evidence Map rows that drive the plan. State explicitly how those rows shaped the implementation checklist and final verification. If the selected Milestone has `SDD: 불필요`, state the recorded reason. If the work is not Milestone-linked, state "not applicable".
|
||||
- `Verification Context`: state whether a handoff was supplied, every source path actually read, concrete commands/criteria applied, preconditions, constraints, gaps, confidence, and repository-native fallback evidence. If required verification leaves the current checkout, include an `External Verification Preflight` record with runner, repo root/workdir, branch/HEAD/dirty state, source sync status, binary/artifact paths, required command help/version output, config path, runtime identity, ports/process state, external hosts, OS/arch assumptions, and the exact setup/sync/rebuild step or blocker derived from mismatches.
|
||||
|
|
@ -281,7 +281,7 @@ Required sections:
|
|||
Each plan item must include:
|
||||
|
||||
- `Problem`: concrete problem with file:line references.
|
||||
- `Solution`: exact approach and before/after code block for non-trivial changes.
|
||||
- `Solution`: exact approach and before/after code block for non-trivial changes. In a WARN/FAIL follow-up, materialize the reviewer's selected fix exactly; do not ask the worker to investigate, analyze the cause, or choose an approach.
|
||||
- `Modified Files and Checklist`: exhaustive file-level checklist.
|
||||
- `Test Strategy`: explicit write/skip decision. If writing tests, include path, test name, assertion goal, and fixtures. If skipping, justify.
|
||||
- `Verification`: runnable commands and expected result.
|
||||
|
|
@ -369,6 +369,7 @@ Do not write or return a prepared pair when either routing target is not `routed
|
|||
- `finalize-task-routing` ran once after the PLAN body was complete, used no routing-only evidence pass, counted only positive packet-local risk, kept capability/grade basis from being relabeled by escalation signals, and produced matching filenames.
|
||||
- Review WARN/FAIL follow-ups entered through this plan skill and did not inherit or compare the archived lane/G.
|
||||
- Every WARN/FAIL finding has one proven direct fix or ordered/satisfied dependency; only then is ownership closed, and verification does not repeat against an unchanged precondition.
|
||||
- Every WARN/FAIL follow-up preserves reviewer-collected evidence, root cause, one selected fix, affected implementation/test targets, and acceptance commands; it contains no worker-owned diagnosis, investigation, or solution choice.
|
||||
- The plan's implementer instructions and review stub limit local implementation agents to implementation/test/evidence work and keep user-review classification plus control-plane stop files out of their input and ownership.
|
||||
- The review stub has a clearly marked `Review-Only Checklist` (legacy: `코드리뷰 전용 체크리스트`) owned only by the review agent.
|
||||
- Routed review file completion table lists every plan item.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ task={task_name}, plan={plan_number}, tag={TAG}
|
|||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Compare implementation of each item against source files. Run the applicable verification commands directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If implementation is present, repair missing or stale verification output instead of failing solely for insufficient recorded evidence. When verification exposes a defect, collect the necessary data, determine the exact root cause, and select one concrete fix before generating the follow-up plan; never delegate investigation or remedy selection to the worker.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
|
|
@ -51,6 +51,8 @@ Review completion means the following steps are finished:
|
|||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Run applicable required verification and record fresh command/output; repair reviewer-reconstructable evidence gaps instead of forwarding them to another plan.
|
||||
- [ ] For every Required/Suggested finding, record reviewer-collected `Evidence`, exact `Root Cause`, and one `Selected Fix` with affected files/symbols/tests and acceptance commands before creating a follow-up plan.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_{review_lane}_{review_grade}_{review_log_number}.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_{build_lane}_{build_grade}_{plan_log_number}.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
|
|
@ -93,5 +95,5 @@ _Record key design decisions here._
|
|||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Verification Results (section headings + commands) | Implementing agent, then review agent | Implementing agent records initial output; review agent reruns applicable commands and may fill, replace, or append fresh verified output before verdict. Implementing-agent command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
|
|||
|
|
@ -343,8 +343,22 @@ def validate_pairs(
|
|||
pairs.append((plan, review, header))
|
||||
union.update(target_ids)
|
||||
contents = plan.read_text(encoding="utf-8") + "\n" + review.read_text(encoding="utf-8")
|
||||
if "[TODO" in contents or "<task_group>" in contents or "<milestone-slug>" in contents:
|
||||
unresolved_contents = contents.replace("`m-<milestone-slug>`", "")
|
||||
if (
|
||||
"[TODO" in unresolved_contents
|
||||
or "<task_group>" in unresolved_contents
|
||||
or "<milestone-slug>" in unresolved_contents
|
||||
):
|
||||
raise CycleError(f"unresolved template token: {plan.parent}")
|
||||
for path in (plan, review):
|
||||
whitespace = run(
|
||||
["git", "diff", "--no-index", "--check", "--", "/dev/null", str(path)],
|
||||
cwd=workspace,
|
||||
check=False,
|
||||
)
|
||||
if whitespace.returncode not in {0, 1}:
|
||||
detail = (whitespace.stderr or whitespace.stdout or "").strip()
|
||||
raise CycleError(f"plan whitespace validation failed: {path}: {detail}")
|
||||
if dispatcher.is_file():
|
||||
run(
|
||||
[sys.executable, str(dispatcher), "--workspace", str(workspace), "--validate-plan", str(plan)],
|
||||
|
|
@ -374,6 +388,17 @@ def active_task_user_reviews(workspace: Path, task_group: str) -> list[Path]:
|
|||
return sorted(root.glob("USER_REVIEW.md")) + sorted(root.glob("*/USER_REVIEW.md"))
|
||||
|
||||
|
||||
def active_task_finalization_artifacts(workspace: Path, task_group: str) -> list[Path]:
|
||||
root = workspace / "agent-task" / task_group
|
||||
if not root.exists():
|
||||
return []
|
||||
values: set[Path] = set(root.rglob("complete.log"))
|
||||
for path in root.rglob("code_review_*.log"):
|
||||
if "\n## Code Review Result\n" in path.read_text(encoding="utf-8"):
|
||||
values.add(path)
|
||||
return sorted(values)
|
||||
|
||||
|
||||
def sdd_user_review(workspace: Path, phase_slug: str, milestone_slug: str) -> Path:
|
||||
return workspace / "agent-roadmap" / "sdd" / phase_slug / milestone_slug / "USER_REVIEW.md"
|
||||
|
||||
|
|
@ -387,6 +412,7 @@ def stage_prompt(
|
|||
task_group: str,
|
||||
base_head: str,
|
||||
checkpoint_head: str | None,
|
||||
recovery_reason: str | None = None,
|
||||
) -> str:
|
||||
common = f"""You are a fresh child agent launched for one bounded Epic preparation stage, not the caller or monitor.
|
||||
Work only in {workspace}.
|
||||
|
|
@ -398,6 +424,13 @@ Target Epic: [{epic.epic_id}] {epic.title}
|
|||
Allowed Milestone Task ids: {','.join(epic.task_ids)}
|
||||
Active task group: agent-task/{task_group}
|
||||
Keep every change inside this Epic and preserve user changes. Final in Korean.
|
||||
"""
|
||||
if recovery_reason:
|
||||
common += f"""
|
||||
This is an explicit retry after the parent runtime rejected the previous stage output.
|
||||
Resolve the exact recovery condition below before finishing, while preserving valid work:
|
||||
{recovery_reason}
|
||||
Do not repeat a forbidden finalization action merely because its artifact is already present.
|
||||
"""
|
||||
if stage == "materialize":
|
||||
return common + f"""
|
||||
|
|
@ -755,6 +788,22 @@ def cycle(args: argparse.Namespace) -> int:
|
|||
if stage in {"materialize", "refine"}
|
||||
else reviewer_target
|
||||
)
|
||||
recovery_details: list[str] = []
|
||||
if args.retry and prior_cycle_status == "failed":
|
||||
finalized = active_task_finalization_artifacts(workspace, task_group)
|
||||
prior_reason = str(state.get("reason", "unknown parent failure"))
|
||||
if not (
|
||||
prior_reason.startswith(
|
||||
"preparation agent created forbidden finalization artifacts:"
|
||||
)
|
||||
and not finalized
|
||||
):
|
||||
recovery_details.append(prior_reason)
|
||||
if finalized:
|
||||
recovery_details.append(
|
||||
"Remove forbidden preparation-time finalization artifacts and restore valid active pairs: "
|
||||
+ ",".join(str(path.relative_to(workspace)) for path in finalized)
|
||||
)
|
||||
prompt = stage_prompt(
|
||||
stage=stage,
|
||||
workspace=workspace,
|
||||
|
|
@ -763,6 +812,7 @@ def cycle(args: argparse.Namespace) -> int:
|
|||
task_group=task_group,
|
||||
base_head=str(state["base_head"]),
|
||||
checkpoint_head=state.get("checkpoint_head"),
|
||||
recovery_reason="\n".join(recovery_details) or None,
|
||||
)
|
||||
result_path = state_root / "attempts" / f"{stage}.json"
|
||||
state.update(
|
||||
|
|
@ -819,6 +869,12 @@ def cycle(args: argparse.Namespace) -> int:
|
|||
)
|
||||
if refreshed_scope.task_ids != epic.task_ids:
|
||||
raise CycleError(f"target Epic Task ids changed unexpectedly: stage={stage}")
|
||||
finalized = active_task_finalization_artifacts(workspace, task_group)
|
||||
if finalized:
|
||||
raise CycleError(
|
||||
"preparation agent created forbidden finalization artifacts: "
|
||||
+ ",".join(str(path.relative_to(workspace)) for path in finalized)
|
||||
)
|
||||
pairs, task_union = validate_pairs(
|
||||
workspace,
|
||||
task_group,
|
||||
|
|
|
|||
|
|
@ -260,6 +260,117 @@ class EpicCycleContractTest(unittest.TestCase):
|
|||
self.assertEqual([pair[0].parent.name for pair in pairs], ["02_second"])
|
||||
self.assertEqual(union, {"second-task"})
|
||||
|
||||
def test_validate_pair_allows_documented_milestone_slug_notation(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
workspace = Path(raw)
|
||||
command(workspace, "git", "init")
|
||||
task = workspace / "agent-task" / "m-sample" / "01_work"
|
||||
task.mkdir(parents=True)
|
||||
header = (
|
||||
"<!-- task=m-sample/01_work plan=0 tag=TEST "
|
||||
"milestone-task=inside -->\n"
|
||||
)
|
||||
(task / "PLAN-local-G01.md").write_text(
|
||||
header + "# Plan\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(task / "CODE_REVIEW-local-G01.md").write_text(
|
||||
header
|
||||
+ "# Review\n\n"
|
||||
+ "For task groups matching `m-<milestone-slug>`, preserve metadata.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
pairs, union = MODULE.validate_pairs(workspace, "m-sample", {"inside"})
|
||||
|
||||
self.assertEqual(len(pairs), 1)
|
||||
self.assertEqual(union, {"inside"})
|
||||
|
||||
def test_validate_pair_rejects_bare_milestone_slug_placeholder(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
workspace = Path(raw)
|
||||
command(workspace, "git", "init")
|
||||
task = workspace / "agent-task" / "m-sample" / "01_work"
|
||||
task.mkdir(parents=True)
|
||||
header = (
|
||||
"<!-- task=m-sample/01_work plan=0 tag=TEST "
|
||||
"milestone-task=inside -->\n"
|
||||
)
|
||||
(task / "PLAN-local-G01.md").write_text(
|
||||
header + "# Plan\n\nPath: agent-task/m-<milestone-slug>/\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(task / "CODE_REVIEW-local-G01.md").write_text(
|
||||
header + "# Review\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(MODULE.CycleError, "unresolved template token"):
|
||||
MODULE.validate_pairs(workspace, "m-sample", {"inside"})
|
||||
|
||||
def test_validate_pair_rejects_untracked_whitespace_error(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
workspace = Path(raw)
|
||||
command(workspace, "git", "init")
|
||||
task = workspace / "agent-task" / "m-sample" / "01_work"
|
||||
task.mkdir(parents=True)
|
||||
header = (
|
||||
"<!-- task=m-sample/01_work plan=0 tag=TEST "
|
||||
"milestone-task=inside -->\n"
|
||||
)
|
||||
(task / "PLAN-local-G01.md").write_text(
|
||||
header + "# Plan\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(task / "CODE_REVIEW-local-G01.md").write_text(
|
||||
header + "# Review\n\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(MODULE.CycleError, "whitespace validation failed"):
|
||||
MODULE.validate_pairs(workspace, "m-sample", {"inside"})
|
||||
|
||||
def test_retry_prompt_includes_parent_failure(self) -> None:
|
||||
prompt = MODULE.stage_prompt(
|
||||
stage="initial-review",
|
||||
workspace=Path("/workspace"),
|
||||
milestone=Path("/workspace/milestone.md"),
|
||||
epic=MODULE.Epic("sample", "Sample", ("inside",), ("inside",), ""),
|
||||
task_group="m-sample",
|
||||
base_head="abc123",
|
||||
checkpoint_head=None,
|
||||
recovery_reason="git diff --cached --check failed",
|
||||
)
|
||||
|
||||
self.assertIn("explicit retry", prompt)
|
||||
self.assertIn("git diff --cached --check failed", prompt)
|
||||
|
||||
def test_finalization_artifacts_allow_replan_logs_but_reject_verdicts(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
workspace = Path(raw)
|
||||
task = workspace / "agent-task" / "m-sample" / "01_work"
|
||||
task.mkdir(parents=True)
|
||||
replan_log = task / "code_review_local_G01_0.log"
|
||||
replan_log.write_text("# Code Review Reference\n", encoding="utf-8")
|
||||
|
||||
self.assertEqual(
|
||||
MODULE.active_task_finalization_artifacts(workspace, "m-sample"),
|
||||
[],
|
||||
)
|
||||
|
||||
verdict_log = task / "code_review_local_G02_0.log"
|
||||
verdict_log.write_text(
|
||||
"# Code Review Reference\n\n## Code Review Result\n\nPASS\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
complete_log = task / "complete.log"
|
||||
complete_log.write_text("done\n", encoding="utf-8")
|
||||
|
||||
self.assertEqual(
|
||||
MODULE.active_task_finalization_artifacts(workspace, "m-sample"),
|
||||
[verdict_log, complete_log],
|
||||
)
|
||||
|
||||
def test_validate_pair_rejects_foreign_pair_outside_selected_batch(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
workspace = Path(raw)
|
||||
|
|
|
|||
Loading…
Reference in a new issue