Skip to content

T-WLNF-scope-task-state-origin-to-branch

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

AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-19 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.

The repo-wide lefthook pre-commit check project-check-task-state-origin (wired in the root lefthook.yml, running sdlc task lint-state-origin) fails globally whenever any other live task/* branch carries a stray state-only “record PR” commit, so it blocks commits on every branch at once — not just the offending one. This follow-up, spawned from T-NLKF-converge-obsidian-bases in sksizer/dev, scopes the check to the branch actually being committed (or to HEAD’s ancestry) so one session’s stray commit no longer blocks unrelated work; alternatively it makes task-work’s append_pr_url land the prs: commit reliably on main so the drift never appears.

The repo-wide lefthook check project-check-task-state-origin (wired in the root lefthook.yml, running sdlc task lint-state-origin) fails globally when any other live task/* branch carries a stray state-only ‘record PR’ commit, blocking commits on EVERY branch — worked around during T-NLKF by LEFTHOOK_EXCLUDE=project-check-task-state-origin. Scope the check to the branch being committed (or to HEAD’s ancestry) rather than failing on another live session’s branch; alternatively make task-work Step 10.4’s append_pr_url reliably land the prs: commit on main, never on a task branch. Sibling context: T-8NFT (start_task.ts stranding on a pre-existing-drift pre-commit block) and closed/done T-SIHV (task-state commits land on main, not the worktree branch).

T-NLKF-converge-obsidian-bases

LocationRole today
apps/sdlc/lib/model/entities/task/ops/_lint_state_origin_core.ts#listTaskBranchesReturns every local task/* branch via git for-each-ref, with no filter for which branch is actually being committed.
apps/sdlc/lib/model/entities/task/ops/_lint_state_origin_core.ts#lintWalks the commits of every branch listTaskBranches returns and flags each state-only commit, so a stray commit on any sibling task/* branch fails the whole run.
apps/sdlc/lib/model/entities/task/ops/lint-state-origin.ts#inputOp input schema (projectRoot, baseRef); carries no branch-scope field, so the CLI/lefthook invocation can only run the check repo-wide.
lefthook.ymlPre-commit gate project-check-task-state-origin runs sdlc task lint-state-origin with no branch argument, so every commit is gated on all live task/* branches at once.

Scope the lint to the branch being committed: resolve the current HEAD branch and check only its commits, so a stray state-only commit on a sibling task/* branch no longer blocks unrelated work. The alternative named in the Goal — hardening append_pr_url so the prs: commit always lands on main — is deferred (see Out of scope); this task takes the lint-scoping path only.

  1. In _lint_state_origin_core.ts, add a currentBranch(g) helper — taking the Git client, like listTaskBranches(g) / resolveBaseRefs(g, …) — that returns git rev-parse --abbrev-ref HEAD (trimmed), yielding HEAD when the checkout is detached.
  2. Change the signature lint(runner, repo, baseRef) to lint(runner, repo, baseRef, scopeBranch?) (scopeBranch is the new FOURTH argument). lint already builds const g = new Git(repo, { runner }); when scopeBranch is omitted, resolve it there via currentBranch(g). After listTaskBranches(g), filter the result to branches.filter((b) => b === scopeBranch). When scopeBranch is not a task/* branch, or is not among the local task branches, the filtered list is empty and the lint is a no-op returning [0, [], []].
  3. In lint-state-origin.ts, add an optional branch field to the input schema and pass it through as the fourth lint argument (lint(ctx.git, repo, args.baseRef, args.branch)). When it is omitted, lint resolves the current HEAD branch itself (step 2); passing --branch explicitly stays supported for tests.
  4. Leave resolveBaseRefs and the trunk-exclusion logic (commitsOnBranch / isStateOnlyCommit) unchanged — this task only narrows WHICH branches are walked, not how commits are excluded or classified.
  5. Keep the render hook’s messages accurate for the scoped case: when the filtered branch list is empty, the existing “no task/* branches present; nothing to check.” line already reads correctly.
  6. Add regression tests to apps/sdlc/lib/model/entities/task/ops/tests/lint-state-origin.test.ts for sibling-branch isolation (a stray state-only commit on task/other does not fail while HEAD is a clean task/fixture; the offending branch is still flagged when it is the branch checked; committing on a non-task branch with a dirty sibling task/* branch is a no-op).
  7. Confirm the lefthook.yml project-check-task-state-origin line needs no change (the op auto-detects the current branch); update only the inline comment there if it documents the retired repo-wide behavior.
LocationKindChange
apps/sdlc/lib/model/entities/task/ops/_lint_state_origin_core.ts#lintmodifyAdd a currentBranch(g) helper and a fourth scopeBranch? parameter to lint(runner, repo, baseRef, scopeBranch?); filter listTaskBranches(g)’ output down to the branch being committed, resolving HEAD via currentBranch(g) when scopeBranch is omitted.
apps/sdlc/lib/model/entities/task/ops/lint-state-origin.ts#inputmodifyAdd an optional branch input and pass it through as the fourth lint argument; lint resolves the current HEAD branch when it is omitted.
apps/sdlc/lib/model/entities/task/ops/tests/lint-state-origin.test.tsmodifyAdd sibling-branch-isolation cases proving a stray state-only commit on another task/* branch no longer fails the current branch’s commit.
  • AC-1: lint() in _lint_state_origin_core.ts walks only the branch being committed (the current HEAD branch), not every local task/* branch.
  • AC-2: With a state-only commit on branch task/other and HEAD on a clean task/fixture, sdlc task lint-state-origin exits 0 — asserted by a new test.
  • AC-3: With HEAD on the offending task/* branch that carries the state-only commit, sdlc task lint-state-origin still exits 1 and names the violating field (existing regression coverage preserved).
  • AC-4: With HEAD on a non-task/* branch (e.g. main or a meta-task/* branch), the lint exits 0 even when a sibling task/* branch carries a stray state-only commit.
  • AC-5: The pre-existing cases in apps/sdlc/lib/model/entities/task/ops/tests/lint-state-origin.test.ts (clean_branch, state_only_commit_flagged, state_plus_body_change_ignored, non_state_field_change_ignored, main_state_commit_ignored, origin_lagging_local_main) still pass unchanged.
  • AC-6: bun test on apps/sdlc/lib/model/entities/task/ops/tests/lint-state-origin.test.ts passes.
  • Hardening task-work Step 10.4’s append_pr_url to land the prs: commit reliably on main — the alternative fix named in the Goal; deferred, not pursued here.
  • Changing the set of frontmatter fields treated as task-state (TASK_STATE_FIELDS) or the trunk-exclusion logic (resolveBaseRefs).
  • Sibling tasks T-8NFT (start_task stranding on pre-existing drift) and closed/done T-SIHV (task-state commits landing on the worktree branch).
  • none

Spawned by /sdlc:spawn-task-pr on 2026-07-19 UTC from T-NLKF-converge-obsidian-bases in sksizer/dev.


← Back to Tasks