From 7468e87185cf76f3d21a4cd096bd67fb6a15428e Mon Sep 17 00:00:00 2001 From: toki Date: Mon, 3 Aug 2026 18:28:38 +0900 Subject: [PATCH] =?UTF-8?q?feat(agent-ops):=20=EB=A7=88=EC=9D=BC=EC=8A=A4?= =?UTF-8?q?=ED=86=A4=20=EC=A4=80=EB=B9=84=20=EA=B8=B0=EB=B3=B8=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EC=9D=84=20=EC=A0=95=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone 준비 요청에서 workspace 위치를 명시하도록 하고, plan 생성과 재검토의 기본 실행을 Codex gpt-5.6-sol xhigh로 고정해 재현 가능한 준비 흐름을 제공한다. 다른 provider를 명시하면 provider 기본 모델을 보존한다. --- agent-ops/.version | 2 +- .../common/prepare-epic-work-items/SKILL.md | 13 +++++----- .../scripts/run_agent_once.py | 9 +++++-- .../scripts/run_epic_cycle.py | 22 +++++++++++++--- .../tests/test_run_agent_once.py | 2 ++ .../tests/test_run_epic_cycle.py | 25 +++++++++++++++++++ .../prepare-milestone-workspace/SKILL.md | 19 +++++++------- .../agents/openai.yaml | 2 +- .../scripts/prepare_workspace.py | 22 +++++++++++++--- .../tests/test_prepare_workspace.py | 25 +++++++++++++++++++ agent-ops/skills/common/router.md | 4 +-- 11 files changed, 117 insertions(+), 28 deletions(-) diff --git a/agent-ops/.version b/agent-ops/.version index 1f698df..ee7a93d 100644 --- a/agent-ops/.version +++ b/agent-ops/.version @@ -1 +1 @@ -1.1.182 +1.1.184 diff --git a/agent-ops/skills/common/prepare-epic-work-items/SKILL.md b/agent-ops/skills/common/prepare-epic-work-items/SKILL.md index d3b63a0..4ae2e82 100644 --- a/agent-ops/skills/common/prepare-epic-work-items/SKILL.md +++ b/agent-ops/skills/common/prepare-epic-work-items/SKILL.md @@ -14,10 +14,10 @@ description: 현재 또는 지정 Milestone의 정확히 한 Epic을 작은 직 - `workspace`: 준비된 feature worktree 절대 경로 (필수) - `target-milestone`: 활성 Milestone slug 또는 경로 (필수) - `target-epic`: 정확한 Epic id 또는 이름 (필수) -- `planner-agent`: `codex`, `claude`, `gemini`, `pi` 중 하나 (필수) +- `planner-agent`: `codex`, `claude`, `gemini`, `pi` 중 하나 (생략 시 `codex`) - `review-agent`: 생략하면 `planner-agent`와 같다. (선택) -- `planner-model`, `review-model`: provider별 model override (선택) -- `reasoning-effort`: 지원하는 provider의 reasoning/thinking override (선택) +- `planner-model`, `review-model`: provider별 model override. Codex 기본 사용 시 `planner-model` 생략 시 `gpt-5.6-sol`, 다른 provider는 해당 CLI 기본 모델을 사용한다. (선택) +- `reasoning-effort`: 지원하는 provider의 reasoning/thinking override. 생략 시 `xhigh` (선택) - `pi-provider`: Pi provider override (선택) - `retry`: terminal failure의 원인을 사용자가 해소한 뒤 같은 Epic 상태를 재개할 때만 사용한다. (선택) @@ -51,12 +51,11 @@ description: 현재 또는 지정 Milestone의 정확히 한 Epic을 작은 직 python3 agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py \ --workspace "$WORKSPACE" \ --milestone "$MILESTONE" \ - --epic "$EPIC" \ - --planner-agent "$PLANNER_AGENT" \ - --review-agent "$REVIEW_AGENT" + --epic "$EPIC" ``` - - model, reasoning, Pi provider override와 `--retry`는 해당 입력이 있을 때만 전달한다. + - 기본값은 `codex / gpt-5.6-sol / xhigh`다. 다른 provider를 지정하면 모델을 별도로 주지 않는 한 해당 provider의 CLI 기본 모델을 사용한다. + - 다른 agent, model, reasoning, Pi provider override와 `--retry`는 해당 입력이 있을 때만 전달한다. - 스크립트는 각 agent를 새 one-shot session으로 실행한다. Codex, Claude, Gemini(`agy` adapter), Pi를 같은 normalized runner 계약으로 지원한다. - model stdout/stderr는 git common dir의 locator log에만 저장한다. caller stdout에는 lifecycle/attention event만 출력한다. diff --git a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_agent_once.py b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_agent_once.py index 2ba0022..e9b355a 100755 --- a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_agent_once.py +++ b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_agent_once.py @@ -17,6 +17,9 @@ import uuid AGENT_COMMAND = {"codex": "codex", "claude": "claude", "gemini": "agy", "pi": "pi"} +DEFAULT_AGENT = "codex" +DEFAULT_MODEL = "gpt-5.6-sol" +DEFAULT_REASONING_EFFORT = "xhigh" LABEL_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$") PROBE_EXPECTED = "MILESTONE_AGENT_READY" @@ -198,13 +201,13 @@ def sanitized_command(command: list[str], prompt: str) -> list[str]: def parser() -> argparse.ArgumentParser: value = argparse.ArgumentParser(description=__doc__) - value.add_argument("--agent", choices=sorted(AGENT_COMMAND), required=True) + value.add_argument("--agent", choices=sorted(AGENT_COMMAND), default=DEFAULT_AGENT) value.add_argument("--workspace", required=True) prompt_group = value.add_mutually_exclusive_group() prompt_group.add_argument("--prompt") prompt_group.add_argument("--prompt-file") value.add_argument("--model") - value.add_argument("--reasoning-effort") + value.add_argument("--reasoning-effort", default=DEFAULT_REASONING_EFFORT) value.add_argument("--pi-provider") value.add_argument("--label", default="one-shot") value.add_argument("--probe", action="store_true") @@ -214,6 +217,8 @@ def parser() -> argparse.ArgumentParser: def execute(args: argparse.Namespace) -> int: workspace = workspace_root(args.workspace) + if args.model is None and args.agent == DEFAULT_AGENT: + args.model = DEFAULT_MODEL if not LABEL_PATTERN.fullmatch(args.label): raise AgentRunError("--label may contain only letters, digits, dot, underscore, and hyphen") prompt = prompt_text(args) diff --git a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py index 9fb09bb..4af4c34 100755 --- a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py +++ b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py @@ -16,6 +16,9 @@ from typing import Any, Iterable STAGES = ("materialize", "initial-review", "refine", "final-review") +DEFAULT_PLANNER_AGENT = "codex" +DEFAULT_PLANNER_MODEL = "gpt-5.6-sol" +DEFAULT_REASONING_EFFORT = "xhigh" PLAN_PATTERN = "PLAN-*-G??.md" REVIEW_PATTERN = "CODE_REVIEW-*-G??.md" HEADER = re.compile(r"^$") @@ -525,17 +528,30 @@ def parser() -> argparse.ArgumentParser: value.add_argument("--workspace", required=True) value.add_argument("--milestone", required=True) value.add_argument("--epic", required=True) - value.add_argument("--planner-agent", choices=("codex", "claude", "gemini", "pi"), required=True) + value.add_argument( + "--planner-agent", + choices=("codex", "claude", "gemini", "pi"), + default=DEFAULT_PLANNER_AGENT, + ) value.add_argument("--review-agent", choices=("codex", "claude", "gemini", "pi")) value.add_argument("--planner-model") value.add_argument("--review-model") - value.add_argument("--reasoning-effort") + value.add_argument("--reasoning-effort", default=DEFAULT_REASONING_EFFORT) value.add_argument("--pi-provider") value.add_argument("--retry", action="store_true") return value +def apply_defaults(args: argparse.Namespace) -> argparse.Namespace: + if args.planner_model is None and args.planner_agent == DEFAULT_PLANNER_AGENT: + args.planner_model = DEFAULT_PLANNER_MODEL + if args.reasoning_effort is None: + args.reasoning_effort = DEFAULT_REASONING_EFFORT + return args + + def cycle(args: argparse.Namespace) -> int: + apply_defaults(args) workspace = resolve_workspace(args.workspace) milestone_path, milestone_match = resolve_milestone(workspace, args.milestone) milestone_text = milestone_path.read_text(encoding="utf-8") @@ -795,7 +811,7 @@ def cycle(args: argparse.Namespace) -> int: def main(argv: Iterable[str] | None = None) -> int: - args = parser().parse_args(argv) + args = apply_defaults(parser().parse_args(argv)) state_path: Path | None = None identity = "unknown" try: diff --git a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_agent_once.py b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_agent_once.py index 7c293c5..d2051b6 100644 --- a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_agent_once.py +++ b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_agent_once.py @@ -114,6 +114,8 @@ class AgentCommandTest(unittest.TestCase): self.assertEqual(result, 0) recorded = json.loads(result_file.read_text(encoding="utf-8")) self.assertEqual(recorded["status"], "succeeded") + self.assertEqual(recorded["model"], "gpt-5.6-sol") + self.assertEqual(recorded["reasoning_effort"], "xhigh") self.assertIn("agent_process_start_token", recorded) locators = list((workspace / ".git" / "epic-work-preparation" / "runs").glob("*/locator.json")) self.assertEqual(len(locators), 1) diff --git a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py index b77458b..8b2036c 100644 --- a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py +++ b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py @@ -31,6 +31,31 @@ def command(cwd: Path, *args: str) -> str: class EpicCycleContractTest(unittest.TestCase): + def test_cycle_defaults_to_codex_top_model_and_reasoning(self) -> None: + args = MODULE.parser().parse_args( + ["--workspace", "/workspace", "--milestone", "milestone.md", "--epic", "epic"] + ) + MODULE.apply_defaults(args) + self.assertEqual(args.planner_agent, "codex") + self.assertEqual(args.planner_model, "gpt-5.6-sol") + self.assertEqual(args.reasoning_effort, "xhigh") + + other = MODULE.parser().parse_args( + [ + "--workspace", + "/workspace", + "--milestone", + "milestone.md", + "--epic", + "epic", + "--planner-agent", + "claude", + ] + ) + MODULE.apply_defaults(other) + self.assertIsNone(other.planner_model) + self.assertEqual(other.reasoning_effort, "xhigh") + def test_live_stage_result_requires_tracking_without_relaunch(self) -> None: with tempfile.TemporaryDirectory() as raw: root = Path(raw) diff --git a/agent-ops/skills/common/prepare-milestone-workspace/SKILL.md b/agent-ops/skills/common/prepare-milestone-workspace/SKILL.md index bbed233..1e15bae 100644 --- a/agent-ops/skills/common/prepare-milestone-workspace/SKILL.md +++ b/agent-ops/skills/common/prepare-milestone-workspace/SKILL.md @@ -1,11 +1,11 @@ --- name: prepare-milestone-workspace -description: 계획 상태의 Milestone을 현재 작업현황과 동기화한 뒤 Git Flow feature branch를 Milestone slug로 생성·push하고 지정 workspace를 worktree로 준비해 첫 Epic 작업 사이클로 넘길 때 사용한다. "X 마일스톤 준비해", "X 마일스톤 작업준비해", "X 마일스톤 작업 준비해", "X 마일스톤 시작할게" 요청에서 사용한다. +description: 명시된 workspace에 계획 상태의 Milestone을 현재 작업현황과 동기화한 뒤 Git Flow feature branch를 Milestone slug로 생성·push하고 worktree로 준비해 첫 Epic 작업 사이클로 넘길 때 사용한다. "workspace에 milestone 작업 준비해" 요청에서 사용한다. --- # Prepare Milestone Workspace -Milestone 작업을 시작한다는 요청(`X 마일스톤 준비해`, `X 마일스톤 작업준비해`, `X 마일스톤 작업 준비해`, `X 마일스톤 시작할게`)은 workspace만 만드는 요청이 아니라, 정합성 확인부터 첫 Epic 준비 사이클 연결까지 수행하는 전체 흐름으로 해석한다. +` 작업 준비해` 요청은 workspace만 추정하거나 만드는 요청이 아니라, 지정된 workspace에 대상 Milestone의 정합성 확인부터 첫 Epic 준비 사이클 연결까지 수행하는 전체 흐름으로 해석한다. ## 목적 @@ -15,13 +15,15 @@ Milestone 작업을 시작한다는 요청(`X 마일스톤 준비해`, `X 마일 - `target-milestone`: 활성 Milestone 이름, id, slug 또는 경로 (필수) - `workspace`: feature worktree로 사용할 절대 경로 (필수) -- `planner-agent`: `codex`, `claude`, `gemini`, `pi` 중 하나 (필수) +- `planner-agent`: `codex`, `claude`, `gemini`, `pi` 중 하나 (생략 시 `codex`) - `review-agent`: 생략하면 `planner-agent`와 같다. (선택) -- `planner-model`, `review-model`: provider별 model override (선택) -- `reasoning-effort`: 지원하는 provider의 reasoning/thinking override (선택) +- `planner-model`, `review-model`: provider별 model override. Codex 기본 사용 시 `planner-model` 생략 시 `gpt-5.6-sol`, 다른 provider는 해당 CLI 기본 모델을 사용한다. (선택) +- `reasoning-effort`: 지원하는 provider의 reasoning/thinking override. 생략 시 `xhigh` (선택) - `pi-provider`: Pi provider override (선택) - `target-epic`: 준비 직후 실행할 정확한 Epic id 또는 이름. 생략하면 첫 미완료 Epic을 선택한다. (선택) +트리거의 첫 번째 위치 표현(``)은 workspace 절대 경로로, 두 번째 표현(``)은 대상 Milestone으로 각각 확정한다. workspace가 생략되거나 둘 이상의 경로로 해석되면 임의로 `current`나 기본 경로를 사용하지 않고 사용자에게 확인한다. + ## 사전 조건 - 대상 checkout은 Git Flow develop branch이고 tracked/untracked 변경이 없어야 한다. @@ -45,12 +47,11 @@ Milestone 작업을 시작한다는 요청(`X 마일스톤 준비해`, `X 마일 python3 agent-ops/skills/common/prepare-milestone-workspace/scripts/prepare_workspace.py \ --repo "$REPO" \ --milestone "$MILESTONE" \ - --workspace "$WORKSPACE" \ - --planner-agent "$PLANNER_AGENT" \ - --review-agent "$REVIEW_AGENT" + --workspace "$WORKSPACE" ``` - - model override가 있으면 `--planner-model`, `--review-model`, reasoning override가 있으면 `--reasoning-effort`, Pi provider override가 있으면 `--pi-provider`로 전달한다. + - 기본값은 `codex / gpt-5.6-sol / xhigh`다. 다른 provider를 지정하면 모델을 별도로 주지 않는 한 해당 provider의 CLI 기본 모델을 사용한다. + - 다른 agent, model, reasoning, Pi provider override가 있으면 해당 인자를 전달한다. - 스크립트는 develop HEAD와 remote develop의 일치, agent probe, branch 충돌, worktree 소유권을 mutation 전에 검사한다. - branch는 Milestone id가 아니라 파일 basename을 사용한 `feature/`다. - 기존 branch/worktree는 정확히 같은 branch·경로이고 clean할 때만 재개한다. diff --git a/agent-ops/skills/common/prepare-milestone-workspace/agents/openai.yaml b/agent-ops/skills/common/prepare-milestone-workspace/agents/openai.yaml index 59654c9..3f6bda4 100644 --- a/agent-ops/skills/common/prepare-milestone-workspace/agents/openai.yaml +++ b/agent-ops/skills/common/prepare-milestone-workspace/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Prepare Milestone Workspace" short_description: "Prepare a Git Flow workspace for one Milestone" - default_prompt: "Use $prepare-milestone-workspace to prepare the named Milestone and start its first Epic." + default_prompt: "Use $prepare-milestone-workspace to prepare the named Milestone in the explicitly provided workspace and start its first Epic." diff --git a/agent-ops/skills/common/prepare-milestone-workspace/scripts/prepare_workspace.py b/agent-ops/skills/common/prepare-milestone-workspace/scripts/prepare_workspace.py index dde1f6e..d6dc8f2 100755 --- a/agent-ops/skills/common/prepare-milestone-workspace/scripts/prepare_workspace.py +++ b/agent-ops/skills/common/prepare-milestone-workspace/scripts/prepare_workspace.py @@ -17,6 +17,9 @@ from typing import Any, Iterable VALID_AGENTS = {"codex", "claude", "gemini", "pi"} AGENT_COMMAND = {"codex": "codex", "claude": "claude", "gemini": "agy", "pi": "pi"} +DEFAULT_PLANNER_AGENT = "codex" +DEFAULT_PLANNER_MODEL = "gpt-5.6-sol" +DEFAULT_REASONING_EFFORT = "xhigh" MILESTONE_PATTERN = re.compile( r"^agent-roadmap/phase/(?P[a-z0-9-]+)/milestones/(?P[a-z0-9-]+)\.md$" ) @@ -248,11 +251,15 @@ def parser() -> argparse.ArgumentParser: value.add_argument("--repo", required=True) value.add_argument("--milestone", required=True) value.add_argument("--workspace", required=True) - value.add_argument("--planner-agent", choices=sorted(VALID_AGENTS), required=True) + value.add_argument( + "--planner-agent", + choices=sorted(VALID_AGENTS), + default=DEFAULT_PLANNER_AGENT, + ) value.add_argument("--review-agent", choices=sorted(VALID_AGENTS)) value.add_argument("--planner-model") value.add_argument("--review-model") - value.add_argument("--reasoning-effort") + value.add_argument("--reasoning-effort", default=DEFAULT_REASONING_EFFORT) value.add_argument("--pi-provider") value.add_argument("--remote", default="origin") value.add_argument( @@ -264,7 +271,16 @@ def parser() -> argparse.ArgumentParser: return value +def apply_defaults(args: argparse.Namespace) -> argparse.Namespace: + if args.planner_model is None and args.planner_agent == DEFAULT_PLANNER_AGENT: + args.planner_model = DEFAULT_PLANNER_MODEL + if args.reasoning_effort is None: + args.reasoning_effort = DEFAULT_REASONING_EFFORT + return args + + def prepare(args: argparse.Namespace) -> int: + apply_defaults(args) if args.skip_agent_probe and not ( args.dry_run or os.environ.get("AGENT_OPS_TESTING") == "1" ): @@ -423,7 +439,7 @@ def prepare(args: argparse.Namespace) -> int: def main(argv: Iterable[str] | None = None) -> int: - args = parser().parse_args(argv) + args = apply_defaults(parser().parse_args(argv)) try: return prepare(args) except (OSError, PreparationError) as exc: diff --git a/agent-ops/skills/common/prepare-milestone-workspace/tests/test_prepare_workspace.py b/agent-ops/skills/common/prepare-milestone-workspace/tests/test_prepare_workspace.py index 8da9b92..1d2340f 100644 --- a/agent-ops/skills/common/prepare-milestone-workspace/tests/test_prepare_workspace.py +++ b/agent-ops/skills/common/prepare-milestone-workspace/tests/test_prepare_workspace.py @@ -31,6 +31,31 @@ def command(cwd: Path, *args: str) -> str: class PrepareWorkspaceTest(unittest.TestCase): + def test_workspace_defaults_to_codex_top_model_and_reasoning(self) -> None: + args = MODULE.parser().parse_args( + ["--repo", "/repo", "--milestone", "milestone.md", "--workspace", "/workspace"] + ) + MODULE.apply_defaults(args) + self.assertEqual(args.planner_agent, "codex") + self.assertEqual(args.planner_model, "gpt-5.6-sol") + self.assertEqual(args.reasoning_effort, "xhigh") + + other = MODULE.parser().parse_args( + [ + "--repo", + "/repo", + "--milestone", + "milestone.md", + "--workspace", + "/workspace", + "--planner-agent", + "claude", + ] + ) + MODULE.apply_defaults(other) + self.assertIsNone(other.planner_model) + self.assertEqual(other.reasoning_effort, "xhigh") + def test_agent_probe_bypass_is_test_only(self) -> None: output = io.StringIO() with contextlib.redirect_stdout(output), mock.patch.dict( diff --git a/agent-ops/skills/common/router.md b/agent-ops/skills/common/router.md index 9dc7a17..99145b3 100644 --- a/agent-ops/skills/common/router.md +++ b/agent-ops/skills/common/router.md @@ -47,7 +47,7 @@ | 이 마일스톤은 X가 끝나야 가능해, A 전까지 B 잠가둬, 잠금 해제 조건은 X야, X 프로젝트 작업 뒤에 현재 마일스톤 진행, 의존성 설정해, 외부 의존 잠금 | `agent-ops/skills/common/update-roadmap/SKILL.md` | | roadmap dependency 확인, locks.yaml 판별, 외부 의존 잠금 확인, unlock-ready 판별, 잠금 해제 조건 충족 여부 확인, roadmap-dependency-checker.sh | `agent-ops/skills/common/check-roadmap-dependency/SKILL.md` | | 지금 작업이 뭐지?, 현재 작업 분석, 어디까지 했지?, 로드맵상 현 위치, 현재 마일스톤 위치, current 기준 breadcrumb | `agent-ops/skills/common/analyze-roadmap-position/SKILL.md` | -| X 마일스톤 준비해, X 마일스톤 작업준비해, X 마일스톤 작업 준비해, X 마일스톤 시작할게 | `agent-ops/skills/common/prepare-milestone-workspace/SKILL.md` | +| X에 Y 작업 준비해, X에 Y 작업준비해, X에 Y 마일스톤 작업 준비해 | `agent-ops/skills/common/prepare-milestone-workspace/SKILL.md` | | 현 마일스톤 Epic 작업 준비해, 마일스톤 Epic 작업 준비해, 이 Epic의 작은 작업은 바로 처리하고 큰 작업은 plan으로 작성해, Epic 작업을 작은 작업과 plan으로 나눠 | `agent-ops/skills/common/prepare-epic-work-items/SKILL.md` | | 계획 세워줘, 계획 작성해, 계획 만들어줘, 구현 계획, PLAN.md, plan, plan 작성해, plan 만들어줘 | `agent-ops/skills/common/plan/SKILL.md` | | 현재 plan들 세분화해, 현재 plan 세분화, 기존 plan 더 나눠, task 세분화해, plan 분리해 | `agent-ops/skills/common/refine-plans/SKILL.md` | @@ -61,7 +61,7 @@ 라우팅 우선순위: -- `X 마일스톤 준비해`, `X 마일스톤 작업준비해`, `X 마일스톤 작업 준비해`, `X 마일스톤 시작할게`처럼 Milestone 시작 의도가 포함되면 `prepare-milestone-workspace`를 선택하고, `WORKSPACE_READY` 뒤 정확히 한 Epic에 `prepare-epic-work-items`를 연결한다. +- `X에 Y 작업 준비해`처럼 workspace 위치와 대상 Milestone이 함께 명시되면 `prepare-milestone-workspace`를 선택하고, `WORKSPACE_READY` 뒤 정확히 한 Epic에 `prepare-epic-work-items`를 연결한다. workspace 위치가 없으면 이 흐름으로 라우팅하지 않고 확인을 요청한다. - 한 Epic 안에서 작은 작업 직접 처리와 큰 작업 plan 작성을 함께 요청하면 `prepare-epic-work-items`를 선택한다. 이미 존재하는 plan만 세분화하는 요청과 새로운 plan만 작성하는 요청에는 이 스킬을 선택하지 않는다. - 이미 생성된 미착수 pair의 분할만 요청하면 lane과 관계없이 `refine-plans`를 선택한다. 새 plan 작성이나 구현 범위 재분석이 포함되면 `plan`을 선택한다. - `refine-plans` 대상이 아닌 PLAN/CODE_REVIEW 작성 또는 재작성이 요청 범위에 포함되면 `plan`을 선택한다. `plan`이 최종 단계에서 `finalize-task-routing`을 필수 호출한다.