Tasks: Consistent Mission-Handle Resolution

Mission: mission-handle-resolution-consistency-01M2TPWG | Branch: issue-4631-4682-mission-handle-resolution Spec: spec.md | Plan: plan.md | Contract: contracts/cli-behavior.md

Six work packages. WP01 is the shared foundation (all others depend on it); WP02–WP05 are file-disjoint per-command fixes (parallel after WP01); WP06 is the cross-command regression net (depends on WP02–WP05). ATDD-first: each WP writes its failing test before the fix.

Subtask Index

IDDescriptionWPParallel
T001Red: unit tests for list_missions_for_selection (0/1/N + legacy no-mission_id)WP01
T002Add list_missions_for_selection(main_repo) (spec/meta population, mid8/friendly_name enrich, slug fallback)WP01
T003Add sole-mission + count helpers on the shared populationWP01
T004Hoist canonical MISSION_NOT_FOUND_MESSAGE / mission_not_found(handle) constantWP01
T005Add __all__ enumerating full public surface + new symbolsWP01
T006Green + ruff/mypy on the seamWP01
T007Red: research --mission <bad> phantom-write + not-found test (snapshot guard)WP02[P]
T008Add .exists() gate after resolve in research.py before any mkdir/scaffoldWP02[P]
T009Emit canonical Mission not found: <handle>, non-zero, before scaffoldWP02[P]
T010Green + confirm no kitty-specs/<bad>/ createdWP02[P]
T011Red: plan/tasks --mission <bad> truthful-not-found + disambiguate-regression testsWP03[P]
T012Branch _build_setup_plan_detection_error on explicit mission_flag (not-found vs disambiguate)WP03[P]
T013Ensure both call-sites (setup-plan, check-prerequisites) surface the not-foundWP03[P]
T014Preserve no-handle multi-mission disambiguate pathWP03[P]
T015Green + ruff/mypyWP03[P]
T016Red: merge --mission <bad> fresh + --resume not-found; --abort tolerance testsWP04[P]
T017Gate fresh path (merge.py:628) with .exists() → canonical not-foundWP04[P]
T018Gate _dispatch_resume (merge.py:391) before the no-state checkWP04[P]
T019Keep merge/resolve.py raw-slug fallback for --abort; leave _dispatch_abort ungatedWP04[P]
T020Green + confirm abort still tolerantWP04[P]
T021Red: bare next 0/1/N discovery + legacy-sole-mission + FR-009-no-preflight-bypass testsWP05[P]
T022Replace _resolve_mission_slug empty-branch raise with a typed missing-handle discovery signalWP05[P]
T023Auto-select sole mission (1); carry listing (N) / empty (0) on the signalWP05[P]
T024Caller (next_cmd.py:195-217) emits + exits: auto-proceed / list / nudge (human + JSON)WP05[P]
T025Render listing as slug (mid8) — friendly_name + backfill nudge; JSON available_missionsWP05[P]
T026Keep nonexistent-but-present handle's clean not-found (regression)WP05[P]
T027Green + ruff/mypy; assert usage semantics not the literal "Invalid value"WP05[P]
T028Cross-command: nonexistent handle → canonical Mission not found: <handle> across fixed commandsWP06
T029NFR-001 snapshot guard across all fixed commands (in-process CliRunner)WP06
T030FR-013 regression: already-correct commands still emit clean not-found (incl. identity/backfill + reconcile envelopes intact)WP06
T031Legacy-mission cross-command count agreement (next vs plan/tasks)WP06
T032Green + full blast-radius runWP06

WP01 — Shared seam: mission population + canonical not-found constant [FOUNDATION]

Goal: Provide the one reusable population/selection helper and the one canonical not-found message the rest of the mission consumes. Priority: P1 (blocks all). Independent test: unit tests for the helper over 0/1/N missions incl. a legacy mission without mission_id; the canonical constant importable and correctly formats a handle.

Subtasks: T001, T002, T003, T004, T005, T006 Depends on: none | FRs: FR-006, FR-012 Prompt: tasks/WP01-shared-seam.md

WP02 — research: existence gate (kill the phantom write)

Goal: research --mission <bad> refuses with the canonical not-found and writes nothing. Priority: P1 (data corruption). Independent test: two real missions + a bad handle → non-zero, kitty-specs/ byte-identical before/after.

Subtasks: T007, T008, T009, T010 Depends on: WP01 | FRs: FR-001 Prompt: tasks/WP02-research-gate.md

WP03 — plan/tasks: truthful not-found

Goal: An explicit unmatched handle yields Mission not found: <handle>, not the disambiguate message; the no-handle multi-mission disambiguate path is preserved. Priority: P1. Independent test: ≥2 missions + bad handle → not-found; no-handle + ≥2 → disambiguate.

Subtasks: T011, T012, T013, T014, T015 Depends on: WP01 | FRs: FR-002, FR-003 Prompt: tasks/WP03-plan-tasks-notfound.md

WP04 — merge: truthful not-found + abort tolerance

Goal: merge (fresh and --resume) refuses a nonexistent handle with the canonical not-found; merge --abort stays tolerant. Priority: P1. Independent test: fresh + resume bad handle → not-found; abort bad handle → tolerant cleanup.

Subtasks: T016, T017, T018, T019, T020 Depends on: WP01 | FRs: FR-004, FR-005 Prompt: tasks/WP04-merge-notfound.md

WP05 — next: missing-handle discovery

Goal: bare next auto-selects (1) / lists (N) / nudges (0) instead of a usage error; nonexistent-but-present handle keeps its clean not-found. Priority: P2. Independent test: 0/1/N missions incl. a legacy sole mission; no Invalid value usage error.

Subtasks: T021, T022, T023, T024, T025, T026, T027 Depends on: WP01 | FRs: FR-006, FR-007, FR-008, FR-009, FR-010, FR-011, FR-012 Prompt: tasks/WP05-next-discovery.md

WP06 — cross-command regression net

Goal: Prove the uniform behavior across commands and guard the pre-existing correct commands (incl. the preserved identity/backfill + reconcile envelopes). Priority: P2. Independent test: a single suite asserting the canonical message + snapshot invariance across the fixed commands and count agreement on a legacy tree.

Subtasks: T028, T029, T030, T031, T032 Depends on: WP02, WP03, WP04, WP05 | FRs: FR-013 Prompt: tasks/WP06-regression-net.md


Dependencies

WP01 ──┬── WP02 ──┐
       ├── WP03 ──┤
       ├── WP04 ──┼── WP06
       └── WP05 ──┘

MVP scope

WP01 + WP02 (the shared seam + the only data-corruption defect) is the minimum shippable slice. WP03/WP04/WP05 complete the consistency story; WP06 locks it against regression.