SLOPSHOPPER

handoff

/carryover writes a state note from the conversation, clears, and carries the note into the fresh context

newcommandtoastpromptmodel
v0.2.0no licenseupdated 2026-10-09narrowstacks/claude-code-mods/handoff
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · handoff
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /carryover ⎿ handoff: Handoff note saved. Clearing; your next prompt carries it. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

claude-code-mods

Function-hook mods for Claude Code. Each folder is one plugin.

ModWhat it does
context-watchOne-line gauge above the prompt for context fill, cost and hot rate limits; toasts at 60/80/90%; compaction keeps your instructions and edited files; /ctx opens a context breakdown pane
live-spinnerSpinner shows what is running: the Bash description or a trimmed command, the ssh host, file being read or edited, search, subagent, MCP tool; a tool's own elapsed time after 15s; "Waiting for signing approval" when a signed git commit or tag sits quiet
pr-watchTracks PRs from gh pr / gt submit / git push output, shows checks above the prompt, toasts on green/red/merged, blocks foreground CI polling loops; /prs
house-styleNo em dashes: system prompt rule, reminder to the model after a write that adds one, toast when a reply uses one
handoff/carryover [focus] writes a state note from the conversation and clears; the next prompt carries it. /carryover save, show, drop
agent-jobs/jobs opens a pane of running subagents and background shells with elapsed time and a Stop button; toasts when background work finishes
zsh-safeQuotes glob flags zsh would expand (--include=*.ts, find -name *.ts) and keeps a grep that finds nothing from failing a cd ... && grep chain
pm-guardBlocks npm/npx/yarn where the lockfile says bun or pnpm, and steers to bun where there is no lockfile
remote-guardAsks before a destructive command (restart, rm, kill, reboot, pct/qm stop, zfs destroy) runs over ssh
xcode-statusOnly in a folder with an .xcodeproj/.xcworkspace: one line with the last build or test result (flags runs that executed 0 tests) and the booted simulator; blocks a second xcodebuild while one is running; re-checks on /cd and keeps each project's last result
agent-modelsPins the model each subagent type runs on (Explore, general-purpose, Plan, any other, plus custom type=model pairs), set in /config or with /agent-models <type> <model>; a model the caller names wins unless "Override explicit models" is on
worktrees/worktrees pane: each git worktree with branch, uncommitted changes, ahead/behind and PR state; Remove (after asking) on clean worktrees whose PR is merged or closed, plus "Remove N merged" to clear every merged one at once
pr-rulesBlocks gh pr create / gt submit when the repo keeps an [Unreleased] changelog and the branch adds no entry (skip with --label no-changelog); where the repo's CLAUDE.md says to label every PR (or requireLabels is on), blocks a PR with no --label and lists the repo's labels. Repos with neither are untouched; `/pr-rules onoffauto` overrides per repo (kept across sessions)

Install

claude plugin marketplace add narrowstacks/claude-code-mods
claude plugin install context-watch@claude-code-mods

Or load from a checkout without installing:

claude --plugin-dir ./context-watch --plugin-dir ./live-spinner

Check

claude plugin validate ./context-watch
claude plugin test ./context-watch

tsc -p ./context-watch works once Claude Code has loaded the mod and written .claude-plugin/types/.

Source 2 files
hooks/register.ts 123 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Note } from '../types'
5
6// Set once a fresh context starts (a /clear, a new session); the next prompt carries the note.
7const armed = atom({ plugin: 'handoff', key: 'armed' } as const, false)
8
9const MAX_AGE_MS = 24 * 60 * 60 * 1000
10
11const PROMPT = `Write a handoff note so a fresh context can continue this work without the transcript. Plain markdown, under 250 words, these headings only:
12
13## Goal
14## Decisions and constraints
15Every explicit instruction or preference the user gave, verbatim where short.
16## Files touched
17Paths, one line each on what changed.
18## Current state
19What works, what is verified, what is broken or unverified.
20## Next step
21The single next action.
22
23Reply with the note only.`
24
25const noteKey = async ($: EngineInterface) => `note:${await $.session.root()}`
26
27export const readNote = async ($: EngineInterface): Promise<Note | undefined> => {
28  const note = (await $.store.get(await noteKey($))) as Note | undefined
29  if (note === undefined || (await $.clock.now()) - note.at > MAX_AGE_MS) {
30    return undefined
31  }
32
33  return note
34}
35
36const save = async ($: EngineInterface, focus: string) => {
37  const prompt = focus === '' ? PROMPT : `${PROMPT}\n\nThe user wants the note to focus on: ${focus}`
38  const reply = await $.model.fork({ prompt })
39  if (!reply.isAnswered) {
40    return { error: reply.reason === 'nothing-to-fork' ? 'nothing to hand off yet' : `the summary failed (${reply.reason})` }
41  }
42
43  const note: Note = { text: reply.text.trim(), at: await $.clock.now() }
44  await $.store.set(await noteKey($), note)
45
46  return { note }
47}
48
49export const register: Register = on => {
50  on('session.start', async ($, e, next) => {
51    await $.command.register({
52      name: 'carryover',
53      description: 'Save a state note and /clear; the next prompt carries it. Args: [focus] | save | show | drop',
54    })
55    if ((await readNote($)) !== undefined) {
56      await update($, armed, () => true)
57    }
58
59    return next(e)
60  })
61
62  on('command.run', { command: 'carryover' }, async ($, e) => {
63    const args = e.args.trim()
64
65    if (args === 'show') {
66      const note = await readNote($)
67
68      return { text: note?.text ?? 'No handoff note for this project.' }
69    }
70    if (args === 'drop') {
71      await $.store.delete(await noteKey($))
72      await update($, armed, () => false)
73
74      return { text: 'Handoff note dropped.' }
75    }
76
77    const isSaveOnly = args === 'save'
78    const saved = await save($, isSaveOnly ? '' : args)
79    if ('error' in saved) {
80      return { text: `carryover: ${saved.error}.` }
81    }
82    if (isSaveOnly) {
83      return { text: 'Handoff note saved. It rides along with the first prompt after /clear or in a new session here.' }
84    }
85
86    $.command.run({ command: 'clear', args: '' }).then(
87      () => update($, armed, () => true),
88      () => $.ui.toast('Handoff saved. Run /clear to start fresh with it.'),
89    )
90
91    return { text: 'Handoff note saved. Clearing; your next prompt carries it.' }
92  })
93
94  on('command.run', { command: 'clear' }, async ($, e, next) => {
95    const ran = await next(e)
96    if ((await readNote($)) !== undefined) {
97      await update($, armed, () => true)
98    }
99
100    return ran
101  })
102
103  on('prompt.submit', async ($, e, next) => {
104    if (e.origin.kind !== 'composer' || !(await read($, armed))) {
105      return next(e)
106    }
107
108    const note = await readNote($)
109    await update($, armed, () => false)
110    if (note === undefined) {
111      return next(e)
112    }
113
114    await $.store.delete(await noteKey($))
115    $.ui.toast('Handoff note carried in')
116
117    return next({
118      ...e,
119      context: [...(e.context ?? []), `Handoff note from the previous context (written before /clear):\n\n${note.text}`],
120    })
121  })
122}
123
types/index.d.ts 8 lines
1export type Note = { text: string; at: number }
2
3declare module 'claude-code' {
4  interface PluginState {
5    handoff: { armed: boolean }
6  }
7}
8