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.mddocuments three prefixes:feat/<basename>(task-work’s implementation branch),docs/<basename>(spec-PR before task-work), andchore/<short-slug>(bulk-work skills). Thefeat/prefix is the one in question.plugin/skills/task-work/SKILL.mdStep 4 creates the branch asfeat/<basename>and creates the worktree at.claude/worktrees/<basename>/pointing at it.plugin/skills/task-close-out/SKILL.mddiscovers merged PRs viagh pr list --search "head:feat/<basename>"and deletes the local- remote
feat/<basename>branch on success.
- remote
plugin/skills/orchestrate/SKILL.mdreasons about in-flight tasks in terms of “thefeat/<basename>branch + matching worktree” pair, and refers to the prefix in its prose.plugin/scripts/count_inflight_tasks.pyfilters withgit branch --list 'feat/*'(line ~174) and constructs the expected branch name asbranch = 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 afeat/<basename>branch, and origin has correspondingfeat/<basename>refs for any open PR.
Proposed
Section titled “Proposed”/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.
Approach
Section titled “Approach”- Update
plugin/conventions/branch-naming.md:- Rename the
feat/<task-basename>section totask/<task-basename>and explain the rationale. - Add a “Migrating from
feat/” subsection covering the transition window — readers MAY accept either prefix until all in-flightfeat/branches drain. - Confirm the
docs/<basename>namespace is unaffected (specs remain ondocs/, NOT collapsed intotask/).
- Rename the
- Update
plugin/skills/task-work/SKILL.mdStep 4 to createtask/<basename>. Update every other Step that names the prefix (worktree path stays at.claude/worktrees/<basename>/). - Update
plugin/skills/task-close-out/SKILL.mdto searchhead:task/<basename>and deletetask/<basename>. During the transition, trytask/first then fall back tofeat/so already-merged-but-not-yet-closed-out tasks still resolve. - Update
plugin/skills/orchestrate/SKILL.mdprose to refer totask/<basename>and update the in-flight-task definition in its invariants. - Update
plugin/scripts/count_inflight_tasks.py:list_local_branchesfilterstask/*(withfeat/*accepted during the transition window — emit a deprecation warning to stderr when afeat/branch is counted).branch = f"task/{basename}"(line ~263), with the transition-window fallback consultingfeat/<basename>next.- Update the module docstring (lines 9–40) to use
task/.
- Update every other SKILL.md / convention / doc that mentions
feat/<basename>literally. At minimum, grep the plugin forfeat/<and update each hit. - 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 viagit worktree repairafter 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.
- For every open PR currently on
Files to touch
Section titled “Files to touch”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.md—gh pr list --searchand 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.py—list_local_branchesfilter, branch-name construction, module docstring, transition fallback + deprecation warning.- Every other plugin file containing
feat/<(grep first, list before implementing).
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A fresh
/sdlc:task-work <basename>run createstask/<basename>(notfeat/<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 jsonagainst a fixture with onetask/<basename>and onefeat/<basename>worktree counts both as in-flight, and stderr contains a deprecation warning naming thefeat/branch. (auto via fixture) - AC-3: Grep for
feat/<acrossplugin/returns only the migration-window references (and theplugin/conventions/branch-naming.mdhistorical note). (auto) - AC-4:
plugin/conventions/branch-naming.mddocuments both the new convention and the migration procedure for in-flight PRs/worktrees. (auto via reviewer eyeball)
Out of scope
Section titled “Out of scope”- 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.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”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?”
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-22. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: deferred-user — A fresh
/sdlc:task-work <basename>run creatingtask/<basename>and closing out cleanly is end-to-end-verifiable only by running the merged skill on a new task. The runtime version oftask-workis 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 createstask/<basename>. - AC-2: auto —
plugin/scripts/test_count_inflight_tasks.py(new), wired intosdlc.yaml’squality_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.mddocuments the newtask/<task-basename>convention, the “Migrating fromfeat/” subsection with the rename procedure for in-flight PRs/worktrees, and the non-migration carve-out for cross-repo-task-pr. Please eyeball-review.
What worked
Section titled “What worked”- The shared executor pattern made the worktree_init step a silent no-op since this project has no init recipe — zero friction.
start_task.pyis already branch-name-parameterised; no script changes needed to support the new prefix.- The deterministic fixture test for
count_inflight_tasks.pyexercises the deprecation warning path AND the prefix-preference path in three short scenarios.
Friction and automation gaps
Section titled “Friction and automation gaps”- The currently-running task-work skill is loaded from
${CLAUDE_PLUGIN_ROOT}, not the worktree, so AC-1 (“a fresh task-work run createstask/<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.pyexits 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 letaudit_entities.pyexit 0 when all remaining drift is onclosed/*entities would unblock the runner’s first-failure semantics. → T-SDB5-audit-entities-baseline-allow
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-ZFE9-task-work-uses-worktree-skill-md — task-work loads
SKILL.md from the worktree, not
${CLAUDE_PLUGIN_ROOT}(created) - T-SDB5-audit-entities-baseline-allow — audit_entities.py: distinguish pre-existing drift from PR-introduced drift (linked)