Skip to content
Development
Command

/done

Finish a task - document, create PR or merge, close

From plugin
lets-workflow
1726 skills15 agents26 commands
> /plugin marketplace add restarter/lets-workflow
> /plugin install lets@lets-workflow

Command definition

done.md
description: Finish a task - document, create PR or merge, close

Task Done

Complete the current task. Document work, create PR or merge locally, close in the tracker.

**This is NOT session end.** Use `/lets:end` to end a session. `/lets:done` finishes a TASK.

> **Convention used in this file (per CLAUDE.md "Naming Convention: LETS_*"):** > - `{LETS_FOO}` placeholder inside ` ```bash ` snippets AND AskUserQuestion strings - the orchestrator substitutes the literal value before running / before the tool call. Required because Bash tool calls are fresh shells (`$LETS_FOO` unset) and AskUserQuestion renders strings literally. > - `$LETS_FOO` in prose and section headings only - read-only reference to the LETS Config inject. Do NOT use `$LETS_FOO` in bash blocks or AskUserQuestion strings - it silently produces wrong commands or a literal `$LETS_FOO` in the rendered question.

> **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.

Step 1: Active Task Detection

Use the **detect-task** skill to find the active task: `Skill(skill: "lets:detect-task")`. If no task found: ask user which task to close.

Epic Guard

Check the detected task's type via the tracker's `show` verb (beads exposes `type`; an adapter that doesn't expose a type can't epic-guard - skip it). If type is **epic** - do NOT close it automatically:

  • Inform user: "This is an epic. Epics stay open for future tasks."
  • Offer: close a specific child task instead, or confirm epic closure if user insists.

Trunk-mode Routing

Several steps below have a conditional branch for **trunk-mode** — when HEAD is `$LETS_MERGE_BRANCH` (user opted in via the `take-task` picker option "Stay on current branch"). The check is HEAD-based at runtime via `git branch --show-current` compared to `$LETS_MERGE_BRANCH` from LETS Config; no persistent flag.

In trunk-mode the following gates fire:

  • Step 4 commit range: `start:..HEAD` (the task boundary from `.task-<slug>`, not `$LETS_MERGE_BRANCH..HEAD`, which is empty when HEAD IS the merge-branch)
  • Existing-PR Guard: **skip** (PR on same-source-target is not a valid PR, nothing to detect)
  • Step 6 confirm: trunk-mode wording (push + close, no PR)
  • Step 7 completion comment: commit range uses `start:..HEAD` (same reason as Step 4)
  • Step 8 finish: upstream-aware push + close (tracker `close` verb; no PR, no merge, no `git branch -d`)
  • Step 9 output: trunk-mode "Next" options (no "Merge & close", no "Switch to merge-branch")

Trunk-mode requires `detect-task` (Step 1) to have returned an active task. If no task — abort with the standard "no task" path.

Step 2: Check Uncommitted Changes

git status --short

If uncommitted changes exist, by level (a **new file** is a `??` line or a staged `A ` line; it is **named** when the owner's own message in this conversation asked to create it - by path, or unambiguously by name):

  • `{LETS_GATES}` is `standard` or `minimal` (or missing - reads as `standard`) and every new file is named (or there is none) -> `Skill(skill: "lets:commit")` (it commits without asking at this level), then one line naming what was committed, and continue. A commit failure stops `/lets:done`.
  • A new file nobody named, or `{LETS_GATES}` is `strict` -> **AskUserQuestion** below, its question naming each new file when there are any (`You have uncommitted changes, including new file(s) {list}. What to do?`): which new files belong to the task is a real decision - a swept-in file can be cruft or a secret.
AskUserQuestion(
  questions=[{
    question: "You have uncommitted changes. What to do?",
    header: "Uncommitted",
    options: [
      { label: "Commit first", description: "Run /lets:commit before finishing task" },
      { label: "Skip", description: "Continue without committing (changes stay unstaged)" },
      { label: "Cancel", description: "Stop - go back to working on the task" }
    ],
    multiSelect: false
  }]
)

**Handle response:**

  • **Commit first** -> invoke `Skill(skill: "lets:commit")`, then continue. When this card named new files, the commit skill's new-file question is answered by this pick - it does not ask again for those same files.
  • **Skip** -> warn and continue
  • **Cancel** -> stop, return to work

Existing-PR Guard

**Skip this entire guard if HEAD == `$LETS_MERGE_BRANCH` (trunk-mode):** PR on same-source-target is not a valid PR. Nothing to detect, nothing to short-circuit — proceed directly to Step 3. Skip it too when `$LETS_PR_FLOW` is neither `github` nor `bitbucket` (a local merge opens no PR).

A branch can already have a PR: still open (a re-run after "Stay on branch"), merged (a parallel session or the browser shipped it, or the branch is reused for new work), or declined. Ask the forge for EVERY PR of this branch before anything is pushed, and decide by COMMITS - never by the branch name, never against `{LETS_MERGE_BRANCH}` (a squash merge leaves the branch's commits outside it).

A PR **holds HEAD** when its head is HEAD (an abbreviated hash: a prefix of HEAD) or HEAD is an ancestor of it: `git merge-base --is-ancestor HEAD <pr-head>`. Only a merged PR is asked, and the answer is yes, no, or cannot tell (its head does not resolve locally - on github fetch `refs/pull/<number>/head` first). An open PR needs no such test: Step 8's plain push decides.

**Lookup** - read-only, after `git fetch origin --quiet`. Only PRs whose head lives in the repository `git push origin` updates count - parse its owner/name from `git remote get-url --push origin` (the push URL - the fetch URL can name another repository); origin with more than one push URL cannot be vouched for (`git remote get-url --push --all origin` lists them). A PR from any other repository that shares the branch name is not this branch's PR and is ig

Read more
@restarter
Ships withlets-workflow

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

1725 skills15 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
17
Star history of restarter/lets-workflow on GitHub, a chart by star-history.com

Other commands on lets-workflow.

backlog

backlog

Command

Backlog review and cleanup - multi-agent backlog review, quick no-agent pulse (--fast), or interactive triage cleanup

end

end

Command

End a work session - a settlement pass that reconciles uncommitted / unpushed work + session context into git, the tracker, and a…

execute

execute

Command

Execute implementation plan from /lets:plan - inline in native plan mode, or delegated to named implementer subagents you review…

More commands from other plugins.

build
Auto-invoked

build

Command

Implement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

@addyosmani@addyosmaniInstall Command