Skip to content

T-KPJE-task-branches-use-task-prefix

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

/sdlc:task-work currently creates implementation branches under the feat/<task-basename> namespace (see plugin/conventions/branch-naming.md). PR #46 review surfaced the suggestion that the prefix should be task/<basename>, since these branches are mechanically created from tasks and exclusively tied to the task lifecycle. The task/ shape would make the namespace self-describing (“this branch belongs to a task entity”) and would let tooling like count_inflight_tasks.py filter by intent rather than by historical convention. This task is the convention change plus the migration story for everything currently on disk and on origin that lives under feat/.

  • plugin/conventions/branch-naming.md documents three prefixes: feat/<basename> (task-work’s implementation branch), docs/<basename> (spec-PR before task-work), and chore/<short-slug> (bulk-work skills). The feat/ prefix is the one in question.
  • plugin/skills/task-work/SKILL.md Step 4 creates the branch as feat/<basename> and creates the worktree at .claude/worktrees/<basename>/ pointing at it.
  • plugin/skills/task-close-out/SKILL.md discovers merged PRs via gh pr list --search "head:feat/<basename>" and deletes the local
    • remote feat/<basename> branch on success.
  • plugin/skills/orchestrate/SKILL.md reasons about in-flight tasks in terms of “the feat/<basename> branch + matching worktree” pair, and refers to the prefix in its prose.
  • plugin/scripts/count_inflight_tasks.py filters with git branch --list 'feat/*' (line ~174) and constructs the expected branch name as branch = f"feat/{basename}" (line ~263). Worktrees on a non-feat/ branch are skipped explicitly as “not the orchestrator’s concern” (lines 38–40, 264–266).
  • Existing on-disk state at the moment this task lands: every in-flight worktree under .claude/worktrees/ tracks a feat/<basename> branch, and origin has corresponding feat/<basename> refs for any open PR.

/sdlc:task-work creates task/<basename> instead of feat/<basename>. Every reader of the namespace (task-close-out, orchestrate, count_inflight_tasks.py, branch-naming.md, any SKILL.md prose that names the prefix) is updated in lockstep. The old feat/ namespace is reserved (and called out in branch-naming.md) as deprecated-but-recognised during a transition window so in-flight branches keep working until they merge or are renamed.

  1. Update plugin/conventions/branch-naming.md:
    • Rename the feat/<task-basename> section to task/<task-basename> and explain the rationale.
    • Add a “Migrating from feat/” subsection covering the transition window — readers MAY accept either prefix until all in-flight feat/ branches drain.
    • Confirm the docs/<basename> namespace is unaffected (specs remain on docs/, NOT collapsed into task/).
  2. Update plugin/skills/task-work/SKILL.md Step 4 to create task/<basename>. Update every other Step that names the prefix (worktree path stays at .claude/worktrees/<basename>/).
  3. Update plugin/skills/task-close-out/SKILL.md to search head:task/<basename> and delete task/<basename>. During the transition, try task/ first then fall back to feat/ so already-merged-but-not-yet-closed-out tasks still resolve.
  4. Update plugin/skills/orchestrate/SKILL.md prose to refer to task/<basename> and update the in-flight-task definition in its invariants.
  5. Update plugin/scripts/count_inflight_tasks.py:
    • list_local_branches filters task/* (with feat/* accepted during the transition window — emit a deprecation warning to stderr when a feat/ branch is counted).
    • branch = f"task/{basename}" (line ~263), with the transition-window fallback consulting feat/<basename> next.
    • Update the module docstring (lines 9–40) to use task/.
  6. Update every other SKILL.md / convention / doc that mentions feat/<basename> literally. At minimum, grep the plugin for feat/< and update each hit.
  7. Migration story for existing on-disk state:
    • For every open PR currently on feat/<basename>, document the rename procedure (git branch -m feat/<basename> task/<basename> && git push origin -u task/<basename> then re-target the PR; GitHub auto-tracks renames in most cases).
    • For every worktree currently checked out on feat/<basename>, document the local rename via git worktree repair after the branch rename.
    • Decide whether the transition window’s feat/ fallback in readers stays forever (cheap, no harm) or is removed in a follow-up after a few weeks.
  • plugin/conventions/branch-naming.md — rename section, add migration subsection.
  • plugin/skills/task-work/SKILL.md — Step 4 branch name + any prose references.
  • plugin/skills/task-close-out/SKILL.mdgh pr list --search and branch-deletion targets; transition fallback.
  • plugin/skills/orchestrate/SKILL.md — prose and invariants referring to the prefix.
  • plugin/skills/orchestrate/invariants.yaml — pins that name the prefix.
  • plugin/scripts/count_inflight_tasks.pylist_local_branches filter, branch-name construction, module docstring, transition fallback + deprecation warning.
  • Every other plugin file containing feat/< (grep first, list before implementing).
  • AC-1: A fresh /sdlc:task-work <basename> run creates task/<basename> (not feat/<basename>) and the resulting PR closes out cleanly via /sdlc:task-close-out. (agent-manual via a real task)
  • AC-2: count_inflight_tasks.py --format json against a fixture with one task/<basename> and one feat/<basename> worktree counts both as in-flight, and stderr contains a deprecation warning naming the feat/ branch. (auto via fixture)
  • AC-3: Grep for feat/< across plugin/ returns only the migration-window references (and the plugin/conventions/branch-naming.md historical note). (auto)
  • AC-4: plugin/conventions/branch-naming.md documents both the new convention and the migration procedure for in-flight PRs/worktrees. (auto via reviewer eyeball)
  • Removing the feat/ fallback. That happens after the transition window in a follow-up; this task just lands the rename + the fallback.
  • Renaming the docs/<basename> spec-PR namespace. That prefix is independently meaningful and stays as-is.
  • Renaming chore/<...> bulk-work branches. Out of scope.
  • Renaming the worktree directory shape under .claude/worktrees/<basename>/. The basename-only directory layout is independent of the branch prefix and stays.
  • none

Spawned from PR #46 review comment by sksizer on plugin/scripts/count_inflight_tasks.py:38 — “could we use task/ as the branch feature instead since they are created with tasks?”

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

  • AC-1: deferred-user — A fresh /sdlc:task-work <basename> run creating task/<basename> and closing out cleanly is end-to-end-verifiable only by running the merged skill on a new task. The runtime version of task-work is loaded from ${CLAUDE_PLUGIN_ROOT}, not the worktree, so this run’s own branch (feat/2026-05-20-task-branches-use-task-prefix) was created by the old SKILL.md before the rename took effect. The start-commit script (start_task.py) is branch-prefix-agnostic and was exercised here against the legacy prefix, confirming the fallback path still works. Please spot-check on the next task pickup that Step 4 creates task/<basename>.
  • AC-2: auto — plugin/scripts/test_count_inflight_tasks.py (new), wired into sdlc.yaml’s quality_checks. Three cases: task/ alone counts, feat/ alone counts AND emits deprecation warning, mixed counts both and warns only on feat/. All pass.
  • AC-3: auto — command grep -rn 'feat/<' plugin/ returns only migration-window references inside readers (task-close-out, count_inflight_tasks, worktree_scope_guard, orchestrate, task-work, sdlc-yaml schema, sdlc-yaml convention, spawn-from-post-mortem, task-review, project-cleanup, branch-naming.md historical note + non-migration carve-out for cross-repo-task-pr), plus the new test file’s docstring/scenario names (intentional). cross-repo-task-pr deliberately keeps feat/ for branches it creates in foreign repos; that’s called out in branch-naming.md as a non-migration use.
  • AC-4: deferred-user (eyeball) — plugin/conventions/branch-naming.md documents the new task/<task-basename> convention, the “Migrating from feat/” subsection with the rename procedure for in-flight PRs/worktrees, and the non-migration carve-out for cross-repo-task-pr. Please eyeball-review.
  • The shared executor pattern made the worktree_init step a silent no-op since this project has no init recipe — zero friction.
  • start_task.py is already branch-name-parameterised; no script changes needed to support the new prefix.
  • The deterministic fixture test for count_inflight_tasks.py exercises the deprecation warning path AND the prefix-preference path in three short scenarios.
  • The currently-running task-work skill is loaded from ${CLAUDE_PLUGIN_ROOT}, not the worktree, so AC-1 (“a fresh task-work run creates task/<basename>”) cannot be verified in the same run that lands the change. The project-local hook does warn about this, but the AC wording still asks for end-to-end verification on this run. Closing the loop would require a bootstrap-skill mechanism (run-the-worktree’s-SKILL.md instead of the global one) — out of scope for this PR but worth a follow-up. → T-ZFE9-task-work-uses-worktree-skill-md
  • audit_entities.py exits non-zero on 8 pre-existing closed-task drift entries (missing template sections that pre-date the current schema). The quality_checks runner short-circuits on the first failure, so I had to run the remaining checks by hand to confirm none of them regressed. A follow-up to either auto-fix the closed-task drift or let audit_entities.py exit 0 when all remaining drift is on closed/* entities would unblock the runner’s first-failure semantics. → T-SDB5-audit-entities-baseline-allow

← Back to Tasks