feat(skills): ship handoff as a bundled default skill (generation 9)
The handoff skill (baton-pass: write a compact decision-ready handoff, persist it, make the context disposable for the next session) becomes a first-party default skill so operate-mode operations can continue across sessions. Bundled at generation 9: HANDOFF_BODY include_str, bundle entry, canonical list via BUNDLED_SKILLS, and a v8->v9 version-bump test mirroring the v5 pattern. Verified: skills::system tests 21/21.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: handoff
|
||||
description: >-
|
||||
Write a compact, decision-ready handoff so the next session (or the user)
|
||||
can continue without reconstructing the current one. Use when the session
|
||||
is ending, context is running low, the user asks for a handoff / "pass the
|
||||
baton" / "hand off", or a long-running operation needs a durable state
|
||||
checkpoint.
|
||||
---
|
||||
|
||||
# Handoff
|
||||
|
||||
> Write a compact, decision-ready handoff so the next session (or the user)
|
||||
> can continue without reconstructing the current one. Use when the session is
|
||||
> ending, context is running low, the user asks for a handoff / "pass the
|
||||
> baton" / "hand off", or a long-running operation needs a durable state
|
||||
> checkpoint. The goal: the durable artifact survives, the context does not
|
||||
> need to.
|
||||
|
||||
Invocation: `model+user`
|
||||
|
||||
## When to use
|
||||
|
||||
- The user says "handoff", "hand off", "pass the baton", "takeover prompt",
|
||||
"write me a handoff", or the session is about to end / compact.
|
||||
- A long operation (multi-turn, multi-workstream) has state that must survive
|
||||
context loss: commits, branches, PRs, CI, blockers, decisions.
|
||||
- You are switching to a fresh session and want the new session to start from
|
||||
evidence instead of reconstructing the old one.
|
||||
|
||||
## What to do
|
||||
|
||||
1. **Gather the truth from tools, not memory.** Run/collect:
|
||||
- `git branch --show-current`, `git status --short`, `git log --oneline
|
||||
origin/<default>..HEAD` (what is local-only), `git log --oneline -5`
|
||||
(recent context).
|
||||
- Live remote state where relevant (`gh pr list --state open`,
|
||||
`gh pr checks <n>`, `gh run list`) — only what the user's operation
|
||||
actually depends on; do not pad the handoff with a full GitHub dump.
|
||||
- Any in-flight work: dirty files, uncommitted slices, partial worktrees,
|
||||
running background jobs/workers, queued CI.
|
||||
2. **Write a compact markdown handoff** (aim under ~60 lines; the user may
|
||||
also ask for a "short text-only" variant — then aim under ~15 lines):
|
||||
|
||||
```markdown
|
||||
# Handoff — <operation/session name> — <date>
|
||||
- **State:** <one-line: what is done vs in-flight vs blocked>
|
||||
- **Landed/committed:** <exact SHAs + one-line what>
|
||||
- **Branches/PRs:** <names + states; which are ours vs community>
|
||||
- **CI:** <what is green, what is waiting, what is broken>
|
||||
- **Blockers:** <exact blocker + what would unblock>
|
||||
- **Decisions made:** <the WHY that a fresh session must not re-litigate>
|
||||
- **Next step:** <the single next action, one line>
|
||||
- **Continuation records:** <pointers to partial work that must be
|
||||
preserved (worktrees, uncommitted files, receipts)>
|
||||
```
|
||||
|
||||
3. **Persist it.** Write the handoff to the agreed location:
|
||||
- If the workspace has an ops/notes convention (e.g. `codewhale-ops/notes/`
|
||||
with a living handoff file), update the living handoff's dated facts and
|
||||
snapshot, or create `<topic>-handoff-<date>.md` next to it.
|
||||
- Otherwise write to the repo root as `HANDOFF.md` or
|
||||
`docs/handoff/<topic>-<date>.md`; never overwrite someone else's
|
||||
uncommitted handoff without reading it first.
|
||||
4. **Clear the way for the new session ("clears context").** A skill cannot
|
||||
delete the current context, but it can make the context disposable:
|
||||
- Ensure nothing is left only in memory: dirty work is either committed
|
||||
(WIP is fine with a real body), stashed with a note, or recorded in the
|
||||
handoff with its exact location.
|
||||
- Kill or record background work that would outlive the session
|
||||
(background jobs, sub-agents) — record what is still running and its
|
||||
task id.
|
||||
- Close with the one-line "next step" so the fresh session has an
|
||||
unambiguous first action.
|
||||
5. **Deliver.** Give the user the compact handoff text in your reply
|
||||
(the persisted file is the durable copy; the reply is the readable one).
|
||||
|
||||
## Constraints
|
||||
|
||||
- Facts only from tool output; never invent SHAs, check states, or blockers.
|
||||
- Keep claims narrower than evidence: distinguish landed/committed, verified
|
||||
locally, CI-verified, and pending.
|
||||
- Preserve other people's uncommitted work: read before touching, archive
|
||||
before overwriting.
|
||||
- The handoff is orientation, not law: tell the next session to refresh
|
||||
live state before acting on it.
|
||||
- If the user asks for a "short text-only" handoff, give exactly that in the
|
||||
reply and skip the full markdown file unless asked.
|
||||
@@ -10,7 +10,9 @@ use std::path::Path;
|
||||
/// Generation 7 adds the explicit-only `help` router (#4698 parity slice).
|
||||
/// Generation 8 adds the explicit-only `contributor-onboarding` path
|
||||
/// requested by @JayBeest (#4227).
|
||||
const BUNDLED_SKILL_VERSION: &str = "8";
|
||||
/// Generation 9 adds the `handoff` workflow skill (baton-pass for
|
||||
/// continuous operate-mode operations).
|
||||
const BUNDLED_SKILL_VERSION: &str = "9";
|
||||
|
||||
// ── system & extension (meta) ───────────────────────────────────────────────
|
||||
const SKILL_CREATOR_BODY: &str = include_str!("../../assets/skills/skill-creator/SKILL.md");
|
||||
@@ -22,6 +24,7 @@ const FLEET_MANAGER_BODY: &str = include_str!("../../assets/skills/fleet-manager
|
||||
const HELP_BODY: &str = include_str!("../../assets/skills/help/SKILL.md");
|
||||
|
||||
// ── end-user workflows ──────────────────────────────────────────────────────
|
||||
const HANDOFF_BODY: &str = include_str!("../../assets/skills/handoff/SKILL.md");
|
||||
const BEST_OF_N_BODY: &str = include_str!("../../assets/skills/best-of-n/SKILL.md");
|
||||
const INTERVIEW_BODY: &str = include_str!("../../assets/skills/interview/SKILL.md");
|
||||
const PLAN_BODY: &str = include_str!("../../assets/skills/plan/SKILL.md");
|
||||
@@ -104,6 +107,11 @@ const BUNDLED_SKILLS: &[BundledSkill] = &[
|
||||
introduced_in: 7,
|
||||
},
|
||||
// End-user workflows
|
||||
BundledSkill {
|
||||
name: "handoff",
|
||||
body: HANDOFF_BODY,
|
||||
introduced_in: 9,
|
||||
},
|
||||
BundledSkill {
|
||||
name: "best-of-n",
|
||||
body: BEST_OF_N_BODY,
|
||||
@@ -551,7 +559,7 @@ mod tests {
|
||||
.find(|skill| skill.name == "contributor-onboarding")
|
||||
.expect("contributor-onboarding must be bundled");
|
||||
assert_eq!(skill.introduced_in, 8);
|
||||
assert_eq!(BUNDLED_SKILL_VERSION, "8");
|
||||
assert_eq!(BUNDLED_SKILL_VERSION, "9");
|
||||
|
||||
let body = skill.body;
|
||||
assert!(body.contains("invocation: explicit-only"));
|
||||
@@ -815,6 +823,24 @@ mod tests {
|
||||
assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_bump_from_v8_adds_handoff_without_recreating_deleted_skills() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
fs::write(marker_file(&tmp), "8").unwrap();
|
||||
|
||||
install_system_skills(tmp.path()).unwrap();
|
||||
|
||||
assert!(skill_file(&tmp, "handoff").is_file());
|
||||
assert!(
|
||||
!skill_file(&tmp, "delegate").exists(),
|
||||
"an intentionally absent older skill must stay absent"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(marker_file(&tmp)).unwrap().trim(),
|
||||
BUNDLED_SKILL_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_bump_from_v5_adds_best_of_n_without_recreating_deleted_skills() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user