Skip to content

T-I2ML-squash-legacy-feat-branch-references

Status: closed/done · Impact: medium · Complexity: small

The feat/<basename> branch namespace was renamed to task/<basename> (per [T-UNUH-codify-task-branch-naming](/planning/tasks/codify-task-branch-naming/)) with a transition window in which readers (count_inflight_tasks, task-close-out, reconcile, project_cleanup, the resume detector in task-work, etc.) accepted either prefix. The transition window can now close: as of 2026-05-28, zero feat/* branches exist locally or on origin, zero worktrees check out a feat/* branch, and zero open PRs target a feat/* head. The only remaining references are historical merged PRs (#122, #123, #125) and the now-dead-weight transition-window prose, regex matches, and tests scattered across the plugin.

Remove all of them in one sweep. Future readers see a single canonical prefix (task/); future contributors aren’t tempted to re-introduce the legacy form “for compatibility.”

LocationRole today
plugin/conventions/branch-naming.mdThe canonical doc. Carries a “Migrating from feat/” section explaining the rename and a “Whether the feat/ fallback in readers stays forever (cheap, no harm) or gets removed once no in-flight feat/ branches remain” line. The whole section becomes historical with this task.
plugin/scripts/count_inflight_tasks.pyWalks task/* AND feat/* branches when listing in-flight worktrees; emits a stderr deprecation warning for any feat/* match. Module docstring documents both prefixes.
plugin/scripts/test_count_inflight_tasks.pyThree test cases specifically exercise the feat/-prefix code paths: case_feat_only_emits_warning, case_mixed_counts_both, plus the dual-prefix module docstring assertions.
plugin/scripts/project_cleanup.pyIncludes "feat/" in the branch-prefix lists for local-branch enumeration and stale-branch cleanup. Also explicitly builds feat/<basename> candidates when searching for a task’s branch.
plugin/scripts/worktree_scope_guard.pyModule docstring references task/<basename> (or legacy feat/<basename> during the transition window) as the supported prefix.
plugin/lib/lease/reconcile.py_local_task_branches returns both task/<id> and feat/<id> short names; the orphan-branch reconciler treats them as equivalent.
plugin/lib/lease/runtime.pyComments reference legacy feat/<task_id> in two places (the discover_lease fallback paragraph and __init__.py’s module docstring via re-export).
plugin/lib/lease/tests/test_migration.pyHas an explicit test fixture that creates a feat/has-feat-branch branch and asserts the migration’s discovery sequence includes the legacy-prefix follow-up.
plugin/lib/lease/tests/test_runtime.pyDocuments and tests “Legacy feat/ prefix is not lease-eligible” as an explicit case.
plugin/conventions/lease-aware-skills.mdTwo paragraphs mention legacy feat/<task_id> branches in the discover_lease fallback explanation.
plugin/conventions/worktree-scope-guard.mdOne reference in the canonical-form paragraph.
plugin/conventions/sdlc-yaml.mdOne reference inside the max_awaiting_review description.
plugin/schemas/sdlc-yaml.schema.jsonOne JSON-string reference inside the max_awaiting_review schema field description (duplicates the convention doc).

Every reference to feat/<basename> as a current-or-legacy task branch prefix is removed. The codebase reads as if task/<basename> were the only convention there ever was. The “Migrating from feat/” section in branch-naming.md is deleted; the open question about when the fallback comes out is resolved by this commit landing.

Tests that specifically exercised feat/-prefix code paths are deleted (not “skipped” or “marked legacy”) — the code paths they covered are gone. Tests that incidentally created feat/ branches as fixtures get rewritten to use a non-prefixed name (so they still test “an unrelated branch in the repo doesn’t confuse the reader”) without referencing the legacy task-prefix.

Historical references in completed task files under docs/planning/tasks/closed/ (if any) stay — those are historical artifacts, not active code or convention.

  1. Sweep grep. Run grep -rnE 'feat/' plugin/ docs/skills/ and walk every match. Classify each:
    • Convention prose (plugin/conventions/*.md): delete the references.
    • Script docstrings + comments (plugin/scripts/*.py, plugin/lib/lease/*.py): delete references.
    • Code paths (regex matches, branch enumeration lists): remove the feat/ alternative.
    • Schema descriptions (plugin/schemas/*.json): delete the legacy-prefix mention.
  2. Convention docs. In plugin/conventions/branch-naming.md, delete the entire “Migrating from feat/” section (heading + body). Update the surrounding prose so the doc reads as a clean single-prefix convention. In lease-aware-skills.md, worktree-scope-guard.md, sdlc-yaml.md — strike the parenthetical “(or legacy feat/...)” inserts everywhere they appear.
  3. count_inflight_tasks.py. Remove the feat/<basename> candidate construction from the worktree-to-branch resolution. Update the --prefix argv default and the inner enumeration to drop "feat/*". Delete the deprecation-warning code path. Update the module docstring.
  4. count_inflight_tasks test suite. Delete the three feat/-specific test cases (case_feat_only_emits_warning, case_mixed_counts_both, plus the module-docstring deprecation-warning assertions if any). The remaining cases (clean task/ worktrees, stale categorization, PR detection) stay unchanged.
  5. project_cleanup.py. Remove "feat/" from the three branch-prefix lists. Remove the explicit f"feat/{t.basename}" and f"feat/{t.slug}" candidate construction in the per-task branch search. Update the user-facing message that says “(or legacy feat/) branch, no worktree, no remote branch exists.” to drop the parenthetical.
  6. worktree_scope_guard.py + worktree-scope-guard.md. Strike the parenthetical “(or legacy feat/...)” from the docstring and convention doc.
  7. plugin/lib/lease/reconcile.py. Remove feat/<id> candidate construction from _local_task_branches. Update the OrphanBranchFinding docstring and any related prose. The orphan reconciler now only sees task/<id> branches; that matches reality.
  8. plugin/lib/lease/runtime.py. Strike the “legacy feat/<task_id>” parenthetical comments.
  9. plugin/lib/lease tests. In test_migration.py, delete the _git(["branch", "feat/has-feat-branch"], ...) setup and the assertion that the migration’s discovery sequence includes the head:feat/ follow-up. In test_runtime.py, delete the test case asserting feat/ is not lease-eligible (the test’s reason for existence — the legacy fallback — is gone). The remaining tests in both files cover the task/-only flow and stay unchanged.
  10. schemas. Remove the (or the legacy feat/<basename> during the transition window) parenthetical from max_awaiting_review’s description in sdlc-yaml.schema.json.
  11. Run quality checks against the baseline. Expect zero new drift. The lint_task_state_commit_origin linter from #167 should not flag anything (every commit on the task branch will be implementation diff, no task-state frontmatter changes).
LocationKindChange
plugin/conventions/branch-naming.mdmodifyDelete the “Migrating from feat/” H3 section and the open question about fallback longevity.
plugin/conventions/lease-aware-skills.mdmodifyStrike the two legacy-feat/ parentheticals.
plugin/conventions/worktree-scope-guard.mdmodifyStrike the legacy-feat/ parenthetical.
plugin/conventions/sdlc-yaml.mdmodifyStrike the legacy-feat/ parenthetical inside max_awaiting_review.
plugin/scripts/count_inflight_tasks.pymodifyRemove feat/ candidate construction, deprecation warning, and module-docstring references.
plugin/scripts/test_count_inflight_tasks.pymodifyDelete the three feat/-specific test cases.
plugin/scripts/project_cleanup.pymodifyRemove "feat/" from three branch-prefix lists; remove feat/<basename> candidate construction; update the user-facing message.
plugin/scripts/worktree_scope_guard.pymodifyStrike the legacy-feat/ parenthetical from the docstring.
plugin/lib/lease/reconcile.pymodifyRemove feat/<id> from _local_task_branches; update OrphanBranchFinding docstring.
plugin/lib/lease/runtime.pymodifyStrike the legacy-feat/ comments.
plugin/lib/lease/tests/test_migration.pymodifyDelete the feat/has-feat-branch fixture and related assertions.
plugin/lib/lease/tests/test_runtime.pymodifyDelete the legacy-feat/ test case.
plugin/schemas/sdlc-yaml.schema.jsonmodifyStrike the legacy-feat/ parenthetical from max_awaiting_review’s description.
  • AC-1: grep -rnE '\bfeat/' plugin/ returns zero matches (excluding plugin/PRINCIPLES.md if it carries unrelated text, and excluding any historical task files referenced from prose). The only acceptable residual feat/ references are in docs/planning/tasks/closed/ (historical) and plugin/conventions/branch-naming.md’s commit-message-prefix table where feat: (with colon, conventional-commit prefix — different concept) may legitimately appear.

  • AC-2: plugin/scripts/test_count_inflight_tasks.py runs to completion with zero feat/-prefix test cases remaining. The remaining test cases all pass.

  • AC-3: plugin/lib/lease/tests/ runs to completion (via pytest at the package root) with zero feat/-prefix test cases remaining. The remaining tests all pass.

  • AC-4: A live /sdlc:task-work run against a fresh open/ready task still succeeds end-to-end. (The prefix removal is a no-op for task/-prefixed flows; this AC just guards against accidental over-removal.)

  • AC-5:

    /Users/sksizer2/.claude/plugins/sdlc/scripts/run_quality_checks.py --config /Users/sksizer2/Developer/dev/sdlc.yaml --diff-against-baseline <origin-main-sha> --baseline-dir /Users/sksizer2/Developer/dev/.sdlc/quality-baselines --line

    reports zero new drift introduced by this branch.

  • Updating historical task files under docs/planning/tasks/closed/ (or any closed/* status task file under docs/planning/tasks/). Those are frozen artifacts of past work; rewriting them would erase context. The grep AC explicitly carves out closed/ content.
  • Updating commit-message conventions to remove feat: as a conventional-commit prefix. feat: (with colon) is a separate concept from feat/<basename> (with slash); the colon form remains valid per Conventional Commits 1.0.
  • Migrating the historical merged PRs (#122, #123, #125) — they’re frozen in GitHub’s history.
  • Adding migration tooling for users who still have local feat/* branches outside the canonical project repo. As of 2026-05-28, zero such branches exist in this project; if a contributor surfaces a stale local feat/* branch later, they can rename via git branch -m.
  • none — the architectural fix [T-SIHV-task-state-frontmatter-commits-on-main-not-worktree-branch](/planning/tasks/task-state-frontmatter-commits-on-main-not-worktree-branch/) shipped (PR #167 merged) and this task complies with it (no task-state frontmatter commits on the task branch).

Surfaced 2026-05-28 after the count_inflight_tasks.py query returned no feat/ worktrees, git branch --list 'feat/*' returned empty, git branch -r --list 'origin/feat/*' returned empty, and gh pr list --state open had no feat/ heads. The transition window is closed; the legacy-prefix accommodation throughout the codebase is dead weight.

Captured by /sdlc:task-work on 2026-05-28. PR: pending.

  • AC-1: auto — grep -rnE '\bfeat/' plugin/ returns zero matches after the sweep (excluding __pycache__).
  • AC-2: auto — plugin/scripts/test_count_inflight_tasks.py reports 1/1 passed with no feat/-prefix cases.
  • AC-3: auto — pytest plugin/lib/lease/tests/test_migration.py test_runtime.py test_reconcile.py reports 96 passed; no feat/ test cases remain.
  • AC-4: agent-manual — this very /sdlc:task-work run succeeded end-to-end up through Step 7 with the canonical task/ flow.
  • AC-5: auto — run_quality_checks.py --diff-against-baseline bd6370fc... reports OK 14/14 (baseline-gated; pre-existing findings ignored).
  • The task body’s “Files to touch” table mapped cleanly to grep hits; classifying each match against the three categories (convention prose / code paths / test fixtures) was straightforward.
  • Baseline-gated quality checks made it easy to confirm the branch introduced zero new drift even though the corpus has 240 pre-existing findings.
  • The lease CLI’s exit-code contract on task acquire worked exactly as documented; one command, one verdict line, no parsing.
  • For grep-AC-driven mechanical-sweep tasks (where the AC is “grep returns zero matches”), the canonical “Files to touch” table tends to under-specify reality — this task’s table listed 13 files but the AC-1 sweep touched ~25. A /sdlc:task-define extension that runs the AC’s grep command and auto-populates the Files-to-touch table from the result would close this gap. Future runs of similar tasks (the next time a transition-window prefix is squashed, a deprecated convention removed, etc.) would benefit.

← Back to Tasks