Mission Spec — Decompose agent/mission.py god-module (remainder) (#2056)
Mission: decompose-mission-god-module-01KVXHF8 Mission type: software-dev Target file: src/specify_cli/cli/commands/agent/mission.py — 4125 LOC, 62 top-level defs, 8 @app.commands. Base: worktree sk-2056 on origin/main (c3814ec5a). This base does NOT include sibling mission #2058.
Overview
agent/mission.py is the agent mission CLI command module. It has grown into a god-module: 4125 LOC across 62 top-level definitions (61 def + 1 class), exposing 8 subcommands (branch-context, create, check-prerequisites, record-analysis, setup-plan, accept, merge, finalize-tasks). Three command functions are themselves mega-functions far over the complexity ceiling: finalize_tasks (1227 LOC), setup_plan (507 LOC), create_mission (281 LOC).
The planning-commit pipeline was already extracted into coordination/commit_router.py by a prior slice (mission 01KVMBD6 — commit_for_mission + materialise/stage/route helpers). This mission decomposes the REMAINDER: the command surface, the shared resolution/parsing/validation helpers, and the internal complexity of the three mega-functions — into cohesive, independently-testable seams behind a thin command-registration shim, preserving the public agent mission CLI surface byte-for-byte.
This is a behavior-preserving refactor (semantic compression, profile randy-reducer): no command, flag, JSON envelope, exit code, or on-disk behavior changes. The only observable effect is a smaller, saner module topology, a complexity ceiling that holds, and a frozen, machine-checkable CLI contract.
What is in scope
importable / test-patched symbol** (~100 names) so existing import edges and @patch("...mission.<name>") targets keep resolving with zero test churn.
_stage_finalize_artifacts_in_coord_worktree) into coordination/commit_router.py and repoint tasks.py's import — these symbols are LIVE on this base (see Critical base-version constraint).
- Decompose the remainder of
mission.pyinto the 4 research-resolved seams. - Internally decompose the 3 mega-functions into
<=15-complexity phase helpers, each with focused tests. - Make
mission.pya thin shim that registers the 8 commands and **re-exports every previously - Relocate the planning-commit residue (
_planning_commit_worktree,_resolve_planning_placement,
What is out of scope
- Any change to command names, flag names/defaults, positional args, JSON envelope keys, or exit codes.
- Re-extracting the already-extracted planning-commit pipeline (
commit_for_mission& friends). - Touching the
agent tasksgod-module (#2058, a sibling mission). - New functionality of any kind.
⚠️ Critical base-version constraint (LIVE planning-commit residue)
This worktree branches from origin/main (c3814ec5a), which does NOT contain the #2058 mission. On this base:
(verified: src/specify_cli/cli/commands/agent/tasks.py:3928 and :3936 call it; :3704 imports _resolve_planning_placement).
tasks.pystill imports_planning_commit_worktreefrommission.py- Therefore
_planning_commit_worktreeand_resolve_planning_placementare LIVE, not dead code.
Disposition decision (binding for this mission): RELOCATE, never delete.
_stage_finalize_artifacts_in_coord_worktree, into coordination/commit_router.py — the canonical home of the planning-commit pipeline that the prior slice (01KVMBD6) established.
_stage_artifacts_in_coord_worktree (commit_router.py:366, whose docstring already names the mission.py version as its mirror) — collapse the duplication rather than forking a second copy.
_resolve_planning_placement to the new commit_router home.
- Move
_planning_commit_worktree,_resolve_planning_placement, and the helper they depend on, - Reconcile
_stage_finalize_artifacts_in_coord_worktreeagainst commit_router's existing near-duplicate - Repoint
tasks.py's single function-local import of_planning_commit_worktree/
This completes the commit-pipeline consolidation that 01KVMBD6 started. The spec is explicit: the planning-commit residue is LIVE and must be relocated + repointed, never deleted. (If #2058 were already on this base, those symbols would be dead and deletable — but they are not, so relocation is the only correct disposition here.)
User Scenarios & Testing
Primary "users" of this module are: (a) the agent/operator invoking spec-kitty agent mission <cmd>, (b) downstream Python callers (lifecycle.py, tasks.py), and (c) the ~50 test files that import or patch mission.<symbol>.
Scenario 1 — Operator invokes any agent mission subcommand (byte-for-byte preserved)
the pre-decomposition base — verified by a golden CLI characterization test captured FIRST.
- Given the decomposition is complete,
- When an operator runs
spec-kitty agent mission <subcommand> [flags](or--json), - Then the command name, flags, defaults, behavior, exit code, and JSON envelope are identical to
Scenario 2 — Existing test patches keep working (zero churn)
(e.g. locate_project_root patched 76×, _find_feature_directory 39×, _show_branch_context 22×),
test suite passes unchanged with no patch-target rewrites.
- Given ~50 test files patch
@patch("specify_cli.cli.commands.agent.mission.<name>") - When the symbols physically move into seam modules,
- Then every such patch target still resolves because
mission.pyre-exports the name — the existing
Scenario 3 — lifecycle.py and tasks.py import edges hold
_build_setup_plan_detection_error as agent_feature.<name>, and tasks.py imports _parse_requirement_refs_from_tasks_md, _resolve_planning_placement, _planning_commit_worktree,
repointed to commit_router; the parsing-helper import resolves via shim re-export or its seam.
- Given
lifecycle.pyimportscreate_mission,setup_plan,finalize_tasks, - When symbols move,
- Then
lifecycle.py's edges resolve via shim re-export;tasks.py's planning-commit imports are
Scenario 4 — finalize-tasks --validate-only mutates zero bytes
decomposition and pinned by the existing readonly regression test.
- Given the
finalize_tasksmega-function is internally decomposed into phase helpers, - When
finalize-tasks --validate-onlyruns, - Then it mutates ZERO bytes on disk (in-memory bootstrap only) — invariant preserved through
Scenario 5 — Complexity ceiling holds
suppressions.
- Given the 3 mega-functions are decomposed,
- When ruff
C901/ SonarS3776run, - Then every function (including all new phase helpers) reports complexity
<=15, with zero new
Functional Requirements
All FRs are Status: Approved.
(branch-context, create, check-prerequisites, record-analysis, setup-plan, accept, merge, finalize-tasks) with their exact positional args, option flags, defaults, exit codes, and JSON envelope shapes — MUST remain byte-identical to the base. The full flag set per command is enumerated in research §1 and frozen in contracts/cli-surface-contract.md.
- FR-001 — Frozen CLI surface. The public
agent missionCLI surface — all 8 subcommands
referencing #2056 (matching the #1623 / existing god-module-pointer convention already present in this repo) that documents the shim role and the seam map, so future maintainers route new responsibilities to the seams, not the shim.
- FR-002 — Decomposition pointer comment.
mission.pyMUST carry a top-of-file pointer comment
1. feature-dir-resolution (Seam D) — _find_feature_directory & friends (_resolve_mission_dir_name_primary_anchored, _primary_anchored_feature_dir, _list_feature_spec_candidates, _sole_mission_slug_or_none, _build_setup_plan_detection_error, _safe_load_meta, _read_feature_meta). 2. parsing/validation (Seam C) — tasks.md/spec.md parsers (_parse_wp_sections_from_tasks_md, _parse_dependencies_from_tasks_md, _parse_requirement_refs_from_tasks_md, _parse_requirement_refs_from_wp_files, _parse_requirement_ids_from_spec_md, _extract_wp_ids_from_task_files), owned-files validation (_normalize_owned_file_path, _is_mission_specs_owned_file, _owned_files_yaml_is_explicit_empty_list, _raw_frontmatter_has_field, _invalid_mission_specs_owned_files), JSON emit shims (_emit_json, _with_cli_version, _with_mission_aliases, _emit_console_or_json_error, _utc_now_iso). 3. record-analysis (Seam A) — record_analysis + _enforce_analysis_report_write_preflight + _resolve_record_analysis_placement_ref. 4. lifecycle-subcommands split per command-family (Seam B) — branch_context, create_mission, check_prerequisites, setup_plan, accept_feature, merge_feature, finalize_tasks and their dedicated helpers, split into per-family modules (not one mega-module).
- FR-003 — Decompose into the 4 research-resolved seams. The remainder MUST be decomposed into:
Seam D is extracted first so the other seams import a stable resolution surface rather than each other. One-way imports only: seams import lower layers (core/status/coordination/mission_runtime), never the shim.
coverage of the seam's code. Pure parsers and resolvers MUST get direct** unit tests (not only indirect coverage through the command path), closing the current gap where Seam C parsers are exercised only via finalize_tasks.
- FR-004 — Per-seam focused tests (≥90%). Each seam MUST carry focused tests achieving **≥90%
(507 LOC), and create_mission (281 LOC) MUST be internally decomposed into phase helpers each with complexity <=15, and each phase helper MUST have a focused test executing its branches directly. Recommended phase boundaries are documented in research §2 (finalize_tasks: preflight → feature-dir resolution → branch resolution → conflict detection → charter-activation gate → dependency resolution → 8-field bootstrap-mutation loop → manifest build/ownership validation → commit → SaaS emit; setup_plan: preflight → resolution → branch-contract injection → plan commit → coord commits; create_mission: scaffold → meta write → coordination-branch creation → branch-contract injection → event emit).
- FR-005 — Internal decomposition of the 3 mega-functions.
finalize_tasks(1227 LOC),setup_plan
currently importable or patchable as mission.<name> so that @patch("...mission.<name>") targets and from ...mission import <name> edges keep working with zero churn. This explicitly includes the heavily patched names (locate_project_root 76×, _find_feature_directory 39×, _show_branch_context 22×, run_command, get_emitter, is_saas_sync_enabled, validate_feature_structure), the exported class CommitToBranchResult, the app Typer object, and all 8 command functions.
- FR-006 — Shim re-exports every test-patched name (~100).
mission.pyMUST re-export EVERY symbol
_resolve_planning_placement, and _stage_finalize_artifacts_in_coord_worktree MUST be RELOCATED into coordination/commit_router.py (the canonical planning-commit pipeline home), reconciling _stage_finalize_artifacts_in_coord_worktree against commit_router's existing near-duplicate _stage_artifacts_in_coord_worktree. tasks.py's import of _planning_commit_worktree / _resolve_planning_placement MUST be repointed to the new commit_router home. These symbols are LIVE on this base (tasks.py calls _planning_commit_worktree) and MUST NOT be deleted as dead code.
- FR-007 — Relocate (not delete) the planning-commit residue.
_planning_commit_worktree,
Non-Functional Requirements
complexity <=15 under ruff C901 (aligned with Sonar S3776). No function left at 16+.
ship with tests in the same WP (Sonar new-code gate).
profile) pass with zero issues and zero warnings on all touched files.
ignore additions. Fix the code, don't silence the checker.
from mission/seams; commit_for_mission / CoordinationWorkspace imports stay function-local where they already are.
- NFR-001 — Complexity ceiling. Every function (shim, seams, phase helpers) reports max cyclomatic
- NFR-002 — Coverage. ≥90% coverage per new seam module and per new phase helper; new branches/helpers
- NFR-003 — Static analysis clean.
ruff checkandmypy --strict(the project's configured strict - NFR-004 — No new suppressions. No new
# noqa,# type: ignore, Sonar suppression, or per-file - NFR-005 — No import cycles. Seam modules import lower layers only;
commit_routernever imports
Constraints
positional args, JSON envelope keys, or exit codes.
coordination/commit_router.py; do not create a parallel pipeline or improvise a new home.
(CliRunner-based: --help for app lists all 8 commands; each subcommand's --help lists exact flags; representative success + error JSON envelopes) MUST be captured in WP01, before any extraction, as the safety net for "byte-for-byte preserved."
- C-001 — No command/flag changes. Zero changes to command names, flag names, flag defaults,
- C-002 — Use canonical commit_router. The relocated planning-commit residue lands in
- C-003 — Behavior preserving. This is a pure refactor. No observable runtime behavior may change.
- C-004 — No new suppressions. (See NFR-004.)
- C-005 — Golden characterization test FIRST. A golden
agent missionCLI characterization test
Success Criteria
byte-identical).
_stage_finalize_artifacts_in_coord_worktree live in commit_router.py; tasks.py imports them from there; tasks.py behavior unchanged (its tests pass).
- SC-1:
mission.pyis a thin shim with no business logic; all command bodies and helpers live in seams. - SC-2: The golden CLI characterization test passes against both base and decomposed code (the contract is
- SC-3: The full existing test suite (≈50 mission-touching files) passes with zero patch-target rewrites.
- SC-4:
mission.py, every seam module, and every phase helper report complexity<=15under ruffC901. - SC-5: Each new seam and phase helper has focused tests; new-code coverage ≥90%.
- SC-6:
ruff+mypy --strictclean on all touched files; zero new suppressions. - SC-7:
_planning_commit_worktree/_resolve_planning_placement/ - SC-8:
finalize-tasks --validate-onlymutates zero bytes (readonly regression test green).
Key Entities
~100 symbols. No logic.
(Seam D), and per-family lifecycle command modules (cmd_branch_context.py, cmd_create.py, cmd_check_prerequisites.py, cmd_setup_plan.py, cmd_finalize_tasks.py, cmd_accept_merge.py) for Seam B. (Module-file-vs-package layout is a plan-phase decision; either preserves the specify_cli.cli.commands.agent.mission import path.)
_planning_commit_worktree / _resolve_planning_placement / _stage_finalize_artifacts_in_coord_worktree.
mission.py(shim) —app = typer.Typer(name="mission", ...); registers the 8 commands; re-exports- Seam modules —
cmd_record_analysis.py(Seam A),parsing.py(Seam C),feature_resolution.py coordination/commit_router.py— canonical planning-commit pipeline; receives the relocatedCommitToBranchResult— exported dataclass imported by tests; must remain importable frommission.<name>.- Golden CLI contract —
contracts/cli-surface-contract.md+ the characterization test pinning it.
Assumptions
bulk patch-target rewrite.
move to a cmd_accept_merge seam without re-importing the full accept/merge graph at module top level.
_kind_for_artifact, record-analysis, JSON envelope) remain the baseline safety net and are extended, not replaced.
- A-1: #2058 is NOT on this base; the
tasks.py → _planning_commit_worktreeedge is live (verified). - A-2: Shim re-exports are the chosen mitigation for patch-target churn (zero test rewrites) rather than a
- A-3:
accept/mergeare already thin delegators totop_level_accept/top_level_mergeand can - A-4: The module-vs-package layout decision is deferred to the plan; both preserve the import path.
- A-5: Existing dedicated tests (finalize_tasks ×6+ files, create_mission,
_commit_to_branch,
Research outcomes (resolved decisions)
parsing/validation (C), record-analysis (A), lifecycle-subcommands split per command-family (B).
setup_plan (507), create_mission (281). Internal phase extraction is the bulk of the effort, not the file split.
(locate_project_root 76×, _find_feature_directory 39×, _show_branch_context 22×, etc.). Shim re-export = zero churn.
live (tasks.py calls them), so the resolved disposition is relocate-into-commit_router + repoint-tasks.py, reconciling against commit_router's existing _stage_artifacts_in_coord_worktree. Deletion is explicitly rejected for this base.
8-command × all-flags + JSON-envelope contract; WP01 captures it before any extraction.
- Seam set resolved to 4 (research §3): feature-dir-resolution (D, extracted first),
- 3 mega-functions confirmed needing internal decomposition:
finalize_tasks(1227 LOC, ~8× ceiling), - ~100 patch-target re-export requirement confirmed from the ~50-test patch survey
- Planning-commit residue: LIVE → RELOCATE (research §0/§3/§6 O-1, O-2). On this base the symbols are
- Golden characterization test required FIRST (research §5): no single test currently pins the full