ktg-plugin-marketplace/plugins/graceful-handoff/README.md
Kjell Tore Guttormsen 1d12231748 docs(graceful-handoff): align README with marketplace standard
Plugin README rewritten from 187 to 354 lines in the same shape as
ai-psychosis, llm-security, config-audit, and ms-ai-architect:

- English (other plugin READMEs are English)
- Standard solo-project + AI-generated disclaimers
- Badges row (version, platform, skill/hooks/pipeline counts, tests, license)
- Table of Contents
- Mermaid architecture diagram (detection / pipeline / resumption / manual)
- 4-step context resolution table (v2.1)
- Components section (skill, pipeline, hooks)
- Workflow examples + safety guarantees + limitations
- Inline version history + Feedback & Contributing

Root README graceful-handoff card updated to reflect v2.1 model-aware
detection and 57-test count (was 36).
2026-05-01 09:14:10 +02:00

355 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Graceful Handoff Plugin for Claude Code
> Auto-trigger session handoff at the context threshold so long-running work survives the next session boundary. Manual `/graceful-handoff` always works as a backup.
*Built for my own Claude Code workflow and shared openly for anyone who finds it useful. This is a solo project — bug reports and feature requests are welcome, but pull requests are not accepted.*
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
![Version](https://img.shields.io/badge/version-2.1.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Skill](https://img.shields.io/badge/skill-1-green)
![Hooks](https://img.shields.io/badge/hooks-3-red)
![Pipeline](https://img.shields.io/badge/pipeline-deterministic-cyan)
![Tests](https://img.shields.io/badge/tests-57-success)
![License](https://img.shields.io/badge/license-MIT-lightgrey)
A Claude Code plugin that solves a structural problem with long sessions: the context window fills (often within ~5 minutes of real work on Opus 4.7), and the user is forced to summarize, commit, and write a continuation prompt under time pressure — or skip steps and lose continuity. This plugin removes those steps from the user's hands using a deterministic JSON pipeline plus three hooks that detect the threshold, auto-execute the reversible work, and auto-load the artifact in the next session.
---
## Table of Contents
- [What Is This?](#what-is-this)
- [The Problem](#the-problem)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [How auto-trigger works](#how-auto-trigger-works)
- [Components](#components)
- [Commands & Arguments](#commands--arguments)
- [Workflow Examples](#workflow-examples)
- [Safety Guarantees](#safety-guarantees)
- [Testing](#testing)
- [Limitations & Open Assumptions](#limitations--open-assumptions)
- [Version History](#version-history)
- [License](#license)
- [Feedback & Contributing](#feedback--contributing)
---
## What Is This?
Three hooks plus one skill that handle session handoff for you:
- **statusLine hint** at 60% and an urgent reminder at 70% — display only, always safe
- **Stop-hook auto-execute** at estimated ≥70% — writes the artifact + creates a commit. Push remains user-triggered
- **SessionStart auto-load** on `source: resume`/`compact` — handoff content is injected into the new session automatically; no `cat` needed
- **Manual `/graceful-handoff`** — always works as a backup, with the same arguments
The skill itself is `disable-model-invocation: true`. The model cannot autonomously invoke handoff — only the user (via the slash command) or the Stop hook (which calls the pipeline script directly, not the skill) can trigger it. This is intentional: handoff is a moment that should be deliberate.
> [!TIP]
> Install the plugin and forget about it. The first time the Stop hook fires, the artifact appears, a commit lands, and `git push` is yours to run when ready.
---
## The Problem
Opus 4.7 fills the context window quickly. On real work — file reads, tool output, agent results — a session can hit 6070% in five minutes. When it happens, three manual steps become rushed or skipped:
1. Summarize the state of the work (commits, local changes, what was tested)
2. Commit and push finished work (otherwise it is lost when the session ends)
3. Write a copy-paste prompt that lets the next session continue without context loss
Doing these three things at 65% context, with the model already forgetting earlier turns, is exactly when mistakes happen. This plugin moves all three out of the critical path:
- **Detection** — the Stop hook estimates context usage and fires at ≥70%
- **Reversible execution** — the artifact is written and committed automatically
- **Irreversible execution** — `git push` stays in your hands; the plugin will never push for you
- **Continuation** — on the next session, the artifact is auto-loaded into context
The ~10% gap between the 60% statusLine hint and the 70% Stop-hook trigger gives you a window to invoke `/graceful-handoff` manually if you want to control the slug or skip the commit.
---
## Quick Start
### Prerequisites
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) v2.x+
- Node.js (any recent LTS — required for hook and pipeline scripts)
- Git repository (the pipeline detects detached HEAD and missing upstream and reports gracefully — it never crashes)
### Install
```bash
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
```
Or enable directly in `~/.claude/settings.json`:
```json
{
"enabledPlugins": {
"graceful-handoff@ktg-plugin-marketplace": true
}
}
```
The three hooks activate immediately on install. No further configuration needed.
### First handoff
Manual:
```
> /graceful-handoff
```
Or just keep working — when context crosses the estimated 70% threshold, the Stop hook fires automatically:
```
⚠️ Auto-handoff utført ved estimert 72% [kilde: direct]:
artefakt /path/NEXT-SESSION-PROMPT.local.md.
Push gjenstår — kjør `git push` når du er klar.
```
Start the next session with `claude --resume` and the artifact is loaded into context automatically.
---
## Architecture
```mermaid
flowchart TB
subgraph Detection["Detection — display + auto-trigger"]
direction LR
SL["statusLine<br/>60% hint, 70% urgent"]
SH["Stop hook<br/>≥70% estimated"]
end
subgraph Pipeline["Deterministic pipeline (Node, no LLM)"]
direction LR
P["handoff-pipeline.mjs<br/>classify → write → stage → commit"]
end
subgraph Resumption["Resumption — auto-load"]
direction LR
SS["SessionStart hook<br/>resume / compact only"]
AR["Archive after read<br/>*.archived.local.md"]
end
subgraph Manual["Manual fallback"]
direction LR
SK["SKILL.md<br/>disable-model-invocation: true"]
end
SL -.display only.-> User
SH -->|spawns| P
SK -->|invokes| P
P -->|writes| AR
SS -->|reads| AR
User((user)) -->|/graceful-handoff| SK
User -->|git push| Done((done))
```
Three independent layers: **detection** (hooks watching context), **pipeline** (deterministic script that does the work), **resumption** (hook that loads the artifact in the next session). Each layer is testable in isolation. The pipeline has no LLM dependencies — `node:test` runs it against fixtures in <8 s.
---
## How auto-trigger works
Claude Code does not expose real-time context-percentage to hooks (Anthropic has closed feature requests [#16988](https://github.com/anthropics/claude-code/issues/16988), [#27969](https://github.com/anthropics/claude-code/issues/27969), [#34340](https://github.com/anthropics/claude-code/issues/34340)). Instead, the Stop hook uses a **4-step resolution chain** (v2.1, in `resolveContextSource()`):
| Step | Source | When used | Source label |
|------|--------|-----------|--------------|
| 1 | `payload.context_window.used_percentage` | If the field is present and > 0 | `direct` |
| 2 | `payload.context_window.context_window_size` + transcript estimate (`chars / 3.5`) | If size > 0 but no `used_percentage` | `payload-size` |
| 3 | `MODEL_WINDOWS[payload.model.id]` + transcript estimate | Opus 4.7 = 1 M, Sonnet 4.6 = 200 K, Haiku = 200 K | `model-map` |
| 4 | `FALLBACK_WINDOW = 1_000_000` + transcript estimate | Last-resort default (2026-aware) | `default-1m` |
When the resolved percentage is ≥ 70%, the Stop hook spawns `handoff-pipeline.mjs --auto --no-push --non-interactive` synchronously (25 s timeout, fits within the 30 s Stop-hook budget). The `additionalContext` message includes `[kilde: <source>]` so the source path is always visible.
**Estimation drift:** Steps 24 use `chars / 3.5` to approximate tokens, which can drift ±10% from Claude's internal counting. The 70% threshold is conservative buffer. Step 1 (`direct`) has no drift.
**Lock file:** `<transcript_dir>/.handoff-lock-<session_id>` is created on first trigger to prevent repeat firing within the same session. Touch happens *before* spawning to win races on rapid Stop events.
**Why 70% (not 65%)?** Earlier designs targeted 65%, but estimation drift and Stop-hook latency make 70% safer. Lower thresholds risk false positives that block normal continuation.
---
## Components
### Skill — `skills/graceful-handoff/SKILL.md`
```yaml
---
name: graceful-handoff
description: Produser handoff-artefakt, commit+push, og copy-paste-prompt for neste sesjon.
disable-model-invocation: true
model: claude-sonnet-4-6
allowed-tools: Bash(git:*) Bash(jq:*) Bash(node:*) Bash(find:*) Bash(pwd:*) Read Write Glob
---
```
Thin orchestration wrapper around the pipeline script. Pinned to Sonnet 4.6 to free Opus budget for the next session. `disable-model-invocation: true` prevents the model from calling the skill on its own — handoff is always user- or hook-triggered.
> [!NOTE]
> `allowed-tools` is *pre-approval*, not restriction. It removes permission prompts for the listed tools but does not block other tools from being invoked. For real sandboxing, use project-level `permissions.deny` rules.
### Pipeline — `scripts/handoff-pipeline.mjs`
Deterministic Node script. Returns structured JSON. No LLM dependencies. Handles:
- Classification of handoff type (`multi-sesjon` / `plugin-arbeid` / `enkelt-oppgave`) based on cwd
- Writing the NEXT-SESSION artifact in the correct directory
- **Explicit staging** of only the artifact (+ `REMEMBER.md` / `TODO.md` if present) — *never* `git add -A`, enforced by a regression test
- Commit-message generation from `git diff --stat` (Conventional Commits)
- Push (unless `--no-push`) with detached-HEAD and no-upstream detection
- Idempotency check: 60 s cooldown on a clean tree with a recent artifact is a no-op
### Hooks — `hooks/scripts/`
| Event | Script | What it does |
|-------|--------|--------------|
| `statusLine` | `statusline-monitor.mjs` | Reads `context_window.used_percentage` from payload. <60% silent, 6069% hint, ≥70% urgent reminder. Display only — never runs git (statusLine scripts are cancellable mid-flight per official docs) |
| `Stop` | `stop-context-monitor.mjs` | Resolves context via the 4-step chain. At ≥70% spawns the pipeline with `--auto --no-push --non-interactive`. Uses a lock file to prevent repeat firing |
| `SessionStart` | `session-start-load-handoff.mjs` | On `source: resume` or `source: compact`, finds the most recent `NEXT-SESSION-*.local.md` (cwd + 3 levels up), injects the content via `additionalContext`, archives the file (`*.archived.local.md`) to prevent stale-load on subsequent sessions |
Registered in `hooks/hooks.json`.
---
## Commands & Arguments
```
/graceful-handoff [topic-slug] [flags]
```
| Argument | Description |
|----------|-------------|
| `[topic-slug]` | Kebab-case slug. With slug: `NEXT-SESSION-<slug>.local.md`. Without: `NEXT-SESSION-PROMPT.local.md` |
| `--no-commit` | Skip commit + push. Artifact is written; user handles git manually |
| `--no-push` | Commit OK, but skip push (the Stop hook always uses this) |
| `--dry-run` | No files written, no git operations; print what would happen |
| `--auto` | Non-interactive, auto-Y on commit confirmation. Intended for hooks |
| `--non-interactive` | Without `--auto`: error. With `--auto`: run without any prompts |
The pipeline script accepts the same flags directly (`node scripts/handoff-pipeline.mjs ...`) — useful for debugging without going through the skill.
---
## Workflow Examples
### Plugin work (auto-trigger)
```
cd plugins/llm-security
# ... work until ~70% context ...
# Stop hook fires automatically:
# → writes plugins/llm-security/NEXT-SESSION-PROMPT.local.md
# → stages ONLY the artifact (not other dirty files)
# → commits with auto-generated Conventional Commits message
# → does NOT push
git push # when you are ready
```
### Manual trigger with slug
```
/graceful-handoff refactor-auth --no-commit
# → writes plugins/<root>/NEXT-SESSION-refactor-auth.local.md
# → no git operations
```
### New session
```
claude --resume
# SessionStart hook auto-injects handoff content into context
# Continue working immediately
# The artifact is renamed to NEXT-SESSION-*.archived.local.md
# so it cannot stale-load on a third session
```
### Dry run before committing the workflow
```
/graceful-handoff --dry-run
# Pipeline prints the JSON it would produce — file paths, commit message,
# next steps — without writing anything or touching git
```
---
## Safety Guarantees
These properties are enforced by tests, not by convention:
- **Push is never automatic.** The auto-execute path always passes `--no-push`. Irreversible operations stay in the user's hands. (`stop-context-monitor.test.mjs`)
- **Staging is explicit.** The pipeline stages *only* the handoff artifact (and `REMEMBER.md` / `TODO.md` if present). `git add -A` is never used — a regression test (`pipeline never stages unrelated dirty files`) enforces this.
- **Pre-commit hooks are respected.** The pipeline never bypasses with `--no-verify`. If a pre-commit hook (gitleaks, pathguard) blocks, the handoff fails and the user fixes the underlying issue.
- **Artifacts are gitignored.** All output files match `*.local.md`, which existing repos in this marketplace already gitignore via `.gitignore` patterns.
- **No network calls.** No WebSearch, no Agent delegation, no MCP. The pipeline is fully local.
- **Bash sub-scoped.** Skill `allowed-tools` enumerates `Bash(git:*) Bash(jq:*) Bash(node:*) Bash(find:*) Bash(pwd:*)` — pre-approval is narrow even though it is not a sandbox.
- **Lock file scoped to transcript directory.** Lock path is based on `dirname(transcript_path)`, not `cwd`, so it survives `cd` mid-session.
---
## Testing
```bash
node --test 'plugins/graceful-handoff/tests/**/*.test.mjs'
```
57 tests across 6 files:
| File | Coverage |
|------|----------|
| `tests/skill-structure.test.mjs` | SKILL.md frontmatter, model pin, allowed-tools shape, removal of legacy `commands/` |
| `tests/scripts/handoff-pipeline.test.mjs` | Pipeline JSON schema, idempotency, **no-staging-regression**, detached HEAD, no-upstream, interactive y/n |
| `tests/hooks/statusline-monitor.test.mjs` | Threshold transitions, null payload, malformed JSON, no side effects |
| `tests/hooks/stop-context-monitor.test.mjs` | 4-step context resolution, lock file behavior, stub-pipeline isolation, env-var failure modes |
| `tests/hooks/session-start-load-handoff.test.mjs` | Source filter (`resume`/`compact` only), multi-level search, archive after read |
| `tests/plugin-manifest.test.mjs` | Plugin.json schema, version pin, CHANGELOG entries |
Stop-hook tests use a **stub pipeline** (a fake `handoff-pipeline.mjs` written into a temp dir) so test runs do not invoke real git operations against the marketplace repo.
The pipeline runs in <8 s on a 2025 Mac. The full test suite runs in ~10 s.
---
## Limitations & Open Assumptions
- **Token estimation drifts ±10%** against Claude's internal counting (steps 24 of the resolution chain). The 70% threshold is set conservatively to absorb this. Step 1 (`direct`) has no drift but requires the payload field to be present.
- **Stop-hook payload schema is undocumented.** It is not officially confirmed that Stop payloads include `used_percentage` or `model.id` (statusLine payloads do). If both are missing, the resolver falls through to `default-1m`. The `[kilde: <source>]` label in `additionalContext` reveals which path was actually used — first real session reveals this.
- **statusLine placement in `hooks/hooks.json` is an open assumption.** Smoke-test before relying on it; the fallback is to move it to global `~/.claude/settings.json`.
- **Issue [#26251](https://github.com/anthropics/claude-code/issues/26251)** — `disable-model-invocation: true` may regress and block user-invocation in some Claude Code versions. Manual smoke-test before relying on it.
- **Auto-execute does not push.** Irreversible operations stay user-triggered, by design.
- **Compaction events are out of scope.** PreCompact fires too late (~95%) and is not configurable. The plugin targets the 6070% window where the user can still benefit from a clean handoff.
---
## Version History
| Version | Date | Highlights |
|---------|------|------------|
| **2.1.0** | 2026-05-01 | **Model-aware context window detection.** Replaces 200 K fallback with 4-step resolution chain (`used_percentage``payload-size``model-map` → 1 M default). Fixes 57× premature firing on Opus 4.7 (1 M window). All `additionalContext` messages include `[kilde: <source>]` for transparency. 6 new tests (57 total). |
| **2.0.0** | 2026-05-01 | **Hard cut from `commands/` to `skills/`.** New deterministic pipeline (`handoff-pipeline.mjs`), three hooks (statusLine, Stop, SessionStart), `disable-model-invocation: true`, sub-scoped `allowed-tools`, explicit staging discipline (no more `git add -A`), pinned to Sonnet 4.6 (BREAKING). |
| **1.0.0** | 2026-04-19 | Initial release — single declarative `/graceful-handoff` command, 6-phase prose workflow, three handoff types, pre-commit hook respect, <60 s time budget. |
Full history in [`CHANGELOG.md`](CHANGELOG.md).
---
## License
MIT. See [`LICENSE`](LICENSE).
---
## Feedback & Contributing
- **Bug reports + feature requests:** open an issue on [Forgejo](https://git.fromaitochitta.com/open/ktg-plugin-marketplace)
- **Pull requests:** not accepted on this repo (solo project, dialog-driven development with Claude Code). Fork freely if you need to extend.
- **Marketplace:** part of [ktg-plugin-marketplace](https://git.fromaitochitta.com/open/ktg-plugin-marketplace) — see the [root README](../../README.md) for related plugins.