Skip to content
Development
Skill

/detect-task

Internal skill for commands. Detect the active task (any tracker adapter) from the task-state file / git branch name. Do not trigger on user conversation - only when commands need task detection.

From plugin
lets-workflow
1719 skills15 agents25 commands
Install
$ npx -y skills add restarter/lets-workflow --skill detect-task --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/detect-task

Context preview

The summary Claude sees to decide when to auto-load this skill.

Internal skill for commands. Detect the active task (any tracker adapter) from the task-state file / git branch name. Do not trigger on user conversation - only when commands need task detection.

SKILL.md

detect-task.SKILL.md
name: detect-task
description: Internal skill for commands. Detect the active task (any tracker adapter) from the task-state file / git branch name. Do not trigger on user conversation - only when commands need task detection.
user-invocable: false

Detect Active Task

Parse the task-state file / current git branch to find the active task ID (tracker-adapter-aware). Used by commands that need to know which task is in progress.

> **IMPORTANT:** If the spec below invokes any deferred tool (e.g. `AskUserQuestion`), you MUST load and call it as specified. Never skip the call, never substitute a default answer of your own — the tool invocation is part of the contract. This is critical.

Why This Exists

10+ commands need to detect the active task from branch name. This skill centralizes the logic so branch format changes are updated in one place.

Detection Flow

Step 1: Parse Branch Name

Go reads the branch against the active tracker's convention (the adapter's `## Worktree` `id:` / `branch:` / `worktree-branch:`, overridable per key by the user's board file) - markdown never matches an id regex by eye:

lets worktree info --json --task-candidate --plugin-root "${CLAUDE_PLUGIN_ROOT}"

Take `task_candidate.id` when `task_candidate.source` is `created` - a `branch:` / `worktree-branch:` match is this branch's task by construction. `reason=no_match` or `detached_head` -> the name carries no id; go on (Step 2 fallback). For the `branch=<ref>` variant, add `--ref-file` (see Optional arguments) - Go reads the ref from that file, so an untrusted ref is never typed into a shell.

**`reason=convention_undeclared` / `convention_declaration_invalid`, or no `lets` binary** - parse the branch name (`git branch --show-current`) by the shapes below, unchanged. Formats:

  • `feature/<task-id>-<slug>` - standard LETS branches (main repo)
  • `worktree-<task-id>-<slug>` - worktree branches created via `/lets:worktree create` in new-branch mode (the LETS convention)
  • `worktree-<custom-name>` - worktree branch without an embedded task ID; use fallback
  • any other shape (e.g. `feature/foo`, `bugfix/bar`) - attached existing branch via `/lets:worktree create --attach`; no task ID in the name; use fallback

The `<task-id>` shape is TRACKER-DEPENDENT (the id sits immediately after `feature/` or `worktree-`, up to the first `-<slug>` boundary):

  • beads: `<prefix>-<alphanum>[.<number>]` - e.g. `lets-abc`, `lets-abc.1`, `proj-xyz.42`
  • a numeric-id tracker: a pure-numeric id - e.g. `48647`, so `feature/48647-<slug>`
  • other adapters: the tracker's own id shape

Whatever this parse yields is a CANDIDATE, not an answer: it goes to **the id gate** below, like every other path. On the `branch=<ref>` variant the name being parsed is a pull request's head, which its author chose.

**Do NOT apply the beads `<prefix>-<alphanum>` regex on a non-beads project** - it false-positives on a slug word: `feature/48647-lifecycle-test` makes the beads regex capture `lifecycle-test`, NOT the numeric id `48647`. Match using the ACTIVE tracker's id shape (`{LETS_TRACKER}` from LETS Config). When the branch shape is ambiguous, the `.task-<slug>` file (Step 1.5, authoritative) and the Step 2 search-and-confirm fallback are safer than a branch-name guess.

Step 1.5: Task-State File (fills the gap when the branch name carries no id)

The `.task-<branch-slug>` file (written by `take-task`) records the CURRENT task - authoritative over the branch name, because a worktree branch is frozen at create time and may host several tasks in sequence (the branch name is only the worktree's home task). Read it after the explicit-arg short-circuit, before the branch-name parse. `{LETS_MERGE_BRANCH}` is from LETS Config:

LETS_PROJECT_ROOT=$(git rev-parse --show-toplevel)
BRANCH=$(git branch --show-current); BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-')
TASK_FILE="$LETS_PROJECT_ROOT/.lets/sessions/.task-${BRANCH_SLUG}"
CANDIDATE=$(sed -n 's/^task: //p' "$TASK_FILE" 2>/dev/null | head -1)
ORIGIN=$(sed -n 's/^origin: //p' "$TASK_FILE" 2>/dev/null | head -1)
[ -n "$CANDIDATE" ] && printf 'candidate=%s on_merge_branch=%s origin=%s\n' "$CANDIDATE" \
  "$([ "$BRANCH" = "{LETS_MERGE_BRANCH}" ] && echo yes || echo no)" "${ORIGIN:-none}"

This prints a CANDIDATE, not an answer - it is a value read off disk, and nothing may use it before **the id gate** below. `on_merge_branch` only decides whether the liveness probe runs; `origin` says whether a person or a guess wrote it.

**`origin=branch` or `origin=dir`** (checked first) - `lets worktree adopt` derived this id from an `accept:` branch shape or the directory name of a worktree Orca or a teammate created; nobody confirmed it. Gate it, then probe once:

show task=<TASK_ID from the gate>   # returns {id,title,status,url}; read status
  • `in_progress` or `open` -> use the id and add one line: "task <id> was derived from the branch name - `/lets:start <id>` confirms it".
  • `closed`, any other status, a failed lookup, or `show` absent (the none adapter) -> discard the candidate, do NOT re-parse the branch name, and continue at Step 2 (search-and-confirm for picker callers, None for the no-picker callers in Step 3).
  • Cost bound: `take-task` rewrites the file without `origin:`, so a claimed worktree never pays this probe.

**`on_merge_branch=no`** - take this candidate to the gate and stop looking. The branch corroborates the file, and a just-closed id in a worktree is low-severity (the next claim overwrites it).

**`on_merge_branch=yes`** - the file alone cannot tell a live trunk claim from a stale `.task-main` left by a closed task or a main-mode session, so verify it. Run the candidate through **the id gate FIRST**: this probe is itself a tracker verb, so a raw candidate here would cross the boundary before anything checked it - which is exactly the hole the gate exists to close, and the reason the gate is not merely a final step.

show t
Read more
Ships withlets-workflow

A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.

Get the whole plugin

Other skills on lets-workflow.