Skip to main content

next-task — what should I start next?

Answers "what are my next tasks, in priority order?" and "I have 15 minutes — what fits?", then offers to start the pick immediately. Read-only: it never changes a status or merges.

Code before model

The ordering is not decided by the model. The model's only job is to gather the open work (the part that needs tool calls) and narrate the result. The actual ranking — priority → urgency → effort, the time-box filter, and the single recommendation — is computed by scripts/next-task-rank.mjs. This keeps the decision deterministic and auditable (same inputs → same order), per the efficiency principle. Do not re-sort or re-pick in prose — present what the script returns.

Steps

  1. Resolve the actor — current ClickUp member (clickup_get_workspace_members / clickup_resolve_assignees) and GitHub user (get_me).

  2. Fetch open ClickUp tasks assigned to the actor (clickup_filter_tasks, scoped to the actor, open statuses). For each capture:

    • priority → map ClickUp's urgent | high | normal | low straight through.
    • dueDate → the task due date (ISO YYYY-MM-DD), else null.
    • storyPoints → the Story points custom field, resolved by name (don't hardcode the field id — see pre-planning). 1 point = 1 working day.
    • blockedtrue if the task has an unresolved blocking dependency or a blocked status.
  3. Fetch assigned GitHub Issues (search_issues assignee:@me state:open, or list_issues). For each capture:

    • effortLabel → the effort:xs|s|m|l|xl label (strip the effort: prefix), else null.
    • priority → from an urgency:*/priority:* label if present, else null.
    • dueDate → milestone due date if set, else null.
    • blockedtrue if it carries a blocked/status:blocked label.
  4. Normalize each item to the script's input shape and collect into one array:

    {
    "source": "clickup" | "github",
    "ref": "CU-86xxxx" | "#123",
    "title": "…",
    "url": "…",
    "priority": "urgent"|"high"|"normal"|"low"|null,
    "dueDate": "2026-06-10" | null,
    "storyPoints": 3 | null,
    "effortLabel": "xs"|"s"|"m"|"l"|"xl" | null,
    "blocked": false
    }
  5. Rank in the script — write the array to a temp file and run it (a temp file avoids shell-quoting issues with titles):

    node scripts/next-task-rank.mjs --file /tmp/next-task-items.json # full ranked list
    node scripts/next-task-rank.mjs --file /tmp/next-task-items.json --fits-in 2h # only what fits 2h

    It returns { now, fitsInHours, ranked[], recommended }. Each ranked item carries effortHours, fits, and a computed rankReason.

  6. Narrate the result:

    • A short ranked list (top ~7), each line: ref · title · rankReason (the script's reason string), with the link.
    • One recommended: <ref> line — the script's recommended — and its one-line why.
    • If --fits-in was given and recommended is null, say plainly that nothing fits the box and name the smallest item.
    • Offer: "Hand this to /continue?" — on yes, seed /continue step 2 with the chosen ref.
  7. Offer to size the unsized (the only mutation this skill makes — opt-in, confirmed). --fits-in is useless when every item is effort unknown (no ClickUp Story points, no GitHub effort:* label), so a time-box can silently return nothing. For each unsized item, propose an estimate from its content — a GitHub effort: bucket (xs|s|m|l|xl) or a ClickUp story-point number (1pt = 1 working day) — each with a one-line rationale, and offer to write it back: set the ClickUp Story points custom field (resolved by name, per pre-planning) or add the GitHub effort:* label. Apply only the items the developer approves; then the next run (and --fits-in) is meaningful. If the developer declines, re-rank in-session using the proposed estimates so this run still answers "what fits?" — but do not persist them. Never write an estimate without confirmation.

Ranking model (what the script encodes)

  • Order: blocked items sink to the bottom (can't be started); otherwise priority (urgent→low, unset last) → urgency (overdue → due ≤1d → this week → later/none) → effort ascending → soonest due date.
  • Effort → hours: xs≈0.5h, s≈2h, m≈8h (1d), l≈24h (3d), xl≈40h; ClickUp storyPoints × 8h. Used for the ascending tiebreaker and the --fits-in budget.
  • --fits-in: 15m | 2h | 1d → an item fits when its effort ≤ the budget. A short box surfaces only small tasks (a 2h box → xs+s; m/l/xl drop out). Unknown-effort items never auto-fit a box — they're listed but flagged. When everything is unknown the box returns nothing useful — step 7 offers to size the unsized so the box becomes meaningful (and the next run inherits it).

Boundaries

  • Read-only, with one opt-in exception. Produces a ranked list + a recommendation; never changes a status, assigns, merges, or pushes. The only write it may perform is the confirmed sizing write-back in step 7 (a Story-points value or an effort:* label) — offered, never automatic. To act on the pick, hand it to /continue.
  • Reports reality. If a tracker is unreachable, rank what was reachable and name what was skipped — never fabricate tasks or invent priorities/efforts. Items with no priority or effort still appear (ranked last in their tie-break), flagged "no priority" / "effort unknown".
  • For "everything on my plate grouped by urgency" use /status; for a postable digest use /daily-summary.