No description
  • JavaScript 100%
Find a file
Kjell Tore Guttormsen 2c4e5e425b feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0)
BREAKING: replace the NEXT-SESSION artifact + 3 hooks with a STATE.md-centric,
skill-only design. /graceful-handoff now overwrites the nearest STATE.md (with the
mandatory 👉 NESTE block) instead of writing a separate handover file.

- Remove hooks/ entirely: Stop auto-trigger (operator choice), SessionStart loader
  (redundant with global session-start.sh), statusLine hint (dead — user settings win).
- Invert the pipeline: the model writes STATE.md (only it has the context for 👉 NESTE);
  handoff-pipeline.mjs becomes a slim deterministic helper (--plan / --commit / --dry-run).
- Remote-aware policy: STATE.md tracked on private remotes, local-only (gitignored) on
  public/open mirrors. Authoritative signal: git check-ignore STATE.md.
- SKILL.md rewritten as the Session-Slutt ritual; dropped the Sonnet model pin.
- Docs (README, CLAUDE.md, CHANGELOG), plugin.json 2.1.0→3.0.0, .gitignore cleanup.
- Tests rewritten for --plan/--commit; no-`git add -A` regression preserved. 30/30 green.

Release-cut (tag v3.0.0 + catalog ref bump) pending — separate gated action.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SiKr4c6GAzQH5n6E6f5NA
2026-06-23 21:09:44 +02:00
.claude-plugin feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
docs docs: add full-depth plugin review (2026-06-20) 2026-06-20 09:14:08 +02:00
scripts feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
skills/graceful-handoff feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
tests feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
.gitignore feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
CHANGELOG.md feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
CLAUDE.md feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00
GOVERNANCE.md docs: introduce GOVERNANCE.md and unify fork-and-own blurb 2026-05-03 14:57:00 +02:00
LICENSE feat(graceful-handoff): initial plugin with /graceful-handoff command 2026-04-19 22:54:10 +02:00
README.md feat(graceful-handoff)!: integrate with STATE.md continuity system (v3.0.0) 2026-06-23 21:09:44 +02:00

Graceful Handoff Plugin for Claude Code

One command to end a session cleanly and hand it to the next one — /graceful-handoff reaches a natural stopping point and overwrites the nearest STATE.md with a complete, self-standing state-of-play.

Solo-maintained, fork-and-own. This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See GOVERNANCE.md for the full model and what upstream provides.

AI-generated: all code produced by Claude Code through dialog-driven development. Full disclosure →

Version Platform Skill Hooks Pipeline Tests License

A Claude Code plugin that makes session handoff a single deliberate command, fully integrated with a STATE.md-based continuity system. When a session is filling up — or you simply want to stop — /graceful-handoff reaches a natural stopping point, overwrites the nearest STATE.md with where you are, the next concrete step, and what to read, then commits per your remote policy. The next session reads that STATE.md and stands on its own.


Table of Contents


What Is This?

One skill — /graceful-handoff — plus a slim deterministic helper script. No hooks.

Invoke it whenever you want to hand off. The session:

  1. Reaches the first natural stopping point — finishes the current logical unit, leaves the code working.
  2. Overwrites the nearest STATE.md with a complete state-of-play in a fixed format, headed by a mandatory 👉 NESTE — START HER block (where we are + the next concrete step + what to read).
  3. Commits per remote policy — staging only STATE.md (when tracked), never git add -A.
  4. Leaves push to you, gated by your push window.

The skill is disable-model-invocation: true: the model cannot trigger a handoff on its own. Handoff is a deliberate, user-initiated moment.


The Problem

Long sessions fill the context window. The classic failure mode is summarizing, committing, and writing a continuation prompt under time pressure — or skipping steps and losing continuity. Earlier versions of this plugin attacked that with auto-trigger hooks that wrote a separate NEXT-SESSION-PROMPT artifact.

That turned out to be the wrong shape for a continuity system built on STATE.md:

  • A separate NEXT-SESSION file is exactly the kind of local handover invention a single-source continuity system forbids. STATE.md already is the handoff.
  • The valuable part of a handoff — where are we, what's the next step, what should I readrequires the session's understanding. A deterministic git log snapshot can't write it; only the model can.
  • An auto-trigger hook has no session context, so it could only ever produce a poor snapshot — and would overwrite a good, model-written STATE.md.

v3.0 fixes the shape: the model writes STATE.md; the script does the deterministic mechanics around it.


The STATE.md Model

STATE.md is one layer of a three-layer continuity system:

Layer Job
STATE.md Current state-of-play where you work. Overwritten (not appended) at session end. The nearest one (cwd → repo root) is auto-injected at session start by a global hook.
auto-memory MEMORY.md Durable facts about the user and preferences.
CLAUDE.md Invariants — rules, commands.

Git history is the long-term log. This plugin owns the write side of STATE.md at session end. The read side (injecting the nearest STATE.md into a new session) is handled by the user's global session-start.sh — so the plugin ships no SessionStart loader of its own.

Mandatory STATE.md format

# STATE — <name>
_Current state-of-play. Overwritten (not appended) at session end. History → git._

## 👉 NESTE — START HER
**<one line: where we are + the next concrete action>**
1. **(you/me)** <concrete step>
2. **(you/me)** <concrete step>
<pointers: which files / plans / memory MUST be read to stand on its own>

## Mission & run-mode
## Gotchas / inviolable rules
## Push status
## Repo & env
## Session sequence (short — most of it lives in git)

The 👉 NESTE block is always first, right after the title — it carries the actionable part so the operator never has to hunt for it.


Quick Start

Prerequisites

  • Claude Code v2.x+
  • Node.js (any recent LTS — for the helper script)
  • A git repository (the helper detects detached HEAD / missing upstream and reports gracefully)

Install

claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git

Or enable directly in ~/.claude/settings.json:

{
  "enabledPlugins": {
    "graceful-handoff@ktg-plugin-marketplace": true
  }
}

Hand off

> /graceful-handoff

The session finishes the current unit, writes STATE.md, commits (if your remote policy allows), and prints a fixed closing line: STATE.md status · next session's first action · commit/push status. Start the next session and the nearest STATE.md is loaded into context automatically by your global session-start hook.


Architecture

flowchart TB
    User((user)) -->|/graceful-handoff| SK

    subgraph Skill["SKILL.md — model-driven ritual (full session context)"]
        SK["1. reach natural stopping point<br/>3. WRITE STATE.md (👉 NESTE block)<br/>7. fixed closing line"]
    end

    subgraph Helper["handoff-pipeline.mjs — deterministic, no LLM"]
        PLAN["--plan<br/>resolve nearest STATE.md<br/>classify remote · git facts"]
        COMMIT["--commit<br/>stage ONLY STATE.md (+ --also)<br/>never git add -A"]
    end

    SK -->|2. fetch facts| PLAN
    PLAN -->|JSON| SK
    SK -->|4. safe commit| COMMIT
    User -->|5. git push, in-window| Done((done))

Two pieces, one responsibility each: the skill synthesizes STATE.md (only it has the context); the helper does the deterministic mechanics (path resolution, remote classification, safe staging). The helper has no LLM dependencies and runs under node:test in seconds.


The Handoff Ritual

SKILL.md instructs the session to, in order:

  1. Reach the first natural stopping point. Finish the current logical unit; leave the code working. Don't cut mid-change; don't start new work.
  2. --plan — fetch deterministic facts (resolved STATE.md path, remote class, git status, recent commits).
  3. Write / overwrite STATE.md in the mandatory format (title → 👉 NESTE block → fixed sections → short history). Max ~60 lines. Overwrite, never append.
  4. --commit — stage only STATE.md (when tracked) plus any explicit --also paths the model judges belong to the unit. Never git add -A.
  5. Push — check the window (date '+%u %H:%M'). In-window: push (Forgejo only, never GitHub). Out-of-window: park it and say so explicitly.
  6. MEMORY.md check — if it exceeds 200 lines, warn (don't auto-edit).
  7. Fixed closing line — STATE.md status (path) · next session's first action (= the 👉 NESTE block) · commit/push status.

Components

Skill — skills/graceful-handoff/SKILL.md

---
name: graceful-handoff
description: Reach a natural stopping point, overwrite the nearest STATE.md (👉 NESTE block), commit per remote policy, write the fixed closing line.
argument-hint: "[--no-commit] [--dry-run]"
disable-model-invocation: true
allowed-tools: Bash(git:*) Bash(node:*) Bash(date:*) Read Write Edit Glob
---

No model: pin — the skill inherits the session model, because writing a self-standing STATE.md is human-facing synthesis that warrants top-tier quality.

Note

allowed-tools is pre-approval, not a sandbox. It removes permission prompts for the listed tools but does not block others. For real sandboxing, use project-level permissions.deny rules.

Helper — scripts/handoff-pipeline.mjs

Deterministic Node script, structured JSON output, no LLM dependencies:

  • --plan (default, read-only): resolve the nearest STATE.md (cwd → repo root), classify the origin remote, gather git facts, and report whether STATE.md is gitignored (the authoritative commit signal) plus a leak_warning on misconfiguration.
  • --commit: stage STATE.md only when it is tracked, plus explicit --also <path> paths; commit with a generated or supplied message. Never git add -A. Never pushes. Detached-HEAD guard.
  • --dry-run: emit the plan JSON, write nothing, touch no git.

Remote Policy — tracked vs local-only

STATE.md must never reach a public mirror. The helper classifies origin:

Remote Class STATE.md
github.com / github.io public local-only (gitignored), never committed
…/open/<repo> (public Forgejo mirror) public local-only (gitignored), never committed
…/ktg/<repo> (private Forgejo) private tracked + committed
no remote none local-only

The authoritative commit decision is git check-ignore STATE.md — so a misclassified remote can never cause a leak; it only produces a leak_warning when the remote looks public but STATE.md is not gitignored. This plugin's own repo has an open/ remote, so its STATE.md is local-only.


Commands & Arguments

/graceful-handoff [flags]
Argument Description
--no-commit Write STATE.md, skip commit/push (you handle git manually)
--dry-run Write nothing, no git — show the --plan JSON for inspection

The helper accepts the same modes directly (node scripts/handoff-pipeline.mjs --plan / --commit / --dry-run) — useful for debugging without going through the skill.


Safety Guarantees

Enforced by tests, not convention:

  • Staging is explicit. --commit stages only STATE.md (plus explicit --also paths). git add -A is never used — a regression test (never stages unrelated dirty files) enforces this.
  • STATE.md never leaks to a public mirror. On a public/open/ remote STATE.md is gitignored and the helper refuses to commit it (local-only-skipped), verified by test.
  • Push is never automatic. The helper never pushes; push stays user-triggered and window-gated.
  • Pre-commit hooks are respected. The helper never uses --no-verify.
  • No network calls. No WebSearch, no Agent delegation, no MCP — fully local.
  • Bash sub-scoped. Skill allowed-tools enumerates Bash(git:*) Bash(node:*) Bash(date:*).

Testing

node --test 'tests/**/*.test.mjs'

30 tests across 3 files:

File Coverage
tests/skill-structure.test.mjs SKILL.md frontmatter (no model: pin, Write in allowed-tools, disable-model-invocation), 👉 NESTE mandate, removal of commands/ and hooks/
tests/scripts/handoff-pipeline.test.mjs --plan/--commit/--dry-run, nearest-STATE resolution, remote classification, no-git add -A regression, gitignored-STATE skip, detached HEAD
tests/plugin-manifest.test.mjs plugin.json version + description, CHANGELOG [3.0.0] BREAKING + preserved history

The suite runs in a few seconds — no LLM involvement.


What Changed in v3.0 (BREAKING)

  • Writes STATE.md, not a NEXT-SESSION artifact. Full integration with the three-layer continuity system; the separate handover file is gone.
  • All three hooks removed. Stop auto-trigger, statusLine hint, and SessionStart loader deleted — redundant (global hook already injects STATE.md), dead (user statusLine wins), or harmful (would overwrite a good STATE.md).
  • Inverted architecture. The model writes STATE.md; the script is a slim deterministic helper.
  • Remote-aware tracked/local-only policy so STATE.md never leaks to a public mirror.
  • Removed the Sonnet pin — handoff synthesis inherits the session model.

Full history in CHANGELOG.md.


Limitations & Open Assumptions

  • disable-model-invocation: true has open issue #26251 — it may regress and block user-invocation in some Claude Code versions. Smoke-test after install.
  • Remote classification is heuristic (open/ / github → public). The authoritative commit decision is git check-ignore STATE.md, so misclassification cannot cause a leak — only an advisory leak_warning.
  • STATE.md read-side depends on the global session-start hook. This plugin only writes; injecting the nearest STATE.md at session start is the user's global ~/.claude/hooks/session-start.sh.

License

MIT. See LICENSE.


Feedback & Contributing

  • Bug reports + feature requests: open an issue on Forgejo
  • 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 — see the root README for related plugins.