From 4ba3992062c87fd34697045c56f978e1d5fdc096 Mon Sep 17 00:00:00 2001 From: "krunal.patel@chanzuckerberg.com" Date: Mon, 23 Mar 2026 15:42:27 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20add=20/handoff=20skill=20=E2=80=94?= =?UTF-8?q?=20structured=20context=20transfer=20between=20parallel=20agent?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 1 + SKILL.md | 4 +- SKILL.md.tmpl | 4 +- handoff/SKILL.md | 364 ++++++++++++++++++++++++++++++++++++++++++ handoff/SKILL.md.tmpl | 296 ++++++++++++++++++++++++++++++++++ 5 files changed, 665 insertions(+), 4 deletions(-) create mode 100644 handoff/SKILL.md create mode 100644 handoff/SKILL.md.tmpl diff --git a/CLAUDE.md b/CLAUDE.md index 5c0389c1f..fc9076688 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,6 +79,7 @@ gstack/ ├── office-hours/ # /office-hours skill (YC Office Hours — startup diagnostic + builder brainstorm) ├── investigate/ # /investigate skill (systematic root-cause debugging) ├── retro/ # Retrospective skill (includes /retro global cross-project mode) +├── handoff/ # /handoff skill (structured context transfer between parallel agents) ├── bin/ # Standalone scripts (gstack-global-discover for cross-tool session discovery) ├── document-release/ # /document-release skill (post-ship doc updates) ├── cso/ # /cso skill (OWASP Top 10 + STRIDE security audit) diff --git a/SKILL.md b/SKILL.md index af9ef7b06..777beca03 100644 --- a/SKILL.md +++ b/SKILL.md @@ -10,8 +10,8 @@ description: | /plan-ceo-review; architecture /plan-eng-review; design /plan-design-review or /design-consultation; auto-review /autoplan; debugging /investigate; QA /qa; code review /review; visual audit /design-review; shipping /ship; docs /document-release; retro - /retro; second opinion /codex; prod safety /careful or /guard; scoped edits /freeze or - /unfreeze; gstack upgrades /gstack-upgrade. If the user opts out of suggestions, stop + /retro; agent context transfer /handoff; second opinion /codex; prod safety /careful or + /guard; scoped edits /freeze or /unfreeze; gstack upgrades /gstack-upgrade. If the user opts out of suggestions, stop and run gstack-config set proactive false; if they opt back in, run gstack-config set proactive true. allowed-tools: diff --git a/SKILL.md.tmpl b/SKILL.md.tmpl index 436e80040..3399fa998 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -10,8 +10,8 @@ description: | /plan-ceo-review; architecture /plan-eng-review; design /plan-design-review or /design-consultation; auto-review /autoplan; debugging /investigate; QA /qa; code review /review; visual audit /design-review; shipping /ship; docs /document-release; retro - /retro; second opinion /codex; prod safety /careful or /guard; scoped edits /freeze or - /unfreeze; gstack upgrades /gstack-upgrade. If the user opts out of suggestions, stop + /retro; agent context transfer /handoff; second opinion /codex; prod safety /careful or + /guard; scoped edits /freeze or /unfreeze; gstack upgrades /gstack-upgrade. If the user opts out of suggestions, stop and run gstack-config set proactive false; if they opt back in, run gstack-config set proactive true. allowed-tools: diff --git a/handoff/SKILL.md b/handoff/SKILL.md new file mode 100644 index 000000000..edbba94e8 --- /dev/null +++ b/handoff/SKILL.md @@ -0,0 +1,364 @@ +--- +name: handoff +version: 1.0.0 +description: | + Structured context transfer between parallel agents. Analyzes recent commits, + diffs, TODOs, and code comments to surface decisions and their rationale, + embedded assumptions, danger zones, and open threads. Produces a handoff + artifact the next agent loads as context — eliminating cold starts in parallel + sprint workflows. + Use when ending a sprint, handing work to another agent, or resuming a branch + after a break. Proactively suggest after /ship, /retro, or long sessions. +allowed-tools: + - Bash + - Read + - Write + - Grep + - Glob + - AskUserQuestion +--- + + + +## Preamble (run first) + +```bash +_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) +[ -n "$_UPD" ] && echo "$_UPD" || true +mkdir -p ~/.gstack/sessions +touch ~/.gstack/sessions/"$PPID" +_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') +find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +_CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) +_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") +_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +echo "BRANCH: $_BRANCH" +echo "PROACTIVE: $_PROACTIVE" +source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true +REPO_MODE=${REPO_MODE:-unknown} +echo "REPO_MODE: $REPO_MODE" +_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") +echo "LAKE_INTRO: $_LAKE_SEEN" +_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) +_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") +_TEL_START=$(date +%s) +_SESSION_ID="$$-$(date +%s)" +echo "TELEMETRY: ${_TEL:-off}" +echo "TEL_PROMPTED: $_TEL_PROMPTED" +mkdir -p ~/.gstack/analytics +echo '{"skill":"handoff","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +``` + +If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke +them when the user explicitly asks. The user opted out of proactive suggestions. + +If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow". If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. + +If `LAKE_INTRO` is `no`: introduce the Completeness Principle and offer to open the essay. Touch `~/.gstack/.completeness-intro-seen`. + +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: prompt for telemetry opt-in per the standard flow. Touch `~/.gstack/.telemetry-prompted`. + +## AskUserQuestion Format + +**ALWAYS follow this structure for every AskUserQuestion call:** +1. **Re-ground:** State the project, the current branch (use `_BRANCH` from the preamble), and the current plan/task. +2. **Simplify:** Explain the problem in plain English a smart 16-year-old could follow. +3. **Recommend:** `RECOMMENDATION: Choose [X] because [one-line reason]` — include `Completeness: X/10` for each option. +4. **Options:** Lettered options with effort scales: `(human: ~X / CC: ~Y)` + +## Completeness Principle — Boil the Lake + +AI-assisted coding makes the marginal cost of completeness near-zero. Always recommend the complete option. Show both human and CC+gstack effort estimates. Don't defer edge cases or skip the last 10%. + +## Repo Ownership Mode — See Something, Say Something + +`REPO_MODE` tells you who owns issues. Solo: investigate and offer to fix proactively. Collaborative: flag via AskUserQuestion. Unknown: treat as collaborative. + +## Contributor Mode + +If `_CONTRIB` is `true`: reflect on the gstack tooling after each major step. File a field report to `~/.gstack/contributor-logs/{slug}.md` if something wasn't a 10. Max 3 per session. + +## Completion Status Protocol + +Report one of: **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, **NEEDS_CONTEXT**. Escalate after 3 failed attempts. Bad work is worse than no work. + +--- + +# /handoff — Agent Context Transfer + +You are producing a structured handoff artifact for the next agent (or future you) +who will work on this branch. Your job is to surface what you know that the code +doesn't say: decisions made, alternatives rejected, assumptions embedded, code that +is fragile, and work that is unfinished. + +**The test:** After reading the handoff artifact, the next agent should be able to +start working in under 60 seconds — no re-reading commits, no grepping for context, +no asking "why was this done this way?" + +--- + +## Step 0: Detect scope + +```bash +BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") +echo "BRANCH: $BRANCH" +echo "REPO: $REPO" +echo "BASE: $BASE" +echo "COMMITS_AHEAD: $COMMIT_COUNT" +git log "$BASE"..HEAD --oneline 2>/dev/null || true +git diff --stat "$BASE"..HEAD 2>/dev/null || true +``` + +If `COMMITS_AHEAD` is 0 and there are no uncommitted changes, print: + +``` +nothing to hand off — no commits or changes ahead of $BASE +│ make some progress first, then run /handoff +``` + +Stop. + +--- + +## Step 1: Ask scope + +Use AskUserQuestion: + +> **Branch:** `{BRANCH}` in `{REPO}` +> +> I'll analyze your recent work and produce a handoff document the next agent can +> load as context. How thorough should this be? +> +> RECOMMENDATION: Choose B if you're handing off to another agent session today. +> Choose A for a quick record before a short break. +> +> A) Quick — decisions + open threads only (Completeness: 5/10) +> (human: ~5 min / CC: ~30 sec) +> +> B) Deep — decisions, assumptions, danger zones, open threads, and a suggested +> starting point for the next agent (Completeness: 10/10) +> (human: ~20 min / CC: ~2 min) + +Store answer as `DEPTH` (quick or deep). + +--- + +## Step 2: Mine commit history + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git log "$BASE"..HEAD --format="%H%n%B%n---COMMIT_SEP---" 2>/dev/null +``` + +Extract from commit messages: +- Explicit decisions ("chose X over Y", "switched from", "replaced", "removed", "reverted") +- Reasons given in commit bodies +- References to issues, PRs, or external constraints + +--- + +## Step 3: Mine the diff for implicit decisions + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD 2>/dev/null +``` + +From the diff, identify: + +**Decisions** — places where a non-obvious choice was made: +- A function that could have been written simpler but wasn't +- A data structure choice that isn't the obvious default +- An early return or guard clause protecting against a specific scenario +- A hardcoded value that looks deliberate, not lazy + +**Assumptions** (deep only) — things the code assumes to be true but doesn't verify: +- Array access without bounds checking (`arr[0]`, `data[key]`) +- Type coercions (`as SomeType`, unchecked casts) +- Environment assumptions (`process.env.X` without fallback) +- Ordering assumptions ("this runs after X" with no enforcement) +- Comments containing "assume", "should be", "always", "never" + +**Danger zones** (deep only) — code that is fragile or likely to break on contact: +- Timing-sensitive logic (setTimeout, retry loops, polling) +- Partially-implemented error handling (`catch(e) { /* TODO */ }`) +- Code patched on top of a previous patch +- Anything with a "don't touch" or "be careful" comment +- Functions that mutate shared state + +--- + +## Step 4: Surface open threads + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +# TODOs introduced in this branch (not pre-existing) +git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|TEMP|WIP" | grep -v "^+++" || true +# Commented-out code (likely removed but not decided) +git diff "$BASE"..HEAD | grep "^+" | grep -E "^\+\s*//" | head -20 || true +# Skipped tests +git diff "$BASE"..HEAD | grep "^+" | grep -iE "skip|xit|xdescribe|pending|\.todo" || true +# Stashed work +git stash list 2>/dev/null || true +``` + +--- + +## Step 5 (deep only): Suggested entry point for next agent + +Skip if `DEPTH` is `quick`. + +Based on the open threads and danger zones, produce one concrete suggestion: +- What should the next agent do FIRST +- What should the next agent NOT touch until a specific condition is met +- What question should the next agent answer before making changes + +--- + +## Step 6: Write the artifact + +```bash +BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") +TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S) +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") +mkdir -p ~/.gstack/handoffs +ARTIFACT="$HOME/.gstack/handoffs/${REPO}-${BRANCH}-${TIMESTAMP}.md" +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +echo "ARTIFACT: $ARTIFACT" +echo "REPO_ROOT: $REPO_ROOT" +``` + +Write the artifact using the Write tool with this format: + +```markdown +# Handoff: {BRANCH} +**Repo:** {REPO} | **Branch:** {BRANCH} | **Created:** {TIMESTAMP} +**Commits ahead of {BASE}:** {N} | **Depth:** {quick|deep} + +--- + +## Decisions + +> What was chosen and why — including alternatives that were rejected. + +- **{decision}** + - Chose: {what} + - Rejected: {alternatives and why} + - Reason: {rationale from commit message or code context} + +*(If no decisions found: "No decisions surfaced — commit messages were terse. +Review the diff manually if something seems non-obvious.")* + +--- + +## Assumptions + +> Things the code assumes to be true that aren't enforced or verified. +> *(Quick mode: section omitted)* + +- `{file}:{line}` — {what is assumed} *(risk: low|medium|high)* + +*(If none found: "No embedded assumptions detected.")* + +--- + +## Danger Zones + +> Code that is fragile, partially done, or likely to break on contact. +> *(Quick mode: section omitted)* + +- `{file}:{line_range}` — {why it's fragile} + - Do: {safe approach} + - Don't: {what to avoid} + +*(If none found: "No danger zones detected.")* + +--- + +## Open Threads + +> Work that was started but not finished, and why it stopped. + +- [ ] {description} — stopped because: {reason} + - Blocked by: {dependency or decision needed} + +*(If none: "No open threads.")* + +--- + +## For the Next Agent + +> Load this file at the start of your session. Start here. + +{suggested entry point — one concrete action} + +**Do not touch** {X} **until** {condition}. + +**Answer this question first:** {the question that unblocks the most work} + +*(Quick mode: entry point omitted — run /handoff deep for a suggested starting point)* + +--- + +*Generated by /handoff · gstack v{VERSION} · {TIMESTAMP}* +``` + +After writing `$ARTIFACT`, copy to the repo root: + +```bash +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +cp "$ARTIFACT" "$REPO_ROOT/HANDOFF.md" +``` + +Check and update `.gitignore` if needed: + +```bash +grep -q "^HANDOFF\.md$" "$REPO_ROOT/.gitignore" 2>/dev/null || echo "HANDOFF_NOT_IGNORED" +``` + +If `HANDOFF_NOT_IGNORED`: + +```bash +echo "HANDOFF.md" >> "$REPO_ROOT/.gitignore" +``` + +Print: + +``` + ✓ handoff artifact written + ~/.gstack/handoffs/{REPO}-{BRANCH}-{TIMESTAMP}.md + {REPO_ROOT}/HANDOFF.md (gitignored) + + next agent: load HANDOFF.md at the start of your session +``` + +--- + +## Completion + +Report status using **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, or **NEEDS_CONTEXT**. + +State how many decisions, assumptions, danger zones, and open threads were surfaced. + +If `DONE_WITH_CONCERNS`: flag if commit messages were too terse to surface decisions — suggest more descriptive commit messages going forward. + +If `PROACTIVE` is `true`, suggest: "Run `/retro` for velocity metrics, or `/review` before handing off to a reviewer." + +## Telemetry (run last) + +```bash +_TEL_END=$(date +%s) +_TEL_DUR=$(( _TEL_END - _TEL_START )) +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true +~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "handoff" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "false" --session-id "$_SESSION_ID" 2>/dev/null & +``` + +Replace `OUTCOME` with success/error/abort based on workflow result. diff --git a/handoff/SKILL.md.tmpl b/handoff/SKILL.md.tmpl new file mode 100644 index 000000000..99c14863e --- /dev/null +++ b/handoff/SKILL.md.tmpl @@ -0,0 +1,296 @@ +--- +name: handoff +version: 1.0.0 +description: | + Structured context transfer between parallel agents. Analyzes recent commits, + diffs, TODOs, and code comments to surface decisions and their rationale, + embedded assumptions, danger zones, and open threads. Produces a handoff + artifact the next agent loads as context — eliminating cold starts in parallel + sprint workflows. + Use when ending a sprint, handing work to another agent, or resuming a branch + after a break. Proactively suggest after /ship, /retro, or long sessions. +allowed-tools: + - Bash + - Read + - Write + - Grep + - Glob + - AskUserQuestion +--- + + + +{{PREAMBLE}} + +# /handoff — Agent Context Transfer + +You are producing a structured handoff artifact for the next agent (or future you) +who will work on this branch. Your job is to surface what you know that the code +doesn't say: decisions made, alternatives rejected, assumptions embedded, code that +is fragile, and work that is unfinished. + +**The test:** After reading the handoff artifact, the next agent should be able to +start working in under 60 seconds — no re-reading commits, no grepping for context, +no asking "why was this done this way?" + +--- + +## Step 0: Detect scope + +```bash +BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") +echo "BRANCH: $BRANCH" +echo "REPO: $REPO" +echo "BASE: $BASE" +echo "COMMITS_AHEAD: $COMMIT_COUNT" +git log "$BASE"..HEAD --oneline 2>/dev/null || true +git diff --stat "$BASE"..HEAD 2>/dev/null || true +``` + +If `COMMITS_AHEAD` is 0 and there are no uncommitted changes, print: + +``` +nothing to hand off — no commits or changes ahead of $BASE +│ make some progress first, then run /handoff +``` + +Stop. + +--- + +## Step 1: Ask scope + +Use AskUserQuestion: + +> **Branch:** `{BRANCH}` in `{REPO}` +> +> I'll analyze your recent work and produce a handoff document the next agent can +> load as context. How thorough should this be? +> +> RECOMMENDATION: Choose B if you're handing off to another agent session today. +> Choose A for a quick record before a short break. +> +> A) Quick — decisions + open threads only (Completeness: 5/10) +> (human: ~5 min / CC: ~30 sec) +> +> B) Deep — decisions, assumptions, danger zones, open threads, and a suggested +> starting point for the next agent (Completeness: 10/10) +> (human: ~20 min / CC: ~2 min) + +Store answer as `DEPTH` (quick or deep). + +--- + +## Step 2: Mine commit history + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git log "$BASE"..HEAD --format="%H %s" 2>/dev/null +``` + +For each commit, read the full message: + +```bash +git log "$BASE"..HEAD --format="%H%n%B%n---COMMIT_SEP---" 2>/dev/null +``` + +Extract from commit messages: +- Explicit decisions ("chose X over Y", "switched from", "replaced", "removed", "reverted") +- Reasons given in commit bodies +- References to issues, PRs, or external constraints + +--- + +## Step 3: Mine the diff for implicit decisions + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD 2>/dev/null +``` + +From the diff, identify: + +**Decisions** — places where a non-obvious choice was made. Look for: +- A function that could have been written simpler but wasn't (why?) +- A data structure choice that isn't the obvious default +- An early return or guard clause that protects against a specific scenario +- A hardcoded value that looks like it was deliberate, not lazy + +**Assumptions** — things the code assumes to be true that aren't verified. Look for: +- Array access without bounds checking (`arr[0]`, `data[key]`) +- Type coercions (`as SomeType`, unchecked casts) +- Environment assumptions (`process.env.X` without fallback) +- Ordering assumptions ("this runs after X" with no enforcement) +- Comments that say "assume", "should be", "always", "never" + +**Danger zones** — code that is fragile, non-obvious, or likely to break. Look for: +- Timing-sensitive logic (setTimeout, retry loops, polling) +- Partially-implemented error handling (`catch(e) { /* TODO */ }`) +- Code that was patched on top of a previous patch +- Anything with a "don't touch" or "be careful" comment +- Functions that mutate shared state + +--- + +## Step 4: Surface open threads + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +# TODOs introduced in this branch (not pre-existing) +git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|TEMP|WIP" | grep -v "^+++" || true +# Commented-out code blocks (likely removed but not decided) +git diff "$BASE"..HEAD | grep "^+" | grep -E "^\+\s*//" | head -20 || true +# Incomplete test coverage markers +git diff "$BASE"..HEAD | grep "^+" | grep -iE "skip|xit|xdescribe|pending|\.todo" || true +``` + +Also check git stash for saved work: + +```bash +git stash list 2>/dev/null || true +``` + +--- + +## Step 5 (deep only): Suggested entry point for next agent + +Skip this step if `DEPTH` is `quick`. + +Based on the open threads and danger zones identified, produce one concrete suggestion: + +- What should the next agent do FIRST +- What should the next agent NOT touch until a specific condition is met +- What question should the next agent answer before making changes + +This is the most valuable sentence in the handoff: "Start here, and don't touch X until Y." + +--- + +## Step 6: Write the artifact + +```bash +BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") +TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S) +mkdir -p ~/.gstack/handoffs +ARTIFACT="$HOME/.gstack/handoffs/${REPO}-${BRANCH}-${TIMESTAMP}.md" +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +``` + +Write the artifact to `$ARTIFACT` using the Write tool. Use this format: + +```markdown +# Handoff: {BRANCH} +**Repo:** {REPO} | **Branch:** {BRANCH} | **Created:** {TIMESTAMP} +**Commits ahead of {BASE}:** {N} | **Depth:** {quick|deep} + +--- + +## Decisions + +> What was chosen and why — including alternatives that were rejected. + +- **{decision}** + - Chose: {what} + - Rejected: {alternatives and why} + - Reason: {rationale from commit message or code context} + +*(If no explicit decisions were found: "No decisions surfaced — commit messages were +terse. Review the diff manually if something seems non-obvious.")* + +--- + +## Assumptions + +> Things the code assumes to be true that aren't enforced or verified. + +- `{file}:{line}` — {what is assumed} *(risk: low|medium|high)* + +*(If none found: "No embedded assumptions detected.")* + +--- + +## Danger Zones + +> Code that is fragile, partially done, or likely to break on contact. + +- `{file}:{line_range}` — {why it's fragile} + - Do: {safe approach} + - Don't: {what to avoid} + +*(If none found: "No danger zones detected.")* + +--- + +## Open Threads + +> Work that was started but not finished, and why it stopped. + +- [ ] {description} — stopped because: {reason} + - Blocked by: {dependency or decision needed} + +*(If none: "No open threads.")* + +--- + +## For the Next Agent + +> Load this file at the start of your session. Start here. + +{suggested entry point — one concrete action} + +**Do not touch** {X} **until** {condition}. + +**Answer this question first:** {the question that unblocks the most work} + +--- + +*Generated by /handoff · gstack {VERSION} · {TIMESTAMP}* +``` + +After writing the artifact, also copy it to `{REPO_ROOT}/HANDOFF.md` so the next +agent in the same repo can find it without knowing the path: + +```bash +cp "$ARTIFACT" "$REPO_ROOT/HANDOFF.md" +``` + +Then check if `HANDOFF.md` is already in `.gitignore`: + +```bash +grep -q "^HANDOFF\.md$" "$REPO_ROOT/.gitignore" 2>/dev/null || echo "HANDOFF_NOT_IGNORED" +``` + +If `HANDOFF_NOT_IGNORED`: append to `.gitignore`: + +```bash +echo "HANDOFF.md" >> "$REPO_ROOT/.gitignore" +echo "Added HANDOFF.md to .gitignore — handoff artifacts are personal, not committed" +``` + +Print: + +``` + ✓ handoff artifact written + ~/.gstack/handoffs/{REPO}-{BRANCH}-{TIMESTAMP}.md + {REPO_ROOT}/HANDOFF.md (gitignored) + + next agent: load HANDOFF.md at the start of your session +``` + +--- + +## Completion + +Report status: + +- **DONE** — Artifact written. State how many decisions, assumptions, danger zones, and open threads were surfaced. +- **DONE_WITH_CONCERNS** — Artifact written, but some sections were empty due to terse commit history. Recommend running `/handoff` with more verbose commits. +- **BLOCKED** — State what prevented artifact creation. + +If `PROACTIVE` is `true`, suggest: "Run `/retro` for velocity metrics, or `/review` before handing off to a reviewer." + +{{TELEMETRY}} From dde7b61de905df6f1560bf3f0de5f1aac2c4ccf4 Mon Sep 17 00:00:00 2001 From: "krunal.patel@chanzuckerberg.com" Date: Mon, 23 Mar 2026 20:52:27 -0700 Subject: [PATCH 2/2] refactor: v1.1.0 -- targeted questions, bounded diff, precise assumptions, CLAUDE.md injection --- handoff/SKILL.md | 314 ++++++++++++++++++++++++++---------------- handoff/SKILL.md.tmpl | 292 +++++++++++++++++++++++++-------------- 2 files changed, 378 insertions(+), 228 deletions(-) diff --git a/handoff/SKILL.md b/handoff/SKILL.md index edbba94e8..05ca77176 100644 --- a/handoff/SKILL.md +++ b/handoff/SKILL.md @@ -1,12 +1,11 @@ --- name: handoff -version: 1.0.0 +version: 1.1.0 description: | - Structured context transfer between parallel agents. Analyzes recent commits, - diffs, TODOs, and code comments to surface decisions and their rationale, - embedded assumptions, danger zones, and open threads. Produces a handoff - artifact the next agent loads as context — eliminating cold starts in parallel - sprint workflows. + Structured context transfer between parallel agents. Captures decisions and + their rationale via targeted questions, surfaces real assumptions and danger + zones, and records open threads. Produces a handoff artifact the next agent + loads as context, auto-injected into CLAUDE.md so it is never missed. Use when ending a sprint, handing work to another agent, or resuming a branch after a break. Proactively suggest after /ship, /retro, or long sessions. allowed-tools: @@ -50,51 +49,50 @@ echo '{"skill":"handoff","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(base for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` -If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke -them when the user explicitly asks. The user opted out of proactive suggestions. +If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills. -If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow". If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. +If output shows `UPGRADE_AVAILABLE `: follow the inline upgrade flow. If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. If `LAKE_INTRO` is `no`: introduce the Completeness Principle and offer to open the essay. Touch `~/.gstack/.completeness-intro-seen`. -If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: prompt for telemetry opt-in per the standard flow. Touch `~/.gstack/.telemetry-prompted`. +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: prompt for telemetry opt-in. Touch `~/.gstack/.telemetry-prompted`. ## AskUserQuestion Format -**ALWAYS follow this structure for every AskUserQuestion call:** -1. **Re-ground:** State the project, the current branch (use `_BRANCH` from the preamble), and the current plan/task. -2. **Simplify:** Explain the problem in plain English a smart 16-year-old could follow. -3. **Recommend:** `RECOMMENDATION: Choose [X] because [one-line reason]` — include `Completeness: X/10` for each option. -4. **Options:** Lettered options with effort scales: `(human: ~X / CC: ~Y)` +**ALWAYS follow this structure:** +1. **Re-ground:** State the project, current branch (use `_BRANCH` from preamble), and current task. +2. **Simplify:** Plain English a smart 16-year-old could follow. +3. **Recommend:** `RECOMMENDATION: Choose [X] because [one-line reason]` with `Completeness: X/10` per option. +4. **Options:** Lettered with effort scales: `(human: ~X / CC: ~Y)` -## Completeness Principle — Boil the Lake +## Completeness Principle -- Boil the Lake -AI-assisted coding makes the marginal cost of completeness near-zero. Always recommend the complete option. Show both human and CC+gstack effort estimates. Don't defer edge cases or skip the last 10%. +Always recommend the complete option. Show both human and CC+gstack effort estimates. Do not defer edge cases. -## Repo Ownership Mode — See Something, Say Something +## Repo Ownership Mode -`REPO_MODE` tells you who owns issues. Solo: investigate and offer to fix proactively. Collaborative: flag via AskUserQuestion. Unknown: treat as collaborative. +Solo: investigate and offer to fix proactively. Collaborative: flag via AskUserQuestion. Unknown: treat as collaborative. ## Contributor Mode -If `_CONTRIB` is `true`: reflect on the gstack tooling after each major step. File a field report to `~/.gstack/contributor-logs/{slug}.md` if something wasn't a 10. Max 3 per session. +If `_CONTRIB` is `true`: file a field report to `~/.gstack/contributor-logs/{slug}.md` if something was not a 10. Max 3 per session. ## Completion Status Protocol -Report one of: **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, **NEEDS_CONTEXT**. Escalate after 3 failed attempts. Bad work is worse than no work. +Report one of: **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, **NEEDS_CONTEXT**. Escalate after 3 failed attempts. --- -# /handoff — Agent Context Transfer +# /handoff -- Agent Context Transfer You are producing a structured handoff artifact for the next agent (or future you) -who will work on this branch. Your job is to surface what you know that the code -doesn't say: decisions made, alternatives rejected, assumptions embedded, code that -is fragile, and work that is unfinished. +who will work on this branch. Your job is to capture what the code does not say: +decisions made and why, things assumed to be true, code that is fragile, and work +that is unfinished. **The test:** After reading the handoff artifact, the next agent should be able to -start working in under 60 seconds — no re-reading commits, no grepping for context, -no asking "why was this done this way?" +start working in under 60 seconds without re-reading commits, grepping for context, +or asking "why was this done this way?" --- @@ -105,19 +103,20 @@ BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") +DIFF_LINES=$(git diff "$BASE"..HEAD --stat 2>/dev/null | tail -1) echo "BRANCH: $BRANCH" echo "REPO: $REPO" echo "BASE: $BASE" echo "COMMITS_AHEAD: $COMMIT_COUNT" +echo "DIFF_SUMMARY: $DIFF_LINES" git log "$BASE"..HEAD --oneline 2>/dev/null || true -git diff --stat "$BASE"..HEAD 2>/dev/null || true ``` -If `COMMITS_AHEAD` is 0 and there are no uncommitted changes, print: +If `COMMITS_AHEAD` is 0 and there are no uncommitted changes: ``` -nothing to hand off — no commits or changes ahead of $BASE -│ make some progress first, then run /handoff +nothing to hand off -- no commits or changes ahead of $BASE +make some progress first, then run /handoff ``` Stop. @@ -128,18 +127,17 @@ Stop. Use AskUserQuestion: -> **Branch:** `{BRANCH}` in `{REPO}` +> **Branch:** `{BRANCH}` in `{REPO}` -- {N} commits ahead of {BASE} > -> I'll analyze your recent work and produce a handoff document the next agent can -> load as context. How thorough should this be? +> I will produce a handoff document for the next agent. How thorough? > -> RECOMMENDATION: Choose B if you're handing off to another agent session today. +> RECOMMENDATION: Choose B if you are handing off to another agent session today. > Choose A for a quick record before a short break. > -> A) Quick — decisions + open threads only (Completeness: 5/10) +> A) Quick -- decisions + open threads only (Completeness: 5/10) > (human: ~5 min / CC: ~30 sec) > -> B) Deep — decisions, assumptions, danger zones, open threads, and a suggested +> B) Deep -- decisions, assumptions, danger zones, open threads, and a suggested > starting point for the next agent (Completeness: 10/10) > (human: ~20 min / CC: ~2 min) @@ -147,48 +145,63 @@ Store answer as `DEPTH` (quick or deep). --- -## Step 2: Mine commit history +## Step 2: Mine explicit decisions from commit history ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") git log "$BASE"..HEAD --format="%H%n%B%n---COMMIT_SEP---" 2>/dev/null ``` -Extract from commit messages: -- Explicit decisions ("chose X over Y", "switched from", "replaced", "removed", "reverted") -- Reasons given in commit bodies -- References to issues, PRs, or external constraints +Extract only what is explicit in commit messages: stated reasons, rejected +alternatives, references to issues or external constraints. Do not infer decisions +from code structure here -- that happens in Step 3. --- -## Step 3: Mine the diff for implicit decisions +## Step 3: Read the capped diff and ask targeted questions + +Find the most-changed files, skipping generated and lock files: + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD \ + -- ':!*.lock' ':!package-lock.json' ':!yarn.lock' ':!*.min.js' ':!*.min.css' \ + ':!*-generated.*' ':!*.generated.*' ':!dist/*' ':!build/*' \ + --stat 2>/dev/null \ + | grep "|" \ + | sort -t'|' -k2 -rn \ + | head -20 \ + | awk '{print $1}' +``` + +Read the diffs for those files: ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") -git diff "$BASE"..HEAD 2>/dev/null +git diff "$BASE"..HEAD -- {top_files} 2>/dev/null ``` -From the diff, identify: +If total diff exceeds 500 lines: read only the top 5 files. Summarize remaining +files by name and line count only -- do not read them. + +From the diff, identify the 3 most non-obvious changes: a function that could have +been simpler but was not, a data structure that is not the obvious default, a guard +clause protecting against something specific, a value that looks deliberate. + +Use AskUserQuestion to ask about each one directly, grounded in the specific change. +For example: -**Decisions** — places where a non-obvious choice was made: -- A function that could have been written simpler but wasn't -- A data structure choice that isn't the obvious default -- An early return or guard clause protecting against a specific scenario -- A hardcoded value that looks deliberate, not lazy +> You switched the retry logic in `auth.ts` from exponential to linear backoff. +> What drove that? -**Assumptions** (deep only) — things the code assumes to be true but doesn't verify: -- Array access without bounds checking (`arr[0]`, `data[key]`) -- Type coercions (`as SomeType`, unchecked casts) -- Environment assumptions (`process.env.X` without fallback) -- Ordering assumptions ("this runs after X" with no enforcement) -- Comments containing "assume", "should be", "always", "never" +> You removed the Redis cache layer from `session.ts`. Intentional or temporary? -**Danger zones** (deep only) — code that is fragile or likely to break on contact: -- Timing-sensitive logic (setTimeout, retry loops, polling) -- Partially-implemented error handling (`catch(e) { /* TODO */ }`) -- Code patched on top of a previous patch -- Anything with a "don't touch" or "be careful" comment -- Functions that mutate shared state +> `payments/webhook.ts` has a hardcoded 3-second delay at line 47. What is that +> protecting against? + +Ask only about what is genuinely non-obvious. Skip anything explained by the commit +message or an existing comment. Maximum 3 questions. If the user skips a question, +record it as "rationale unknown" in the artifact. --- @@ -196,36 +209,86 @@ From the diff, identify: ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") -# TODOs introduced in this branch (not pre-existing) -git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|TEMP|WIP" | grep -v "^+++" || true -# Commented-out code (likely removed but not decided) -git diff "$BASE"..HEAD | grep "^+" | grep -E "^\+\s*//" | head -20 || true -# Skipped tests -git diff "$BASE"..HEAD | grep "^+" | grep -iE "skip|xit|xdescribe|pending|\.todo" || true +# TODOs and FIXMEs introduced in this branch only +git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|WIP" | grep -v "^+++" || true +# Skipped or pending tests +git diff "$BASE"..HEAD | grep "^+" | grep -iE "\.skip|\.only|xit\b|xdescribe\b|pending\(" | grep -v "^+++" || true # Stashed work git stash list 2>/dev/null || true ``` --- -## Step 5 (deep only): Suggested entry point for next agent +## Step 5 (deep only): Surface real assumptions + +Skip if `DEPTH` is quick. + +Flag only these three patterns in new code introduced in this branch. Do not flag +every array access or type cast -- that is noise. + +**Pattern 1 -- unguarded environment variables** (no fallback on same line): + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD | grep "^+" | grep -E "process\.env\.[A-Z_]+" | grep -v "||" | grep -v "??" | grep -v "^+++" || true +``` + +**Pattern 2 -- explicit assumption comments:** -Skip if `DEPTH` is `quick`. +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD | grep "^+" | grep -iE "//.*\b(assume|assumes|should be|always|never|expected to)\b" | grep -v "^+++" || true +``` -Based on the open threads and danger zones, produce one concrete suggestion: -- What should the next agent do FIRST -- What should the next agent NOT touch until a specific condition is met -- What question should the next agent answer before making changes +**Pattern 3 -- external calls with no error handling:** + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD | grep "^+" | grep -iE "\.(fetch|query|findOne|execute|request)\(" | grep -v "catch\|try\|await\|\.then" | grep -v "^+++" | head -10 || true +``` + +For each finding: record file, line, what is assumed, and risk (low/medium/high) +based on whether failure is silent vs. loud and whether it is in a critical path. --- -## Step 6: Write the artifact +## Step 6 (deep only): Identify danger zones + +Skip if `DEPTH` is quick. + +From the diff read in Step 3, look for: +- Timing-sensitive code: setTimeout, setInterval, retry loops, polling, sleep +- Partial error handling: catch blocks that are empty, log-only, or contain TODO +- Layered patches: new code added on top of a recent commit that was itself a fix +- Comments containing "don't touch", "be careful", "fragile", "hacky", "workaround" + +For each: record file, line range, why it is fragile, what is safe to do, and +what to avoid. + +--- + +## Step 7 (deep only): Suggested entry point + +Skip if `DEPTH` is quick. + +Based on open threads and danger zones, produce one concrete recommendation: +- What the next agent should do first (specific file or task, not "review X") +- What the next agent should not touch until a specific condition is met +- The one question that, if answered, unblocks the most remaining work + +Generic advice is not acceptable. "Do not touch `payments/webhook.ts` until the +load test in issue #89 completes -- start with the session timeout fix in +`auth/middleware.ts` instead" is the bar. + +--- + +## Step 8: Write the artifact ```bash BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") -TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S) BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S) COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") mkdir -p ~/.gstack/handoffs ARTIFACT="$HOME/.gstack/handoffs/${REPO}-${BRANCH}-${TIMESTAMP}.md" @@ -234,7 +297,7 @@ echo "ARTIFACT: $ARTIFACT" echo "REPO_ROOT: $REPO_ROOT" ``` -Write the artifact using the Write tool with this format: +Write the artifact using the Write tool: ```markdown # Handoff: {BRANCH} @@ -245,37 +308,34 @@ Write the artifact using the Write tool with this format: ## Decisions -> What was chosen and why — including alternatives that were rejected. - -- **{decision}** +- **{description of change}** - Chose: {what} - - Rejected: {alternatives and why} - - Reason: {rationale from commit message or code context} + - Rejected: {alternatives, if known} + - Reason: {rationale from commit message or answer to targeted question} -*(If no decisions found: "No decisions surfaced — commit messages were terse. -Review the diff manually if something seems non-obvious.")* +*(If rationale was skipped: "Rationale unknown -- {change description}. Inspect +before modifying.")* + +*(If no decisions were surfaced: "Commit messages were terse and no non-obvious +changes were identified. Review the diff manually.")* --- ## Assumptions +*(Quick mode: section omitted)* -> Things the code assumes to be true that aren't enforced or verified. -> *(Quick mode: section omitted)* - -- `{file}:{line}` — {what is assumed} *(risk: low|medium|high)* +- `{file}:{line}` -- {what is assumed} *(risk: low|medium|high)* -*(If none found: "No embedded assumptions detected.")* +*(If none found: "No assumptions detected.")* --- ## Danger Zones +*(Quick mode: section omitted)* -> Code that is fragile, partially done, or likely to break on contact. -> *(Quick mode: section omitted)* - -- `{file}:{line_range}` — {why it's fragile} - - Do: {safe approach} - - Don't: {what to avoid} +- `{file}:{line_range}` -- {why it is fragile} + - Safe: {what to do} + - Avoid: {what not to do} *(If none found: "No danger zones detected.")* @@ -283,72 +343,84 @@ Review the diff manually if something seems non-obvious.")* ## Open Threads -> Work that was started but not finished, and why it stopped. - -- [ ] {description} — stopped because: {reason} - - Blocked by: {dependency or decision needed} +- [ ] {description} -- stopped because: {reason or "unknown"} *(If none: "No open threads.")* --- ## For the Next Agent +*(Quick mode: section omitted)* -> Load this file at the start of your session. Start here. - -{suggested entry point — one concrete action} +Start with: {specific file or task} -**Do not touch** {X} **until** {condition}. +Do not touch {X} until {condition}. -**Answer this question first:** {the question that unblocks the most work} - -*(Quick mode: entry point omitted — run /handoff deep for a suggested starting point)* +Answer this first: {the question that unblocks the most work} --- -*Generated by /handoff · gstack v{VERSION} · {TIMESTAMP}* +*Generated by /handoff v{VERSION} -- {TIMESTAMP}* ``` -After writing `$ARTIFACT`, copy to the repo root: +Copy to repo root: ```bash -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) cp "$ARTIFACT" "$REPO_ROOT/HANDOFF.md" ``` -Check and update `.gitignore` if needed: +--- + +## Step 9: Inject into CLAUDE.md + +Check for an existing active handoff entry: ```bash -grep -q "^HANDOFF\.md$" "$REPO_ROOT/.gitignore" 2>/dev/null || echo "HANDOFF_NOT_IGNORED" +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +grep -n "Active handoff" "$REPO_ROOT/CLAUDE.md" 2>/dev/null | head -1 || echo "NO_ENTRY" +``` + +If `NO_ENTRY`: append to `{REPO_ROOT}/CLAUDE.md`: + +```markdown +## Active handoff + +Load `HANDOFF.md` before starting work -- it contains decisions, danger zones, +and open threads from the last session on branch `{BRANCH}`. ``` -If `HANDOFF_NOT_IGNORED`: +If the entry exists: replace the branch name in that section with the current +branch. This keeps the entry pointing to the current handoff. + +Then ensure `HANDOFF.md` is gitignored: ```bash -echo "HANDOFF.md" >> "$REPO_ROOT/.gitignore" +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +grep -q "^HANDOFF\.md$" "$REPO_ROOT/.gitignore" 2>/dev/null || echo "HANDOFF.md" >> "$REPO_ROOT/.gitignore" ``` Print: ``` - ✓ handoff artifact written + handoff written ~/.gstack/handoffs/{REPO}-{BRANCH}-{TIMESTAMP}.md - {REPO_ROOT}/HANDOFF.md (gitignored) - - next agent: load HANDOFF.md at the start of your session + {REPO_ROOT}/HANDOFF.md (gitignored) + CLAUDE.md updated -- next agent will load this automatically ``` --- ## Completion -Report status using **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, or **NEEDS_CONTEXT**. +Report status: **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, or **NEEDS_CONTEXT**. State how many decisions, assumptions, danger zones, and open threads were surfaced. -If `DONE_WITH_CONCERNS`: flag if commit messages were too terse to surface decisions — suggest more descriptive commit messages going forward. +If `DONE_WITH_CONCERNS`: note if commit messages were terse (decisions sparse), or +if the diff exceeded the cap (list which files were skipped). -If `PROACTIVE` is `true`, suggest: "Run `/retro` for velocity metrics, or `/review` before handing off to a reviewer." +If `PROACTIVE` is `true`: suggest `/retro` for velocity metrics or `/review` before +handing off to a reviewer. ## Telemetry (run last) @@ -361,4 +433,4 @@ rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true --used-browse "false" --session-id "$_SESSION_ID" 2>/dev/null & ``` -Replace `OUTCOME` with success/error/abort based on workflow result. +Replace `OUTCOME` with success/error/abort. diff --git a/handoff/SKILL.md.tmpl b/handoff/SKILL.md.tmpl index 99c14863e..f47299ecc 100644 --- a/handoff/SKILL.md.tmpl +++ b/handoff/SKILL.md.tmpl @@ -1,12 +1,11 @@ --- name: handoff -version: 1.0.0 +version: 1.1.0 description: | - Structured context transfer between parallel agents. Analyzes recent commits, - diffs, TODOs, and code comments to surface decisions and their rationale, - embedded assumptions, danger zones, and open threads. Produces a handoff - artifact the next agent loads as context — eliminating cold starts in parallel - sprint workflows. + Structured context transfer between parallel agents. Captures decisions and + their rationale via targeted questions, surfaces real assumptions and danger + zones, and records open threads. Produces a handoff artifact the next agent + loads as context, auto-injected into CLAUDE.md so it is never missed. Use when ending a sprint, handing work to another agent, or resuming a branch after a break. Proactively suggest after /ship, /retro, or long sessions. allowed-tools: @@ -25,13 +24,13 @@ allowed-tools: # /handoff — Agent Context Transfer You are producing a structured handoff artifact for the next agent (or future you) -who will work on this branch. Your job is to surface what you know that the code -doesn't say: decisions made, alternatives rejected, assumptions embedded, code that -is fragile, and work that is unfinished. +who will work on this branch. Your job is to capture what the code does not say: +decisions made and why, things assumed to be true, code that is fragile, and work +that is unfinished. **The test:** After reading the handoff artifact, the next agent should be able to -start working in under 60 seconds — no re-reading commits, no grepping for context, -no asking "why was this done this way?" +start working in under 60 seconds without re-reading commits, grepping for context, +or asking "why was this done this way?" --- @@ -42,19 +41,20 @@ BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") +DIFF_LINES=$(git diff "$BASE"..HEAD --stat 2>/dev/null | tail -1) echo "BRANCH: $BRANCH" echo "REPO: $REPO" echo "BASE: $BASE" echo "COMMITS_AHEAD: $COMMIT_COUNT" +echo "DIFF_SUMMARY: $DIFF_LINES" git log "$BASE"..HEAD --oneline 2>/dev/null || true -git diff --stat "$BASE"..HEAD 2>/dev/null || true ``` If `COMMITS_AHEAD` is 0 and there are no uncommitted changes, print: ``` -nothing to hand off — no commits or changes ahead of $BASE -│ make some progress first, then run /handoff +nothing to hand off -- no commits or changes ahead of $BASE +make some progress first, then run /handoff ``` Stop. @@ -65,18 +65,17 @@ Stop. Use AskUserQuestion: -> **Branch:** `{BRANCH}` in `{REPO}` +> **Branch:** `{BRANCH}` in `{REPO}` -- {N} commits ahead of {BASE} > -> I'll analyze your recent work and produce a handoff document the next agent can -> load as context. How thorough should this be? +> I will produce a handoff document for the next agent. How thorough? > -> RECOMMENDATION: Choose B if you're handing off to another agent session today. +> RECOMMENDATION: Choose B if you are handing off to another agent session today. > Choose A for a quick record before a short break. > -> A) Quick — decisions + open threads only (Completeness: 5/10) +> A) Quick -- decisions + open threads only (Completeness: 5/10) > (human: ~5 min / CC: ~30 sec) > -> B) Deep — decisions, assumptions, danger zones, open threads, and a suggested +> B) Deep -- decisions, assumptions, danger zones, open threads, and a suggested > starting point for the next agent (Completeness: 10/10) > (human: ~20 min / CC: ~2 min) @@ -84,103 +83,163 @@ Store answer as `DEPTH` (quick or deep). --- -## Step 2: Mine commit history +## Step 2: Mine explicit decisions from commit history ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") -git log "$BASE"..HEAD --format="%H %s" 2>/dev/null -``` - -For each commit, read the full message: - -```bash git log "$BASE"..HEAD --format="%H%n%B%n---COMMIT_SEP---" 2>/dev/null ``` -Extract from commit messages: -- Explicit decisions ("chose X over Y", "switched from", "replaced", "removed", "reverted") -- Reasons given in commit bodies -- References to issues, PRs, or external constraints +Extract only what is explicit in commit messages: stated reasons, rejected +alternatives, references to issues or external constraints. Do not infer decisions +from code structure here -- that happens in Step 3. --- -## Step 3: Mine the diff for implicit decisions +## Step 3: Read the diff and ask targeted questions + +Read the diff of the most-changed files, skipping generated files. Cap to the top +20 files by change volume: ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") -git diff "$BASE"..HEAD 2>/dev/null +# Find top changed files, skip generated/lock files +git diff "$BASE"..HEAD \ + -- ':!*.lock' ':!package-lock.json' ':!yarn.lock' ':!*.min.js' ':!*.min.css' \ + ':!*-generated.*' ':!*.generated.*' ':!dist/*' ':!build/*' \ + --stat 2>/dev/null \ + | grep -v "changed" \ + | sort -t'|' -k2 -rn \ + | head -20 \ + | awk '{print $1}' ``` -From the diff, identify: +Read the diffs for those files: -**Decisions** — places where a non-obvious choice was made. Look for: -- A function that could have been written simpler but wasn't (why?) -- A data structure choice that isn't the obvious default -- An early return or guard clause that protects against a specific scenario -- A hardcoded value that looks like it was deliberate, not lazy +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD -- {top_files} 2>/dev/null +``` -**Assumptions** — things the code assumes to be true that aren't verified. Look for: -- Array access without bounds checking (`arr[0]`, `data[key]`) -- Type coercions (`as SomeType`, unchecked casts) -- Environment assumptions (`process.env.X` without fallback) -- Ordering assumptions ("this runs after X" with no enforcement) -- Comments that say "assume", "should be", "always", "never" +If the total diff exceeds 500 lines: read only the top 5 files by change volume. +Do not read the full diff -- summarize the remaining files by name and line count only. -**Danger zones** — code that is fragile, non-obvious, or likely to break. Look for: -- Timing-sensitive logic (setTimeout, retry loops, polling) -- Partially-implemented error handling (`catch(e) { /* TODO */ }`) -- Code that was patched on top of a previous patch -- Anything with a "don't touch" or "be careful" comment -- Functions that mutate shared state +From the diff, identify the 3 most non-obvious changes: a function that could have +been simpler but was not, a data structure that is not the obvious default, a guard +clause protecting against something specific, a value that looks deliberate. + +Use AskUserQuestion to ask about each one directly. Frame each question around +the specific change, not a generic "what decisions did you make?" For example: + +> You switched the retry logic in `auth.ts` from exponential to linear backoff. +> What drove that? + +> You removed the Redis cache layer from `session.ts`. Intentional or temporary? + +> `payments/webhook.ts` has a hardcoded 3-second delay at line 47. What is that +> protecting against? + +Ask only about what is genuinely non-obvious. If a change is self-explanatory from +the commit message or a comment, skip it. Maximum 3 questions. If the user is in a +hurry, they can skip a question -- record it as "rationale unknown" in the artifact. --- ## Step 4: Surface open threads +These are explicit signals -- do not infer them, just find them: + +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +# TODOs and FIXMEs introduced in this branch only +git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|WIP" | grep -v "^+++" || true +# Skipped or pending tests +git diff "$BASE"..HEAD | grep "^+" | grep -iE "\.skip|\.only|xit\b|xdescribe\b|pending\(" | grep -v "^+++" || true +# Stashed work +git stash list 2>/dev/null || true +``` + +--- + +## Step 5 (deep only): Surface real assumptions + +Skip if `DEPTH` is quick. + +Do not flag every array access or type cast. Flag only these three patterns, in +new code introduced in this branch: + +**Pattern 1 -- unguarded environment variables:** ```bash BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") -# TODOs introduced in this branch (not pre-existing) -git diff "$BASE"..HEAD | grep "^+" | grep -iE "TODO|FIXME|HACK|XXX|TEMP|WIP" | grep -v "^+++" || true -# Commented-out code blocks (likely removed but not decided) -git diff "$BASE"..HEAD | grep "^+" | grep -E "^\+\s*//" | head -20 || true -# Incomplete test coverage markers -git diff "$BASE"..HEAD | grep "^+" | grep -iE "skip|xit|xdescribe|pending|\.todo" || true +git diff "$BASE"..HEAD | grep "^+" | grep -E "process\.env\.[A-Z_]+" | grep -v "||" | grep -v "??" | grep -v "^+++" || true ``` +(Only flag if there is no fallback operator on the same line.) -Also check git stash for saved work: +**Pattern 2 -- explicit assumption comments:** +```bash +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD | grep "^+" | grep -iE "//.*\b(assume|assumes|should be|always|never|expected to)\b" | grep -v "^+++" || true +``` +**Pattern 3 -- external calls with no error handling:** ```bash -git stash list 2>/dev/null || true +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") +git diff "$BASE"..HEAD | grep "^+" | grep -iE "\.(fetch|query|findOne|execute|request)\(" | grep -v "catch\|try\|await\|\.then" | grep -v "^+++" | head -10 || true ``` +For each finding, record the file, line, and a one-sentence description of what +is assumed. Assign risk (low / medium / high) based on whether failure would be +silent vs. loud and whether it is in a critical path. + --- -## Step 5 (deep only): Suggested entry point for next agent +## Step 6 (deep only): Identify danger zones -Skip this step if `DEPTH` is `quick`. +Skip if `DEPTH` is quick. -Based on the open threads and danger zones identified, produce one concrete suggestion: +From the diff already read in Step 3, look for: +- Timing-sensitive code: setTimeout, setInterval, retry loops, polling, sleep +- Partial error handling: `catch` blocks that are empty, log-only, or contain TODO +- Layered patches: code added on top of a recent commit that itself was a fix +- Comments containing "don't touch", "be careful", "fragile", "hacky", "workaround" -- What should the next agent do FIRST -- What should the next agent NOT touch until a specific condition is met -- What question should the next agent answer before making changes +For each danger zone: record file, line range, why it is fragile, what is safe to +do, and what to avoid. -This is the most valuable sentence in the handoff: "Start here, and don't touch X until Y." +--- + +## Step 7 (deep only): Suggested entry point + +Skip if `DEPTH` is quick. + +Based on open threads and danger zones, produce one concrete recommendation: +- What the next agent should do first +- What the next agent should not touch until a specific condition is met +- The one question that, if answered, unblocks the most work + +This should be specific to the actual state of the branch, not generic advice. +"Review the auth module" is not acceptable. "Do not touch `payments/webhook.ts` +until the load test in issue #89 completes -- start with the session timeout fix +in `auth/middleware.ts` instead" is what this should look like. --- -## Step 6: Write the artifact +## Step 8: Write the artifact ```bash BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown") +BASE=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null || echo "main") TIMESTAMP=$(date +%Y-%m-%dT%H-%M-%S) +COMMIT_COUNT=$(git rev-list --count "$BASE"..HEAD 2>/dev/null || echo "0") mkdir -p ~/.gstack/handoffs ARTIFACT="$HOME/.gstack/handoffs/${REPO}-${BRANCH}-${TIMESTAMP}.md" REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +echo "ARTIFACT: $ARTIFACT" +echo "REPO_ROOT: $REPO_ROOT" ``` -Write the artifact to `$ARTIFACT` using the Write tool. Use this format: +Write the artifact using the Write tool: ```markdown # Handoff: {BRANCH} @@ -191,35 +250,34 @@ Write the artifact to `$ARTIFACT` using the Write tool. Use this format: ## Decisions -> What was chosen and why — including alternatives that were rejected. - -- **{decision}** +- **{description of change}** - Chose: {what} - - Rejected: {alternatives and why} - - Reason: {rationale from commit message or code context} + - Rejected: {alternatives, if known} + - Reason: {rationale from commit message or targeted question answer} -*(If no explicit decisions were found: "No decisions surfaced — commit messages were -terse. Review the diff manually if something seems non-obvious.")* +*(If rationale was unknown for a question: "Rationale unknown -- {description of +change}. Inspect before modifying.")* + +*(If no decisions were surfaced: "Commit messages were terse and no non-obvious +changes were identified. Review the diff manually.")* --- ## Assumptions +*(Quick mode: section omitted)* -> Things the code assumes to be true that aren't enforced or verified. - -- `{file}:{line}` — {what is assumed} *(risk: low|medium|high)* +- `{file}:{line}` -- {what is assumed} *(risk: low|medium|high)* -*(If none found: "No embedded assumptions detected.")* +*(If none found across all three patterns: "No assumptions detected.")* --- ## Danger Zones +*(Quick mode: section omitted)* -> Code that is fragile, partially done, or likely to break on contact. - -- `{file}:{line_range}` — {why it's fragile} - - Do: {safe approach} - - Don't: {what to avoid} +- `{file}:{line_range}` -- {why it is fragile} + - Safe: {what to do} + - Avoid: {what not to do} *(If none found: "No danger zones detected.")* @@ -227,70 +285,90 @@ terse. Review the diff manually if something seems non-obvious.")* ## Open Threads -> Work that was started but not finished, and why it stopped. - -- [ ] {description} — stopped because: {reason} - - Blocked by: {dependency or decision needed} +- [ ] {description} -- stopped because: {reason or "unknown"} *(If none: "No open threads.")* --- ## For the Next Agent +*(Quick mode: section omitted)* -> Load this file at the start of your session. Start here. +Start with: {specific file or task} -{suggested entry point — one concrete action} +Do not touch {X} until {condition}. -**Do not touch** {X} **until** {condition}. - -**Answer this question first:** {the question that unblocks the most work} +Answer this first: {the question that unblocks the most work} --- -*Generated by /handoff · gstack {VERSION} · {TIMESTAMP}* +*Generated by /handoff v{VERSION} -- {TIMESTAMP}* ``` -After writing the artifact, also copy it to `{REPO_ROOT}/HANDOFF.md` so the next -agent in the same repo can find it without knowing the path: +Copy to repo root: ```bash cp "$ARTIFACT" "$REPO_ROOT/HANDOFF.md" ``` -Then check if `HANDOFF.md` is already in `.gitignore`: +--- + +## Step 9: Inject into CLAUDE.md + +After writing the artifact, update the project CLAUDE.md so the next agent picks +it up automatically. Check if an active handoff entry already exists: + +```bash +grep -n "Active handoff" "$REPO_ROOT/CLAUDE.md" 2>/dev/null || echo "NO_ENTRY" +``` + +If `NO_ENTRY`: append to CLAUDE.md: + +``` +## Active handoff + +Load `HANDOFF.md` before starting work -- it contains decisions, danger zones, +and open threads from the last session on branch `{BRANCH}`. +``` + +If the entry already exists (a previous `/handoff` run): replace it with the +updated branch name and timestamp. This ensures the entry always points to the +current handoff, not a stale one. + +Then check `.gitignore` for both entries: ```bash +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) grep -q "^HANDOFF\.md$" "$REPO_ROOT/.gitignore" 2>/dev/null || echo "HANDOFF_NOT_IGNORED" ``` -If `HANDOFF_NOT_IGNORED`: append to `.gitignore`: +If `HANDOFF_NOT_IGNORED`: ```bash echo "HANDOFF.md" >> "$REPO_ROOT/.gitignore" -echo "Added HANDOFF.md to .gitignore — handoff artifacts are personal, not committed" ``` Print: ``` - ✓ handoff artifact written + handoff written ~/.gstack/handoffs/{REPO}-{BRANCH}-{TIMESTAMP}.md {REPO_ROOT}/HANDOFF.md (gitignored) - - next agent: load HANDOFF.md at the start of your session + CLAUDE.md updated -- next agent will load this automatically ``` --- ## Completion -Report status: +Report status using **DONE**, **DONE_WITH_CONCERNS**, **BLOCKED**, or **NEEDS_CONTEXT**. + +State how many decisions, assumptions, danger zones, and open threads were surfaced. -- **DONE** — Artifact written. State how many decisions, assumptions, danger zones, and open threads were surfaced. -- **DONE_WITH_CONCERNS** — Artifact written, but some sections were empty due to terse commit history. Recommend running `/handoff` with more verbose commits. -- **BLOCKED** — State what prevented artifact creation. +If `DONE_WITH_CONCERNS`: note if commit messages were terse (decisions section will +be sparse), or if the diff was too large to read fully (list which files were skipped). -If `PROACTIVE` is `true`, suggest: "Run `/retro` for velocity metrics, or `/review` before handing off to a reviewer." +If `PROACTIVE` is `true`: suggest `/retro` for velocity metrics or `/review` before +handing off to a reviewer. {{TELEMETRY}}