From 328dd0082115f6afc96bcf2b743a555df1be2a1f Mon Sep 17 00:00:00 2001 From: Robert Gering Date: Mon, 15 Jun 2026 22:23:13 +0200 Subject: [PATCH 1/2] Make /define worktree-aware: write tasks to main repo When invoked from a linked worktree, /define now resolves the main repo path and writes the task file there, so the backlog stays centralized and survives /close removing the worktree. - Resolve via `git worktree list --porcelain`; detect linked-worktree invocation via `git rev-parse --show-toplevel` - Thread the absolute /tasks/ path through the duplicate check, write step, and confirmation output (no persistent cd) - Main-worktree invocation: paths identical, behavior unchanged - Bump work-system 1.2.4 -> 1.2.5; document centralized backlog in README Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/marketplace.json | 2 +- .../work-system/.claude-plugin/plugin.json | 2 +- plugins/work-system/README.md | 2 +- plugins/work-system/skills/define/SKILL.md | 34 +++++++++++++------ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index dc6c1db..79d7465 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -18,7 +18,7 @@ "name": "work-system", "source": "./plugins/work-system", "description": "Generic task and worktree workflow system for Claude Code. Manage tasks as markdown files, work in isolated git worktrees, and track progress through the full lifecycle.", - "version": "1.2.4" + "version": "1.2.5" }, { "name": "pr-flow", diff --git a/plugins/work-system/.claude-plugin/plugin.json b/plugins/work-system/.claude-plugin/plugin.json index 9e27f79..2a5cbee 100644 --- a/plugins/work-system/.claude-plugin/plugin.json +++ b/plugins/work-system/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "work-system", "description": "Generic task and worktree workflow system for Claude Code. Manage tasks as markdown files, work in isolated git worktrees, and track progress through the full lifecycle: create, start, continue, check, close.", - "version": "1.2.4", + "version": "1.2.5", "author": { "name": "gering" }, diff --git a/plugins/work-system/README.md b/plugins/work-system/README.md index a95e810..f620b50 100644 --- a/plugins/work-system/README.md +++ b/plugins/work-system/README.md @@ -39,7 +39,7 @@ Generic task and worktree workflow system for Claude Code. Manage tasks as markd ### Tasks -Each task is a markdown file in your project's `tasks/` directory: +Each task is a markdown file in your project's `tasks/` directory. This is a centralized backlog on the main worktree — `/define` always writes there, even when invoked from inside a linked worktree, so tasks stay visible to `/kickoff` and `/list` and survive `/close`: ```markdown # Add Dark Mode Support diff --git a/plugins/work-system/skills/define/SKILL.md b/plugins/work-system/skills/define/SKILL.md index 72f388d..c8add78 100644 --- a/plugins/work-system/skills/define/SKILL.md +++ b/plugins/work-system/skills/define/SKILL.md @@ -11,13 +11,24 @@ user_invocable: true > Create a new task file from current context and conversation +## Where the task file lands + +Task files are a **centralized backlog** on the main worktree, visible to `/kickoff` and `/list`. When `/define` runs from inside a linked worktree, the file must still land in the **main repo's** `tasks/` — otherwise it's isolated on the task branch and lost when `/close` removes the worktree. + +Resolve the main repo path once (step 1) and use it for every path below. Never persist a `cd` — Bash CWD leaks across tool calls; use the absolute `` path instead. + ## Arguments - `$ARGUMENTS` - Optional: brief description of the task ## Instructions -1. **Gather task information**: +1. **Resolve the main repo path**: + - Run: `git worktree list --porcelain | awk '/^worktree /{print $2; exit}'` → `` (first entry = main worktree). + - Run: `git rev-parse --show-toplevel` → current tree. + - If they differ, this is a linked-worktree invocation: the task file still goes to `/tasks/`. If they match, `` is the current repo and behavior is unchanged. + +2. **Gather task information**: If `$ARGUMENTS` provided, use as starting point for the task description. @@ -26,19 +37,19 @@ user_invocable: true - "Are there specific files or areas of code involved?" - "What are the acceptance criteria?" -2. **Generate task name**: +3. **Generate task name**: - Create a kebab-case name from the description - Examples: - "Fix the calendar date bug" → `fix-calendar-date-bug` - "Add dark mode support" → `add-dark-mode-support` - Show proposed name and ask for confirmation -3. **Check for duplicates**: - - Run: `ls tasks/ 2>/dev/null | grep -i ""` +4. **Check for duplicates**: + - Run: `ls "/tasks/" 2>/dev/null | grep -i ""` - If `gh` is available: `gh pr list --state open --search "" --limit 3` - If similar tasks exist, show them and ask if user wants to continue -4. **Create task file**: +5. **Create task file**: Template: ```markdown @@ -63,27 +74,28 @@ user_invocable: true ``` -5. **Include conversation context**: +6. **Include conversation context**: - If there was relevant discussion, summarize it in the Notes section - If specific code or files were mentioned, add them to Relevant Files - If errors or bugs were discussed, include error messages -6. **Write the file**: - - Path: `tasks/.md` +7. **Write the file**: + - Path: `/tasks/.md` (absolute — never `cd` into the main repo to write it) - Show the content to user for review - Ask: "Create this task file?" -7. **Confirm creation**: +8. **Confirm creation**: ``` - ✅ Task created: tasks/.md + ✅ Task created: /tasks/.md Next steps: • Start immediately: /kickoff • View all tasks: /list • Check status later: /status ``` + - When invoked from a linked worktree, add: "Written to the main repo backlog, not this worktree." -8. **Optional — Start immediately**: +9. **Optional — Start immediately**: - Ask: "Would you like to start working on this task now?" - If yes, proceed with `/kickoff` workflow From cf16c64c6daa61220076111b37f032877130fc14 Mon Sep 17 00:00:00 2001 From: Robert Gering Date: Tue, 16 Jun 2026 12:38:29 +0200 Subject: [PATCH 2/2] Extract robust main-repo resolver for /define Address code-review findings on the worktree-aware /define change: - Add scripts/main-repo-path.sh as the canonical resolver: - `path` strips the porcelain prefix via parameter expansion, so it survives repo paths containing spaces (the inline `awk '{print $2}'` truncated at the first space) - `linked` compares --git-common-dir vs --git-dir, immune to symlinked paths (the prior --show-toplevel string compare could false-positive) - Route /define step 1 through the helper instead of an inline command, removing a divergent copy of the worktree-resolution logic Sibling skills (kickoff/close/adopt/list/continue) still inline their own copy; migrating them to this helper is tracked as a separate task. Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/work-system/scripts/main-repo-path.sh | 42 +++++++++++++++++++ plugins/work-system/skills/define/SKILL.md | 9 ++-- 2 files changed, 46 insertions(+), 5 deletions(-) create mode 100755 plugins/work-system/scripts/main-repo-path.sh diff --git a/plugins/work-system/scripts/main-repo-path.sh b/plugins/work-system/scripts/main-repo-path.sh new file mode 100755 index 0000000..bbdad23 --- /dev/null +++ b/plugins/work-system/scripts/main-repo-path.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# main-repo-path.sh — resolve the main worktree for work-system skills. +# +# The shared task backlog (tasks/) lives in the main worktree. A skill invoked +# from a linked worktree must target the main worktree's tasks/, so it needs +# the main path and a way to tell the two invocations apart. +# +# Subcommands: +# path Print the absolute path of the main worktree (the first +# `git worktree list` entry). Robust against paths containing +# spaces — strips the porcelain "worktree " prefix without +# field-splitting. +# linked Print "linked" if the current directory is inside a linked +# worktree, "main" if it is the main worktree. Compares git's own +# dir bookkeeping (--git-common-dir vs --git-dir), so it is immune +# to symlinked paths rather than string-comparing two path forms. +# +# Exits non-zero if not run inside a git repository. +set -eu + +case "${1:-path}" in + path) + # Porcelain output starts each block with "worktree "; the first + # block is the main worktree. Take the first line, drop the prefix. + list="$(git worktree list --porcelain)" + first="${list%%$'\n'*}" + printf '%s\n' "${first#worktree }" + ;; + linked) + common="$(cd "$(git rev-parse --git-common-dir)" && pwd)" + gitdir="$(cd "$(git rev-parse --git-dir)" && pwd)" + if [ "$common" = "$gitdir" ]; then + echo "main" + else + echo "linked" + fi + ;; + *) + echo "usage: ${0##*/} [path|linked]" >&2 + exit 2 + ;; +esac diff --git a/plugins/work-system/skills/define/SKILL.md b/plugins/work-system/skills/define/SKILL.md index c8add78..cd72181 100644 --- a/plugins/work-system/skills/define/SKILL.md +++ b/plugins/work-system/skills/define/SKILL.md @@ -23,10 +23,9 @@ Resolve the main repo path once (step 1) and use it for every path below. Never ## Instructions -1. **Resolve the main repo path**: - - Run: `git worktree list --porcelain | awk '/^worktree /{print $2; exit}'` → `` (first entry = main worktree). - - Run: `git rev-parse --show-toplevel` → current tree. - - If they differ, this is a linked-worktree invocation: the task file still goes to `/tasks/`. If they match, `` is the current repo and behavior is unchanged. +1. **Resolve the main repo path** (shared helper — handles paths with spaces and symlinks): + - Run: `bash "${CLAUDE_PLUGIN_ROOT}/scripts/main-repo-path.sh" path` → `` (the main worktree, where the shared `tasks/` backlog lives). + - Run: `bash "${CLAUDE_PLUGIN_ROOT}/scripts/main-repo-path.sh" linked` → `main` or `linked`. `linked` = invoked from a worktree (the task file still goes to `/tasks/`); `main` = behavior unchanged. 2. **Gather task information**: @@ -93,7 +92,7 @@ Resolve the main repo path once (step 1) and use it for every path below. Never • View all tasks: /list • Check status later: /status ``` - - When invoked from a linked worktree, add: "Written to the main repo backlog, not this worktree." + - When step 1 reported `linked`, add: "Written to the main repo backlog, not this worktree." 9. **Optional — Start immediately**: - Ask: "Would you like to start working on this task now?"