بنقرة واحدة
end-session
End-of-session ritual — verify, document for humans and robots, commit, close out
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
End-of-session ritual — verify, document for humans and robots, commit, close out
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
| name | end-session |
| description | End-of-session ritual — verify, document for humans and robots, commit, close out |
| user-invocable | true |
| allowed-tools | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, Agent, TodoWrite |
End the current session. "Verify, document for humans and for robots, commit, close out."
This skill has three phases: verify, document, commit + close out. Run all three unless the user says to skip one.
Instrumentation: logs milestones to the shared workflow log alongside the bookend skills — bash .claude/skills/_shared/wflog.sh end-session <step> "<detail>" appends to .claude/workflow-log.jsonl; BRISTLENOSE_WORKFLOW_DEBUG=1 echoes each to stderr. Milestones: start, verify, done. Non-fatal — a logging failure must never stop the close-out. (Uniform observability across the workflow family; end-session also keeps its own last-end-session.json sentinel + audit-log.jsonl telemetry.)
bash .claude/skills/_shared/wflog.sh end-session start
Skip condition: if the only changes since the last commit are documentation files (.md, .txt, locale .json, CLAUDE.md, TODO.md, CHANGELOG.md, CONTRIBUTING.md, MEMORY.md, SKILL.md), skip Phase 1 entirely — there's nothing to break. Check with git diff --name-only and git diff --cached --name-only and git ls-files --others --exclude-standard.
If code files were changed:
.venv/bin/python -m pytest tests/.venv/bin/ruff check . (whole repo, not just bristlenose/)cd frontend && npm run build (tsc catches type errors Vitest doesn't)If anything fails, stop and fix before documenting. Don't document a broken state.
Desktop-bundle freshness (informational, not a gate). If this session touched frontend/ or bristlenose/locales/, the bundled .app's baked SPA is now stale until the sidecar is rebuilt. This does NOT block the close-out and is NOT a release concern (PyPI/Homebrew/Snap build the frontend on a clean CI checkout). It only matters for the next local .app QA. Check and note it in the human summary:
desktop/scripts/check-sidecar-freshness.sh 2>&1 | tail -1 # "STALE" / "no .source-stamp" → rebuild before next .app QA
If it reports stale, mention in the summary: "bundled sidecar is stale vs frontend — desktop/scripts/build-sidecar.sh (runs npm build) before next .app QA." Don't rebuild it automatically — it's a ~2-min step the user may not need this session.
bash .claude/skills/_shared/wflog.sh end-session verify "<pass|skipped-docs-only>"
Two audiences, done in parallel where possible.
TODO.md — mark completed items done, add new items discovered during the session. Update the "Last updated" date. If the near-horizon roadmap changed, reorder it.
100days.md sprint progress — read docs/private/100days.md. Find the current sprint (check the sprint schedule table against today's date). Compare items tagged with that sprint — and any untagged items — against what was accomplished this session (check git log --oneline since session start, TODO.md completions, and the work just done). Strike through completed items with ~~. If an item is partially done, leave it unstruck but add a brief parenthetical note (e.g. — phase 1 done, phase 2 remains). Don't touch items the session didn't work on.
Design docs — if the session produced or updated a design doc (docs/design-*.md), check that it reflects the final state, not an intermediate plan. Remove stale TODOs inside design docs that were resolved.
Catches things this branch did that aren't yet visible in the project's durable artefacts. Two scans share one engine; a single consolidated prompt presents both kinds of finding so the user judges them as one set instead of context-switching between two pop-ups.
The two failure modes the audit catches — both have repeatedly cost plan-cycles when missed:
git log -p so they propose the rejected approach again. Worked example: F49 (e4037d5, 15 May 2026) chose icloud outline over icloud.and.arrow.down and deferred explicit download to post-TF. 3 days later, multi-project-cloud-evicted's handoff proposed exactly the rejected affordance because nothing in docs/ knew F49 had decided otherwise.Skip the whole audit if the branch touched only docs / config / lockfiles (signal-to-noise too low). Audit is otherwise non-blocking — empty result on both scans = silent skip.
Window — what range of commits constitutes "the work to audit":
git merge-base main HEAD..HEAD by default. But honour the sentinel on a repeat end-session — if .claude/last-end-session.json exists, its head_sha is an ancestor of HEAD (clean continuation, not rewritten history), AND the most-recent .claude/audit-log.jsonl entry for this branch shows no pending defers (scan_a.defer == 0 && scan_b.defer == 0, or no entry at all), narrow the base to that head_sha — audit only work since the last sign-off. The pre-sentinel commits were already audited last run; re-scanning them is the duplication this avoids. When narrowed, note it in the consolidated prompt (audit windowed to <short-sha>..HEAD — N pre-sentinel commits already audited, no defers pending)./end-session (from the sentinel's head_sha). If no prior sentinel, fall back to HEADWhy the defer guard (don't narrow blindly): a deferred finding lives in a pre-sentinel commit and is meant to re-surface next session — narrowing past it would silently bury it. So narrow ONLY when the last audit deferred nothing; if it deferred anything, keep the full merge-base window so the deferred items re-surface. Re-scanning the wide window is cheap-of-output even so: the finding-level filters (Scan A already-recorded check, Scan B struck-bullet filter) drop everything already actioned. This refinement does NOT bump audit_version — the matcher + doc set are unchanged, and a first/never-sentineled audit behaves identically; it only de-dups repeat scans, so existing sentinels stay current (no spurious /close-branch re-prompts).
Inputs:
# Window base: full branch by default; narrow to the last sentinel ONLY on a repeat
# end-session with no deferred findings pending (defers must re-surface, so they
# force the full window). Finding-level filters de-dup output either way.
WINDOW_BASE=$(git merge-base main HEAD 2>/dev/null || echo HEAD~10)
SENTINEL=.claude/last-end-session.json
if [ -f "$SENTINEL" ]; then
SENT_SHA=$(python3 -c "import json;print(json.load(open('$SENTINEL')).get('head_sha',''))" 2>/dev/null)
if [ -n "$SENT_SHA" ] && git merge-base --is-ancestor "$SENT_SHA" HEAD 2>/dev/null; then
PENDING=$(python3 - "$(git branch --show-current)" <<'PY'
import json, sys, os
br = sys.argv[1]; last = None
p = ".claude/audit-log.jsonl"
if os.path.exists(p):
for line in open(p):
line = line.strip()
if not line:
continue
try:
d = json.loads(line)
except Exception:
continue
if d.get("branch") == br:
last = d
# "none" = never audited (nothing was deferred); else the last run's total defers
print("none" if last is None
else last.get("scan_a", {}).get("defer", 0) + last.get("scan_b", {}).get("defer", 0))
PY
)
if [ "$PENDING" = "none" ] || [ "$PENDING" = "0" ]; then
WINDOW_BASE="$SENT_SHA" # incremental: only work since last sign-off
fi
fi
fi
git log "$WINDOW_BASE"..HEAD --format="%H%n%B%n---END---" # commit bodies — Scan A
git log "$WINDOW_BASE"..HEAD --pretty=%s # commit subjects — Scan B keywords
git diff "$WINDOW_BASE"..HEAD --name-only # files touched — Scan B keywords
Sentinel audit_version — when running the audit, also note the audit_version you implemented for inside the sentinel JSON written at step 13a. /close-branch's Step 3.5 will read this to detect "sentinel exists but predates audit feature" (treat as if the sentinel were missing — re-prompt for /end-session). Current audit_version: 1.
Consolidated prompt — once both scans have candidates, present them in a single audit summary:
End-of-session audit
====================
Decisions to promote (Scan A — commit bodies → design docs):
A1. e4037d5: "icloud (outline), not icloud.and.arrow.down …" → docs/design-decisions.md?
A2. ...
Bullets to close (Scan B — branch diff → planning trackers):
B1. [100days.md:267] sidebar drag-drop into folder — matched on `FolderRow.swift`
B2. [TODO.md:42] ...
For each, answer:
- <ID> yes (apply the proposed edit)
- <ID> defer (skip this session, surface again next /end-session)
- <ID> no (record nothing — false match or genuinely still open)
- <ID> CLAUDE.md (Scan A only — write to CLAUDE.md gotchas instead of design docs)
- <ID> <other path> (write to a specific doc you name)
- all yes / all no / all defer
'defer' is the third option (genuinely uncertain whether to promote / strike). Without it, "no" carries two semantics — false match and don't decide yet — and the audit can't distinguish the two on next run.
Instrumentation — log one line per audit to .claude/audit-log.jsonl (gitignored), so we can tune the noise budget against the catch rate:
{"date": "2026-05-18", "branch": "<branch>", "head_sha": "<sha>",
"scan_a": {"surfaced": 2, "yes": 1, "defer": 0, "no": 1},
"scan_b": {"surfaced": 5, "yes": 3, "defer": 0, "no": 2}}
After ~10 audited sessions, review the log: if "no" rate is consistently >70%, the matchers are too loose; tighten the regex / keyword filter. If catch is genuinely cold (consistently 0 surfaced even when branch was substantive), they're too tight.
Matcher — grep commit bodies for decision-shaped sentences:
git log "$WINDOW_BASE"..HEAD --format="%H%n%B%n---END---" | \
grep -B1 -iE "(^| )not [a-z.]+|deferred to|explicitly (chose|rejected)|chose .+ over|post-TF|v2 only|rather than|status-only|never wire|did NOT|out of scope"
Doc targets for promotion (proposed in this order; user can override):
docs/design-decisions.md — the default for user-facing affordance choices.docs/design-*.md — when the decision is feature-specific (e.g. iCloud handling → docs/design-multi-project.md).CLAUDE.md or a sibling CLAUDE.md — when the decision is internal mechanics that future Claude might re-invent (use proc_pidpath not subprocess, cp -RL breaks bundle rpath, etc.). The grep already prefilters for affordance-shaped language, so this branch is rare but real.Already-recorded check — before prompting, grep the proposed target for the feature/affordance keyword. If the decision is already there, silently drop the candidate. Avoids reflexive-dismissal noise.
Promotion format — append a one-line entry:
- <area>: <decision in one line>. Reason: <quote from commit body>. (`<short-sha>`, D Mon YYYY)
Edge cases:
defer; surfaces again next session. If still partial after 3 deferrals, the matcher should probably loosen.CLAUDE.md as a target alongside the design docs.What this won't catch (by design):
/end-session step 10 — CLAUDE.md gotchas — is the right place for those.)Keyword extraction from window inputs:
ProjectRow, cli, s12_render)theme/, stages/s07, frontend/src/islands, desktop/Bristlenose/)package.json, README, CHANGELOG, BRANCHES, MEMORY, TODO, CLAUDE, lockfilesDoc set:
docs/private/100days.mdTODO.mddocs/private/plans/*.mdCHANGELOG.md (catches bullets announced as shipped that never got the planning-doc strike-through — same matcher, different target)Candidate = any markdown bullet that is BOTH:
~~ ... ~~)✅ token + sha pattern)Ranking — by keyword-hit count, weighting file-path matches higher than commit-noun matches (paths are higher-signal). Cap to top 10. Each surfaced candidate carries its match reason inline so the user judges confidence at a glance.
Open question — match presentation: show a numeric confidence score, or stay with the inline match-reason hint we ship today? Numeric score helps the top-10 cap but adds complexity. Worth letting the audit-log data answer empirically before deciding. (Plan author flagged this; leaving open.)
Open question — filename-only thoroughness: path matches with no bullet-text overlap are excluded by default (high recall, low precision, reflexive-dismissal risk). User can request 'more' to surface ranks 11–20, but 'more' overloads two axes (overflow + filename-only). Probably wants a separate 'thorough' answer that adds the filename-only candidates to the prompt. Leaving open until usage shows whether reflexive-dismissal materialises.
Apply strike-throughs. For each confirmed closure:
~~ ... ~~ ✅ shipped <today as "D Mon YYYY"> ()<sha> = merge commit on PR branches, HEAD on direct-on-mainD Mon YYYY, e.g. 18 May 2026)Edge cases:
no → record nothing; unstruck bullets stay open. Primary false-positive mode is "file mentioned but bug still present" — human is the right judge.10 matches → cap at 10;
'more'shows 11–20.
/end-session aborts → no harm, idempotent (struck bullets are filtered next time).What this won't catch (by design):
Worked examples that motivated this scan (18 May 2026 audit during a high-merge window — ~33% silent-closure rate, 4 of 12):
multi-project-folder-watcher row reshape; commit said "F53 subtitle text uses .secondary uniformly", never mentioned the bullet.Set<SidebarSelection> evolution; code comment says "Set enables Cmd+click / Shift+click multi-select natively"; tracker never updated.multi-project-drag-onto; commit message ("copy machinery, progress pill, NewFilesSheet stub") didn't list it.Steady-state silent-closure rate may be lower outside high-merge windows. After ~10 audited sessions of audit-log data, review:
Tighten / loosen the regex and keyword filter accordingly. Update audit_version in the sentinel JSON when the matcher changes materially so /close-branch can detect "sentinel from old audit_version" and re-prompt.
CHANGELOG.md — if a version was bumped, add an entry. Format: **X.Y.Z** — _D Mon YYYY_. If no version bump, skip.
README.md — if a version was bumped, update the changelog section. If a user-visible feature shipped, add it to the feature list if appropriate. Don't touch README for internal-only changes.
CONTRIBUTING.md — only if design system, release process, or dev setup changed.
CLAUDE.md — project-wide conventions, infrastructure gotchasfrontend/CLAUDE.md — React/TS/Vite patterns, test gotchasbristlenose/theme/CLAUDE.md — CSS, design systembristlenose/stages/CLAUDE.md — pipeline, transcript formatbristlenose/llm/CLAUDE.md — providers, credentialsbristlenose/server/CLAUDE.md — FastAPI, data APIdesktop/CLAUDE.md — macOS app, bridge, SwiftUIThe test: "If I removed this line, would a future Claude session make the same mistake?" If yes, add it. If not, skip it. Don't add things Claude can infer from reading the code.
Auto-memory — save anything that should persist across conversations but doesn't belong in CLAUDE.md:
Write to /Users/cassio/.claude/projects/-Users-cassio-Code-bristlenose/memory/ following the memory file format (frontmatter with name, description, type). Update MEMORY.md index if a new file was created.
Skip if nothing new was learned. Don't write memory for the sake of writing memory.
If this session implemented against a HANDOFF.md (symlink to .claude/plans/<branch>.md, canonical home ~/Code/bristlenose/docs/private/handoffs/<branch>.md), capture any judgement calls that deviated from the original §Scope before closing.
Why: the original §Scope is what QA reads against. If the implementer changed a control from disabled-to-hidden, picked a different component than specced, deferred a sub-item to a follow-up branch, or chose inline over an extracted molecule — and only documented it in a code comment — QA verifies against the wrong spec. The fix is to log the drift in the handoff as it happens, so the next reader (QA, reviewer, future-Claude) sees the current spec alongside the original reasoning.
Heuristic — fire the prompt automatically. Grep this session's commits and changed files for deviation markers; if any match, run the prompt below. (If they don't match, still ask once — the implementer may have made an undocumented call.)
# Commit messages since branch diverged from main
git log main..HEAD --format=%B | grep -iE "judgement call|decided to|instead of|deviation|chose .* over|hide.*instead|disabled.*instead"
# Code comments touched this branch
git diff main...HEAD -- '*.ts' '*.tsx' '*.py' '*.swift' | grep -iE "^\+.*(judgement call|decided to|instead of|reason:|rather than|visual noise)"
Prompt to the user (or to self if running autonomously):
Did you make any judgement calls during implementation that weren't in the original handoff? Common examples:
- changing a control from disabled-to-hidden (or vice versa)
- picking a different component over the one specced
- deferring a sub-item to a follow-up branch
- choosing inline over an extracted molecule (or vice versa)
- swapping an icon, copy string, or interaction pattern
If yes, append to §Decisions in
HANDOFF.mdbefore closing.
Pattern — append-only ## Decisions during implementation block. Preserve the original §Scope verbatim (useful for "why did we change our mind?"); the §Decisions block carries the current spec. QA reads both together.
## Decisions during implementation
- YYYY-MM-DD: <decision in one line>. Reason: <why>. Supersedes §<section> "<original wording verbatim>".
Example (from pipeline-completion-trust-ux, 10 May 2026):
- 2026-05-10: RefreshButton renders nothing when `lastRun === null` rather than rendering disabled. Reason: a disabled control with no path to enabled is just visual noise; empty-state copy carries the page. Supersedes §Scope item 1 "Pre-pipeline state: button is disabled (no run to refresh against). Read `lastRun === null` from the store."
Edit the canonical path (~/Code/bristlenose/docs/private/handoffs/<branch>.md in the main repo) so the change survives worktree removal. The HANDOFF.md symlink in this worktree points at the .claude/plans/ copy — if the canonical path no longer exists, write to whichever path resolves.
If this session did not work off a HANDOFF, skip this step.
If this session was a diagnostic / sandpit / planning walk that identified one or more follow-up branches the next session should pick up — write a handoff prompt for each before closing out. Do not assume the next session will reverse-engineer it from your logs.
Path: ~/Code/bristlenose/docs/private/handoffs/<branch>.md (gitignored, lives in main repo, picked up automatically by /new-branch <branch>).
Required shape: see docs/private/handoffs/README.md in that directory. Sections — Purpose / Context (cold-read) / Spec / Call sites / Acceptance / Out of scope / Open questions. Self-contained — readable cold by a fresh session.
/new-branch invocation line — --kind is a closed enum. When you draft the recommended /new-branch <branch> --plan=… --kind=… --purpose=… --files=… line at the bottom of the handoff, --kind MUST be one of the values in docs/BRANCHES.md § "Branch Kinds" — the single source of truth. Currently: feature | bugfix | refactor | docs | ci | chore | spike | diagnostic | parked. Pick the one that best matches the work shape. Kind is descriptive metadata for human readers of BRANCHES.md, not control flow. /new-branch rejects anything outside the enum and stops before creating the branch — so if you're unsure, check docs/BRANCHES.md rather than guessing.
The test: "If a future Claude session opened the new branch and read only this file, would they know exactly what to do?" If no, expand the handoff before closing the session. The cost of writing it now is a few minutes; the cost of skipping it is the next session re-doing the diagnostic walk to figure out its own purpose.
If this session did not identify follow-up branches, skip this step.
Check for uncommitted changes — git status + git diff --stat
Stage and commit — commit all changes from this chunk with a descriptive message. If multiple logical changes were made, ask the user whether to bundle into one commit or split.
Commit message style: short, descriptive, lowercase. Examples:
add security findings to TODO, document bridge handler wiringfix tag suggest offering tags the quote already hasinspector panel with drag-resize and signal card selection13a. Write end-of-session sentinel — once Phase 2 (document) has completed successfully and any required commit has landed (or has been skipped because there was nothing to commit), write .claude/last-end-session.json in the worktree root. This trigger is independent of step ordering — it fires whether the session went through the standard commit path (step 13), the branch-handoff path, or the no-changes-to-commit short-circuit. Write only once per /end-session invocation; subsequent steps (maintenance, QA backlog, push) don't re-trigger it. The sentinel reflects "documenting was committed", not "push succeeded" — pushed records the decision made at step 17, but the sentinel's primary consumer (/close-branch drift detection) only reads head_sha. This is the positive sentinel /close-branch reads to confirm sign-off and detect drift. Gitignored.
Schema (ASCII-only — use ensure_ascii=True if writing via Python):
{
"completed_at": "2026-05-14T09:52:18Z",
"head_sha": "<full 40-char SHA from git rev-parse HEAD>",
"branch": "<git branch --show-current>",
"phases": {
"verify": "ok" | "skipped-docs-only" | "skipped-no-changes",
"document": "ok" | "skipped",
"commit": "ok" | "skipped-no-changes"
},
"tests": "passed" | "skipped" | "failed",
"lint": "clean" | "skipped" | "errors",
"handoff_drift": "none" | "appended" | "no-handoff",
"audit_version": 1,
"pushed": false | "origin/main" | "origin/main:wip" | "origin/<branch>"
}
audit_version records which version of the Durable-artefact audit (Scan A + B) ran during this end-session. Bump when the matcher or doc set changes materially. /close-branch's Step 3.5 reads it to distinguish "sentinel current with current audit" from "sentinel from before audit feature / from old audit_version" — both treated as stale and re-prompted. Current audit_version: 1.
Timestamp via date -u +"%Y-%m-%dT%H:%M:%SZ". Write only on successful completion of Phase 3 commit (or successful no-op skip). If /end-session aborts mid-phase, leave any prior sentinel in place — stale-but-truthful beats absent. Re-running /end-session overwrites with the new timestamp; that's fine.
bash .claude/skills/_shared/wflog.sh end-session done
Maintenance schedule check — read the "Dependency maintenance" section of TODO.md. If today's date is past any unchecked quarterly/annual item, remind the user it's due.
QA backlog reminder — check docs/qa-backlog.md for unacked items. Remind the user if any exist.
Branch cleanup — check for merged feature branches that can be deleted. Ask before deleting.
Push decision — don't push by default. Remind about the evening release rule (after 9pm London on weekdays; weekends any time). If they want to see work remotely before release: git push origin main:wip. Push only if the user says to.
CI verification — only if pushed. Check the latest push passes CI.
After completing, print a brief summary:
End of session:
- Tests: passed (N tests) / skipped (docs only)
- Lint: clean / skipped (docs only)
- Updated: TODO.md, CLAUDE.md (list what was touched)
- 100days: struck through 2 items in S1 (or "no sprint items completed")
- Memory: saved feedback on X (or "nothing new")
- Handoff drift: appended 1 decision to HANDOFF.md (or "none" / "no handoff")
- Committed: "commit message here" (N files, +X -Y lines)
- Sentinel: wrote .claude/last-end-session.json (HEAD <short-sha>)
- Maintenance: nothing due (or "May 2026 dep review is due")
- QA backlog: 0 unacked (or "3 items need review")
- Not pushed (evening release rule — push with `git push origin main`)
Start work in an ISOLATED git worktree (the exception path — use only when two envs must be live at once, the work is multi-day and main must stay shippable meanwhile, or it's a throwaway spike). Creates the branch + worktree + venv + frontend build + smoke test + BRANCHES.md entry. For ordinary work, use /new-feature (trunk) instead. Any Kind — captured in Step 3.5. The `--from-cloud` flag instead ADOPTS an existing `origin/claude/*` cloud branch (built on phone/cloud, never compiled): fetch → build the Mac env → run the tests the cloud couldn't → preview the merge against main.
Ship a release — bump version, finalise changelog/readme, tag, push, and verify PyPI. The ONLY workflow command that touches public distribution (PyPI, Homebrew, the public changelog, the tag). Evening window on weekdays. Supports --dry-run.
Finish a piece of trunk work on main — runs tests + lint, surfaces human-QA checks, trues docs, adds a changelog line, marks the 100days item done, and commits. No merge (you're already on main). If you're on a branch, it offers to close the branch properly instead.
Archive a merged feature branch — stale marker, detach worktree, update BRANCHES.md (preserves local directory)
Start a piece of work on main (trunk — the DEFAULT path). Loads the plan / 100days item, checks recent history, agrees a plan, and records the task. No branch, no worktree, no env setup. For work that needs an isolated live environment, use /new-branch instead.
Comprehensively reviews SwiftUI code for best practices on modern APIs, maintainability, and performance. Use when reading, writing, or reviewing SwiftUI projects.
استنادا إلى تصنيف SOC المهني