Commit b70174b687db

Vincent Demeester <vincent@sbr.pm>
2026-05-12 10:39:50
feat(handoff): add list and pickup workflows
Added --list and --pickup flags to the handoff script for discovering active handoffs and archiving them after use. Updated SKILL.md with documentation for the new workflows.
1 parent a8177df
Changed files (2)
dots
config
claude
skills
dots/config/claude/skills/handoff/scripts/create_handoff.py
@@ -53,7 +53,7 @@ def parse_args() -> argparse.Namespace:
     )
     parser.add_argument(
         "--name",
-        required=True,
+        default=None,
         help="Descriptive kebab-case slug for the handoff file (e.g. 'fix-auth-token-expiry').",
     )
     parser.add_argument(
@@ -73,6 +73,16 @@ def parse_args() -> argparse.Namespace:
         action="store_true",
         help="Create a new uniquely named handoff file instead of reusing an existing slug.",
     )
+    parser.add_argument(
+        "--list",
+        action="store_true",
+        help="List active handoffs for the current repo.",
+    )
+    parser.add_argument(
+        "--pickup",
+        default=None,
+        help="Pick up (read and archive) a handoff by slug name.",
+    )
     return parser.parse_args()
 
 
@@ -238,6 +248,61 @@ def scaffold_template(handoff_path: Path, name: str) -> None:
     handoff_path.write_text(content)
 
 
+def list_handoffs(repo_name: str) -> int:
+    """List active (non-archived) handoffs for a repo."""
+    handoff_dir = HANDOFF_DIR / repo_name
+    if not handoff_dir.exists():
+        print(f"No handoffs directory for '{repo_name}'.")
+        return 0
+
+    files = sorted(handoff_dir.glob("*.md"))
+    if not files:
+        print(f"No active handoffs for '{repo_name}'.")
+        return 0
+
+    print(f"Active handoffs for '{repo_name}':")
+    for f in files:
+        # Extract goal line from file
+        goal = ""
+        in_goal = False
+        for line in f.read_text().splitlines():
+            if line.startswith("## Goal"):
+                in_goal = True
+                continue
+            if in_goal and line.strip():
+                goal = line.strip()
+                break
+            if in_goal and line.startswith("##"):
+                break
+        print(f"  {f.stem}" + (f"  — {goal}" if goal else ""))
+    return 0
+
+
+def pickup_handoff(repo_name: str, slug: str) -> int:
+    """Read a handoff, print its content, and archive it."""
+    sanitized = sanitize_branch(slug)
+    handoff_dir = HANDOFF_DIR / repo_name
+    handoff_path = handoff_dir / f"{sanitized}.md"
+
+    if not handoff_path.exists():
+        print(f"Error: no handoff found at {handoff_path}", file=sys.stderr)
+        return 1
+
+    content = handoff_path.read_text()
+    print(content)
+
+    # Archive it
+    archive_dir = handoff_dir / ".archive"
+    archive_dir.mkdir(parents=True, exist_ok=True)
+    archive_path = archive_dir / handoff_path.name
+    # If archive already has one with same name, add suffix
+    if archive_path.exists():
+        archive_path = generate_unique_path(archive_path)
+    handoff_path.rename(archive_path)
+    print(f"\n--- Archived to {archive_path} ---")
+    return 0
+
+
 def main() -> int:
     args = parse_args()
     start = Path(args.repo).resolve()
@@ -249,6 +314,17 @@ def main() -> int:
 
     repo_name = detect_repo_name(root)
 
+    # Handle list/pickup actions before requiring --name
+    if args.list:
+        return list_handoffs(repo_name)
+
+    if args.pickup:
+        return pickup_handoff(repo_name, args.pickup)
+
+    if not args.name:
+        print("Error: --name is required (unless using --list or --pickup).", file=sys.stderr)
+        return 1
+
     branch = args.branch or detect_branch(root)
     if branch is None:
         branch = "(detached)"
dots/config/claude/skills/handoff/SKILL.md
@@ -10,12 +10,21 @@ Capture the current goal, progress, context, and next steps in a concise handoff
 ## Quick Start
 
 ```bash
+# Create a new handoff
 python3 "<path-to-skill>/scripts/create_handoff.py" --repo "." --name "<descriptive-slug>"
 # Add --json for structured output
+
+# List active handoffs for current repo
+python3 "<path-to-skill>/scripts/create_handoff.py" --repo "." --list
+
+# Pick up a handoff (reads content, then archives it)
+python3 "<path-to-skill>/scripts/create_handoff.py" --repo "." --pickup "<slug>"
 ```
 
 ## Workflow
 
+### Writing a handoff
+
 1. Inspect current git context and choose a short kebab-case slug describing the work
 2. Run the helper script to scaffold the handoff file with git context
 3. If an existing handoff exists for this slug, read it before updating
@@ -24,6 +33,17 @@ python3 "<path-to-skill>/scripts/create_handoff.py" --repo "." --name "<descript
 
 Use `--new-copy-if-exists` to create a fresh copy even when the slug already exists.
 
+### Picking up a handoff
+
+When resuming work (e.g. user says "check handoffs"):
+
+1. Run `--list` to show active handoffs for the repo
+2. Read and present the relevant handoff(s) to the user
+3. Run `--pickup <slug>` to archive the handoff — it has served its purpose
+4. The archived file moves to `.archive/` under the repo's handoff directory
+
+Archived handoffs can still be found in `~/.local/share/ai/handoffs/<repo>/.archive/` if needed later.
+
 ## Handoff Contents
 
 Include these sections: