Research: Retire Standalone Tasks CLI

Phase-0 research resolving the spec's open questions and the post-spec gate findings. All claims verified against live code (file:line).

D-01 — Consumer-migration decision (FR-008) → no migration needed

Decision (01KWANGYM89NRT5KNAHVGX8BF5, resolved): the standalone tasks CLI is not deployed to consumer projects — absent from the init/upgrade template payload and the release-packaging scripts. The only historical consumer-side path, .kittify/scripts/tasks/, is already removed on upgrade by the existing m_0_10_0_python_only migration (_remove_tasks_helpers, m_0_10_0_python_only.py:235-251). The packaged src/specify_cli/scripts/tasks/ leaves the wheel automatically on deletion; scripts/tasks/ and .kittify/overrides/scripts/tasks/ are spec-kitty-repo-only dev/snapshot artifacts. Rationale: no consumer carries the surface, so no migration is warranted. Alternatives considered: add an auto-discovered migration removing the override snapshot — rejected (no consumer carries it; would add a no-op migration + a baseline bump).

D-02 — Merge-metadata tolerance → accepted loss (no port)

_prepare_merge_metadata/_finalize_merge_metadata exist only in scripts/tasks/tasks_cli.py:606-642; they wrap canonical record_merge/finalize_merge (mission_metadata.py:512-558) in a try/except (ValueError, FileNotFoundError) that swallows the malformed-meta.json ValueError from load_meta (mission_metadata.py:275-305). record_merge/finalize_merge have zero production callers — the canonical merge path (merge/executor.py, merge/done_bookkeeping.py) writes WP-done status events (_record_merged_wps_done_for_merge) and resolves actor via _resolve_merge_actor; it never writes meta.json merge_history. So the tolerance guarded no shippable path. Decision: delete TestMergeToleranceMalformedMeta (test_feature_metadata.py:856-972); keep the canonical record_merge/finalize_merge direct tests. Record as an explicit accepted loss in the NFR-002 map. Follow-up (out of scope): record_merge/finalize_merge become production-dead — a future dead-symbol sweep candidate.

D-03 — FR-005 without-flag behavior already implemented

collect_feature_summary reads artifacts via _read_text_strict (acceptance/__init__.py:474-479) which raises ArtifactEncodingError (subclass of AcceptanceError, :210) with a message already naming the fix: "Invalid UTF-8 encoding in {path}… Run with --normalize-encoding to fix automatically." (:216). cli/commands/accept.py:318 except AcceptanceError → prints Error: + typer.Exit(1), writing nothing. The real accept.py has no normalize/encoding flag today. Decision: FR-005 = (i) add --normalize-encoding flag wiring delegating to canonical normalize_feature_encoding(repo_root, feature) -> list[Path] (acceptance/__init__.py:600), mirroring the standalone control flow (scripts/tasks/tasks_cli.py:156-205, _collect_summary_with_encoding / _handle_encoding_failure) without copying standalone logic (C-003); (ii) NFR-004 tests: repair-with-flag, no-rewrite default, error-without-flag (asserts the existing AcceptanceError/exit-1 path).

D-04 — Test classification (FR-004/FR-009)

DELETE: test_tasks_cli_commands.py, test_task_helpers.py, tests/specify_cli/scripts/test_task_helpers.py, tests/specify_cli/scripts/tasks/test_tasks_cli.py (+ empty tests/specify_cli/scripts/ package tree), test_standalone_tasks_cli_canonical.py. SURGICAL: tests/utils.py (critical — repoint write_wp to specify_cli.task_utils.support; behaviorally equivalent, set_scalar same-output not byte-identical; used by 40 files. The sys.path injection + run_tasks_cli are removed in WP03, not here — see plan WP shape, removing them earlier breaks collection); test_acceptance_support.py (reclassified DELETE→SURGICAL post-plan gate: 19 tests, 8 drive the REAL spec-kitty accept via CliRunner with non-duplicated behaviors — diagnose-JSON skipped/failed, no-commit merge-pending non-mutation, matrix/meta/events non-mutation, corrupt-lanes.json blocking; acc. resolves to canonical via the thin shim. Swap imports to canonical, keep the real-CLI + canonical-engine tests, re-home only the encoding assertions to FR-005); tests/conftest.py (ensure_imports fixture, removed in WP03); test_feature_metadata.py (drop TestMergeToleranceMalformedMeta); test_accept_pre30_hard_reject.py (keep engine test + add real-CLI regression); test_acceptance_regressions.py (drop T014/T016); test_pre30_guard_wiring.py (drop tasks_cli._command arms); test_lane_regression_guard.py (drop _standalone_task_scripts); test_codebase_sweep.py (drop vacuous standalone sweep). LEAVE: test_template_compliance.py (asserts absence), test_migration_python_only_unit.py, plus false-positives test_atomic_status_commits_unit.py / test_tasks_cli_contract.py (canonical agent tasks, not the standalone surface). write_wp repoint is faithful: standalone task_helpers.{set_scalar,split_frontmatter,build_document,append_activity_log} (scripts/tasks/task_helpers.py:232-300) vs canonical task_utils/support.py:154-225 differ only in whitespace; task_helpers.py:13 carries a "keep in sync" comment.

D-05 — Pre-3.0 hard-reject coverage (FR-009)

Surviving: test_accept_pre30_hard_reject.py::test_collect_feature_summary_rejects_pre30 (:112-133, engine-level). The real accept inherits the reject via accept.py:293→305 except Pre30LayoutError. Gap: no test exercises the typer accept command on a pre-3.0 layout (the deleted tests targeted the standalone tasks_cli.accept_command). Decision: FR-009 adds a thin real-CLI regression (CliRunner on the existing _pre30_repo helper) asserting exit 1 + "spec-kitty upgrade" + no commit.

D-06 — FR-007 allowlist enumeration (counts)

  • test_no_dead_symbols.py: acceptance_support:: (13) + task_helpers:: (21) = 34 symbol entries removed; category_b_grandfathered_legacy baseline → live recompute (do NOT trust the stale "added 22" inline note).
  • test_no_dead_modules.py: 3 _CATEGORY_3 module entries removed; category_3_external_cli_entrypoints 4 → 1 (only commit_guard_hook remains).
  • test_gate_read_literal_ban.py: 1 list_command residual (gate self-validates the pinned file exists → dangling entry MUST go).
  • resolution_gate_allowlist.yaml: 1 _prepare_merge_metadata write-site entry.
  • surface_resolution_audit/inventory.md (2 rows), write_candidate_classification.yaml (3 sites): stale audit rows.
  • test_coord_read_residuals_closeout.py: comment-only; no assertion, no baseline impact.