session-end

v2026.09.24

End-of-session orchestrator. Previews which of wrap/distill/feedback/taskwarrior-sync qualify, single confirm, then sequence. Use when winding down a session.

GitHub
Install command
npx skhub add laurigates/session-end
Markdown
SKILL.md

session-end

One survey, one preview, one confirmation — then run only the end-of-session passes that actually qualify. This is the orchestrator over three capture skills that used to compete for the wind-down moment (design decisions D3/D4, docs/archive/session-plugin-workflow.md):

PassSkillCaptures
Wrapsession-plugin:session-wrapLoose threads → taskwarrior, optional journal, GitHub issues, upstream issue/PR candidates
Distillsession-plugin:session-distillDurable learnings → rules, skill updates, justfile recipes, process/methodology (script+recipe or project-local .claude/skills/)
Feedbackfeedback-plugin:feedback-sessionNotable plugin/skill interactions → GitHub issues on claude-plugins
Taskwarrior sync(inline, no sub-skill)Close done tasks, update statuses, add follow-ups no open PR/issue already tracks; uses stable UUIDs
Blueprint tracker-syncblueprint-plugin:blueprint-feature-tracker-syncDrain closed WO-linked tasks from tracker tasks.pending → tasks.completed (--drain-wave)

When to Use This Skill

Use this skill when...Use alternative when...
User winds down ("wrap up", "done for today") and more than one pass may applyOnly loose threads to capture → session-plugin:session-wrap directly
The Stop-hook nudge offered this skill and the user confirmedOnly learnings to codify → session-plugin:session-distill directly
User invokes /session-endMid-session single-task close → taskwarrior-plugin:task-done

Execution

Execute this orchestration. Not fully automatic by design: filing GitHub issues and writing a journal are not git restore-able, so the single confirmation gate below is mandatory.

Step 1: Survey once

One shared decision pass — do not let each sub-skill re-survey. Run the shared collector (the same one the wrap/spinup skills and the nudge hook use); it emits detection, git state, PRs, taskwarrior tasks with stable UUIDs, and recent commits in one parallel-safe pass. Note two scoping keys in its TASKWARRIOR section: TASK_SCOPE (project / remote-name / ancestor-name / all-projects-fallback / unknown / none) names where OPEN_TASKS was actually counted, and PROJECT_CONFIDENCE (high / low) says whether that slug can be trusted — the detected project is a directory-basename guess and can be wrong (chezmoi source dirs, worktrees, portfolio checkouts, renamed clones). A third pair, PROJECT_AMBIGUOUS / PROJECT_AMBIGUOUS_TASKS, appears only when the detected slug owns zero tasks while a named ancestor slug owns some. A fourth, PROJECT_PREFIX_SIBLINGS / PROJECT_PREFIX_SIBLING_TASKS, appears only when other slugs share the detected slug's prefix — the split taskwarrior's own CLI filter (task project:<slug>) hides, which is how a wrong slug gets "verified" and follow-ups land in a near-empty sibling. PROJECT_EXACT_TASKS is always present: the slug alone, without its . subprojects.

bash "${CLAUDE_SKILL_DIR}/../../scripts/session-survey.sh" --with-commits --with-blueprint --with-dedup

Pass --project <name> to override the detected project. Hand the digest to the confirmed passes in Step 4 so they don't re-survey. --with-dedup is what populates the GITHUB_DRIFT section that Step 4's taskwarrior-sync redundancy test reads — without it the section is always empty and the guard silently runs against nothing. That section also carries GH_READY: false means it is present but unqueried, the same "not clean, just not asked" signal as PROJECT_CONFIDENCE=low above — don't treat an empty GITHUB_DRIFT under GH_READY=false as evidence there's nothing to dedup against. Plus the conversation: what finished, what's hanging, what was learned, what plugin/skill friction or wins occurred.

Remediating GH_READY=false. It always ships with GH_FAIL_REASON=, which says why GitHub went unqueried — the six causes want different responses, so act on the reason rather than treating every false alike. Never re-run for auth, no-cli, or no-remote. In every case the GitHub-derived counts stay unqueried, not zero — so the taskwarrior-sync redundancy test in Step 4 must not use them as evidence a follow-up is untracked. See REFERENCE.md for the per-reason table.

For the Distill qualify gate (Step 2), also run the distill collector's coarse summary — the mechanical half of the Distill signal (recipe candidates, hot files, process groupings). It degrades to TRANSCRIPT_AVAILABLE=false when no transcript is reachable, so treat a SKIP as "no mechanical signal, judge conceptually only":

bash "${CLAUDE_SKILL_DIR}/../../scripts/distill-survey.sh" --session-id "${CLAUDE_SESSION_ID}" --summary

Step 2: Qualify each pass

Apply each skill's own signal filter strictly; silently skip passes that don't qualify — offering an empty pass is the drown-in-signals failure mode.

PassQualifies when
Wrap≥1 genuine loose thread per session-wrap's LOG IT filter
DistillA durable, generalizable learning emerged AND the repo has a distillable surface (.claude/rules/ or a justfile). Corroborate the mechanical half with the distill collector's --summary (below): RECIPE_CANDIDATE_COUNT / HOT_FILE_COUNT / PROCESS_SIGNAL > 0 means recipes/hot-files/process are worth a pass even if no conceptual rule emerged
FeedbackA plugin/skill behaved notably well or badly — bug, enhancement, or positive worth filing
Taskwarrior syncTASK_AVAILABLE=true AND (OPEN_TASKS ≥ 1 OR RECENT_TASK_COUNT ≥ 1 OR PROJECT_AMBIGUOUS_TASKS ≥ 1) in the Step 1 digest. When PROJECT_CONFIDENCE=low, name the scope actually used (TASK_SCOPE, plus PROJECT_RESOLVED when set) in the Step 3 preview and offer --project <slug> — a low-confidence zero is an unqueried project, never a clean queue. When PROJECT_AMBIGUOUS is set, render the preview as 0 here, N under <slug> and offer --project <slug>; this fires even at PROJECT_CONFIDENCE=high, because a user-asserted --project and a repo declaration both deliberately keep high — so the low-confidence escape below does not cover it. When PROJECT_PREFIX_SIBLINGS is present, name it in the preview as N under <slugs> and confirm the slug before filing anything: those slugs are what a task project:<slug> CLI check would have swept in, so a slug verified that way can be the wrong one and the follow-ups land in a sibling nobody reads
Blueprint tracker-syncUNDRAINED_COUNT ≥ 1 in the Step 1 digest's BLUEPRINT section. Non-blueprint / tracker-missing repos auto-disqualify (count is 0) → silent skip. If blueprint-plugin isn't installed, note it and skip (as with Feedback)

Blueprint auto-drain (ADR-0020 level 1): when the qualifying repo's docs/blueprint/manifest.json enables and opts the feature-tracker-sync task into auto-running at autonomy level ≥ 1, that pass is auto-confirmed — leave it out of the Step 3 question, run it in Step 4 order, and report a one-line receipt in Step 5. All other passes still go through the Step 3 confirmation. The gate requires all three fields (issue #2358) — run it as written, auto ⇒ auto-confirm, anything else ⇒ ask:

jq -r 'if ((.automation.autonomy_level // 0) >= 1) and (.task_registry["feature-tracker-sync"].enabled == true) and (.task_registry["feature-tracker-sync"].auto_run == true) then "auto" else "ask" end' docs/blueprint/manifest.json 2>/dev/null

For why all three are required and the safe default for a missing enabled key, see REFERENCE.md.

If nothing qualifies, say so in one line and end — no preview, no question.

Step 3: One preview, one confirmation

Present a single compact preview: each qualifying pass with a one-line reason and its concrete payload (the wrap items; the distill proposal sketch; the feedback finding; the open taskwarrior items with their UUIDs; for blueprint: Blueprint tracker-sync — N closed WO task(s) not drained from the feature tracker: WO-…). Then one AskUserQuestion (multiSelect) listing the qualifying passes as options, qualifying ones described with their reasons. The user picks any subset; "Other" covers adjustments.

Never end the turn on a freeform "y/n" text question — ending the turn fires Stop hooks mid-confirmation (the race that motivated this orchestrator). AskUserQuestion keeps the turn open. Where AskUserQuestion is unavailable (a harness without the tool), ask in plain text and end the turn.

Step 4: Sequence the confirmed passes

Run in this order, each via the Skill tool, or — where there is no Skill tool — read that skill's SKILL.md and follow it (inline for taskwarrior sync), passing along the Step 1 survey so they don't re-do it:

  1. Taskwarrior sync (if confirmed) — run inline before Wrap so Wrap sees the updated queue state. For each open/active task: ask the user (via AskUserQuestion) whether to mark done, update, or leave. Address tasks by stable UUID (task +LATEST uuids after creation; task <uuid> done / task <uuid> modify for existing tasks). Never use volatile numeric IDs — they shift when other tasks complete. Any follow-up added here obeys Wrap's redundancy test: an open PR or assigned issue in the Step 1 digest's PRS / GITHUB_DRIFT sections is already its own tracker — annotate, never mirror it into a task.

  2. session-plugin:session-wrap — closes/annotates/adds tasks so later passes see the final queue state. Any upstream issue/PR candidates it surfaces appear in the Step 3 preview and route per-candidate in Wrap's own Step 4 (track-for-later vs verify-then-file) under this mandatory confirmation gate — filing an upstream issue is not git restore-able, exactly what the gate exists for. A File now candidate always routes through workflow-orchestration-plugin:workflow-verify-before-filing → agent-patterns-plugin:cold-read-gate → file; never blind

  3. Blueprint tracker-sync (if confirmed) — runs after passes 1–2 because both mutate the taskwarrior queue and may close more WO-linked tasks after the Step 1 survey. Re-derive the wave inline right before delegating (never reuse the survey's UNDRAINED_WOS as the drain list):

    task bpid.any: status:completed export 2>/dev/null | jq -r --slurpfile t docs/blueprint/feature-tracker.json '([.[] | .bpid // empty] | unique) as $closed | (($t[0].tasks.pending // []) | map(.id)) as $pending | [$closed[] | select(. as $w | $pending | index($w))] | join(",")'
    

    Then invoke /blueprint:blueprint-feature-tracker-sync --drain-wave <list> with no evidence flags — the sync skill sources evidence from taskwarrior annotations itself (its priority order: files → inline → annotation → ask). If the re-derived list is empty, report "already drained" and move on. Cross-plugin: if blueprint-plugin isn't installed, note it and skip

  4. session-plugin:session-distill — apply mode per its own flow; the user already confirmed the pass, so skip a second blanket prompt but keep distill's per-category destructive-change prompts

  5. feedback-plugin:feedback-session — cross-plugin; if the feedback plugin isn't installed, note it and skip

Step 5: Report

One short block: what each executed pass wrote (tasks touched / closed, files edited, issues filed) and which passes were skipped as not qualifying.

Seam: distill vs feedback

"Discovered a better flag / a skill suggested something subtly wrong" → feedback (issue on claude-plugins). "Found a reusable project pattern, rule, or recipe" → distill (artifact in this repo). When both apply, both run — they write to different places.

Auto-surfacing

A Stop hook (hooks/session-end-nudge.sh) offers this skill at most once per session when the user's own messages carry a wind-down phrase. It is offer-only and stays silent when this skill (or wrap/distill) is already in the transcript. Pre-silence: touch ~/.cache/claude-session-end-nudge/<session_id>.

Agentic Optimizations

ContextCommand
One-pass survey (detection + git + PRs + tasks-with-UUIDs + commits + blueprint tracker state + GitHub-drift dedup)bash "${CLAUDE_SKILL_DIR}/../../scripts/session-survey.sh" --with-commits --with-blueprint --with-dedup
Trust the task count?TASK_SCOPE= + PROJECT_CONFIDENCE= in the TASKWARRIOR section (low ⇒ re-run with --project <slug> before treating 0 as clean)
Which repo do the branch, dirt and PR rows describe?GIT_SCOPE= + GIT_CONFIDENCE= in the GIT section (PRS_SCOPE= / PRS_CONFIDENCE= mirror them). repo ⇒ the checkout at PROJECT_DIR. workspace-root ⇒ an undeclared outer repo contains that checkout and the rows describe it — name GIT_ROOT= when reporting them, and say the cwd sits inside GIT_NESTED_REPO=. Never re-run from the workspace root: the collector already did
Distill qualify signal (recipe/hot-file/process counts)bash "${CLAUDE_SKILL_DIR}/../../scripts/distill-survey.sh" --session-id "${CLAUDE_SESSION_ID}" --summary
Re-derive the drain wave before delegatingtask bpid.any: status:completed export | jq … intersected with tracker tasks.pending (Step 4.3)
Stable UUID for latest tasktask +LATEST uuids
Mark task done by UUIDtask <uuid> done
Distillable surface checkfind . -maxdepth 2 -path '*/.claude/rules' -o -maxdepth 1 -name 'justfile' -o -maxdepth 1 -name 'Justfile'

For the GH_FAIL_REASON remediation table and the Blueprint auto-drain gate details, see REFERENCE.md.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

session-plugin/skills/session-end

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3