Quickstart: Implementing feature-alias-removal-01KW0N87
Branch: feat/feature-alias-removal Clone: /tmp/sk-1797/m1060-feature-alias-removal
Before You Start
1. Confirm you are on the correct branch: ``bash git -C /tmp/sk-1797/m1060-feature-alias-removal branch --show-current # Expected: feat/feature-alias-removal ``
2. Confirm _legacy_aliases.py is absent: ``bash find /tmp/sk-1797/m1060-feature-alias-removal/src -name "_legacy_aliases.py" # Expected: no output ``
3. Verify the test suite is green before touching anything: ``bash cd /tmp/sk-1797/m1060-feature-alias-removal PWHEADLESS=1 pytest tests/contract/test_terminology_guards.py tests/contract/test_feature_alias_scope.py -q ``
Edit Sequence (per-WP expected decomposition)
WP01 — Remove --feature from implement.py and merge.py
Files: src/specify_cli/cli/commands/implement.py, merge.py
1. implement.py:
- Remove
feature: Annotated[str|None, typer.Option("--feature", hidden=True, ...)] = Noneparam fromimplement()(line ~934) - Remove
feature: str | Noneparam from_run_recover_mode()(line ~836) - Update
detect_feature_context(): removefeature_flagparam, changeraw_handle = mission_flag or feature_flagtoraw_handle = mission_flag, add whitespace-normalization inline guard raisingtyper.BadParameter - Update call sites:
detect_feature_context(mission, feature, ...)→detect_feature_context(mission, ...)at lines 851 and 999 - Rename
_feature_number→_mission_numberat both call sites (lines 851 and 999) - Update
_run_recover_modecall at line ~979: dropfeaturearg
2. merge.py:
- Remove
feature: str = typer.Option(None, "--feature", hidden=True, ...)frommerge()(line ~412) - Remove
feature: str | Nonefrom_resolve_slug_or_exit()(line ~231); update(mission or feature or "").strip()→(mission or "").strip() - Remove
feature: str | Nonefrom_dispatch_abort()(~277) and_dispatch_resume()(~328); sameor featureremoval in slug computation - Update all three callers in
merge()to dropfeaturearg (~441, ~445, ~453) - Rename
resolved_feature→resolved_missioneverywhere (≈10 occurrences):merge(),_run_real_merge(),_dispatch_resume() - Update no-selector error at line ~501-503:
raise typer.Exit(1)→raise typer.Exit(2)
WP02 — Remove --feature from next_cmd.py, research.py, context.py, accept.py
Files: src/specify_cli/cli/commands/next_cmd.py, research.py, context.py, accept.py
1. next_cmd.py:
- Remove
feature: Annotated[...] = Noneparam (lines ~71-74) - Update
_resolve_mission_slug(mission, feature, repo_root)→ dropfeatureparam, replaceresolve_selector(...)block with inline guard - Update call at line ~116: drop
featurearg
2. research.py:
- Remove
feature: str | None = typer.Option(...)param (lines ~33-38) - Replace
resolve_selector(...)block (lines ~67-79) with inline guard - Remove
except typer.BadParameterblock (inline guard raises BadParameter directly) - Rename StepTracker keys
"feature"→"mission"throughout (cosmetic but consistent)
3. context.py (mission_resolve_command):
- Remove
feature: Annotated[...] = Noneparam (line ~244) - Replace
resolve_selector(...)call (lines ~269-276) with inline guard
4. accept.py:
- Remove
feature: str | None = typer.Option(...)param (lines ~231-236) - Change
raw_handle = mission or feature→raw_handle = mission.strip() if isinstance(mission, str) else None - Update
if raw_handle is None:→if not raw_handle:(catches whitespace-only) - Change
raise typer.Exit(1)→raise typer.Exit(2)in no-selector guard - Rename
feature_slug→mission_slugin_spec_artifact_dirty_paths()and_commit_residual_acceptance_artifacts()parameter and local variable names
WP03 — Remove --feature from lifecycle.py and mission_type.py; positional rename
Files: src/specify_cli/cli/commands/lifecycle.py, mission_type.py
1. lifecycle.py:
specify(): rename positionalfeature→mission(line ~126); update help text; update_slugify_feature_input(feature)→_slugify_feature_input(mission)at line ~133plan(): removefeature: str | None = typer.Option(None, "--feature", ...)(line ~169); changeif mission is not None or feature is not None:→if mission is not None:; replaceresolve_selector(...)block with inline whitespace-normalizationtasks(): same pattern asplan()but also update theelse:branch condition
2. mission_type.py (current_cmd):
- Remove
feature: Annotated[...] = Noneparam (lines ~207-210) - Change
if mission is None and feature is None and not detected_mission:→if mission is None and not detected_mission:(~217) - Change
if mission is None and feature is None:→if mission is None:(~239) - Replace
resolve_selector(...)+except typer.BadParameterblock with inline guard
WP04 — Terminology guard extension and test updates
Files: tests/contract/test_terminology_guards.py, tests/contract/test_feature_alias_scope.py, tests/specify_cli/cli/test_no_visible_feature_alias.py
1. test_terminology_guards.py: add 8 new paths to INSCOPE_FEATURE_FREE_FILES:
src/specify_cli/cli/commands/implement.pysrc/specify_cli/cli/commands/merge.pysrc/specify_cli/cli/commands/next_cmd.pysrc/specify_cli/cli/commands/research.pysrc/specify_cli/cli/commands/context.pysrc/specify_cli/cli/commands/accept.pysrc/specify_cli/cli/commands/lifecycle.pysrc/specify_cli/cli/commands/mission_type.py
2. test_feature_alias_scope.py: flip merge assertions:
test_merge_still_accepts_feature_alias→ now assertsresult.exit_code == 2and"No such option" in result.outputtest_merge_feature_and_mission_both_accepted→ now asserts--featurerejected (exit 2),--missionacceptedtest_merge_feature_alias_is_hidden_in_cli_introspection→ now asserts merge has NO--featureparam- Update
_INSCOPE_FILESand_INSCOPE_COMMAND_NAMESto include the 8 new files/commands
3. test_no_visible_feature_alias.py:
- Add
test_zero_feature_flags_exist_cli_wide()asserting 0--featureparams anywhere in the CLI tree test_every_feature_flag_is_hiddenpasses vacuously (no flags → 0 offenders)
WP05 — No-selector regression tests (FR-008)
File: tests/contract/test_no_selector_guard.py (new)
Add 8 tests, one per command, following the shape in contracts/no-selector-error-contract.md. Each test invokes the command with no --mission arg and asserts exit code 2, no TypeError, and a user-readable error string.
WP06 — Docs and CHANGELOG (FR-010)
Files: docs/status-model.md, docs/reference/environment-variables.md, docs/reference/orchestrator-api.md, docs/engineering_notes/3-2-0-training-bugs-2007/pedro-command-drift.md, CHANGELOG.md
are now alias-free
description to note it is now inert (no --feature warnings are emitted)
to remove --feature
docs/status-model.md:10: update "deferred user-facing top-level" to reflect all top-level commandsdocs/reference/environment-variables.md:232,287: updateSPEC_KITTY_SUPPRESS_FEATURE_DEPRECATIONdocs/reference/orchestrator-api.md:81: remove or update the--featureentry in the command options listdocs/engineering_notes/3-2-0-training-bugs-2007/pedro-command-drift.md:24: update the implement opts listCHANGELOG.md: add unreleased entry describing the complete removal of--featurefrom all 8 commands
Ruff/Mypy Gate
After each WP, run:
cd /tmp/sk-1797/m1060-feature-alias-removal
ruff check src/specify_cli/cli/commands/{implement,merge,next_cmd,research,context,accept,lifecycle,mission_type}.py
mypy src/specify_cli/cli/commands/{implement,merge,next_cmd,research,context,accept,lifecycle,mission_type}.py
No new errors or warnings are acceptable.
Full Suite Gate
After WP05:
PWHEADLESS=1 pytest tests/contract/ tests/specify_cli/cli/ -q -n auto --dist loadfile
After WP06:
pytest tests/architectural/test_no_legacy_terminology.py -q