tsugu: local-first prepare + human-takeover recognition (spec 012)#53
Conversation
…, schema 5 (spec 012 Change A)
…ug-pairing (spec 012 Change B)
…nup; auto-push invariant (spec 012 Changes C, D)
…eover (review, spec 012) Task-1-3 review (Minor): the pre-existing 011 note said containment outside accepted prefixes 'carries no derived meaning' — Change B now makes it the takeover signal. Reword so pending=slug-pairing and foreign containment=takeover don't contradict.
…nt takeover recipe (spec 012)
… + compat migration pins old yes (spec 012)
…al-first (spec 012)
…ec 012) The re-run migration example still said a schema-1 repo runs 1→2→3→4 and used a 3→4 commit-message example. Bump to 1→2→3→4→5 / 4→5 to match the schema-5 bump.
…hema-5 straggler, spec 012) Task-9 catch-all sweep caught a stale current-claim: the 3→4 section said a schema-1 repo runs 1→2→3→4 'under the N→N+1 contract'. Under schema 5 the full cumulative run is 1→2→3→4→5 (… then 4→5). (The 3→4 step's own 'Stamp tsugu-schema: 4 — last' at :418 is correct and stays.)
…n-over; README cold-start reads local+remote (spec 012) Final whole-branch review (2 Minor doc-consistency): git-recipes' prune-sweep 'surface + confirm' list now includes the taken-over (redundant prepare) bucket next to possibly-landed; README's cold-start paragraph now says the queue is read from local + remote work refs (was a leftover 'remote-tracking refs' line). The third Minor (duplicate 0.7.0 jq check) is harmless and left as-is. Refs #52.
There was a problem hiding this comment.
Pull request overview
Updates the tsugu skill to spec 012 by making prepare local-first (no remote push by default), adding human-takeover recognition by containment, and introducing schema/metadata updates so existing repos don’t silently change behavior.
Changes:
- Flip default behavior to local-first prepare via schema 5 + 4→5 migration that pins legacy
push-prepare-branches: yeswhen previously implicit. - Document takeover-by-containment and the taken-over (redundant prepare) disposition (suppress from auto-work, surface for human-confirmed cleanup).
- Bump tsugu marketplace/plugin metadata to v0.7.0 and expand content-guard checks accordingly.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/tsugu/test-skill-content.sh | Extends content-regression checks for spec-012 wording, schema 5, and version 0.7.0. |
| plugins/tsugu/skills/tsugu/templates/policy.md | Updates default policy template to tsugu-schema: 5 and push-prepare-branches: no with revised rationale. |
| plugins/tsugu/skills/tsugu/SKILL.md | Documents local-first discovery (local+remote), takeover-by-containment, taken-over disposition, and schema-aware push defaults. |
| plugins/tsugu/skills/tsugu/references/policy-and-intake.md | Updates schema references to current=5 and migration chain to include 5. |
| plugins/tsugu/skills/tsugu/references/notes-and-packet.md | Documents taken-over (redundant prepare) surfacing and human-confirmed cleanup. |
| plugins/tsugu/skills/tsugu/references/migrations.md | Adds Migration 4→5 definition (pin old push default; then stamp schema 5). |
| plugins/tsugu/skills/tsugu/references/git-recipes.md | Updates cold-start queue reading to local+remote union and adds takeover-by-containment recipe notes. |
| plugins/tsugu/skills/tsugu/README.md | Updates README to describe local-first prepare and takeover surfacing at prune/converge. |
| plugins/tsugu/commands/prune.md | Extends prune command description to include taken-over (redundant prepare) surfacing. |
| plugins/tsugu/commands/prepare.md | Updates prepare command description/invariants to local-first + local+remote discovery framing. |
| plugins/tsugu/commands/init.md | Updates init command description/invariants for schema 5 and 1→…→5 migration chain. |
| plugins/tsugu/.claude-plugin/plugin.json | Updates tsugu plugin description to mention local-first + containment takeover behavior. |
| .claude-plugin/marketplace.json | Bumps tsugu marketplace entry to v0.7.0 and updates description accordingly. |
Comments suppressed due to low confidence (1)
plugins/tsugu/skills/tsugu/README.md:136
- This README section still describes the derived partition in terms of only “settled” and “pending (slug-paired accepted branch)”. With spec 012, there is now a third derived state (“taken over” via non-default, non-work containment). The README should mention that state (and its effect: suppressed from auto-work, surfaced at prune/converge) to avoid contradicting the updated docs elsewhere.
After `git fetch`, the queue is read from **local + remote** work-prefix refs
(local-first by default; remote work refs are read too, for opt-in pushes and
leftovers) — branch names plus each branch's `context.md` must be legible enough
that a cold-start agent can
reconstruct what branches exist, why, and what's next, with **zero conversation
transcript**.
## State is derived
Tsugu writes **no status fields**. Live coordination state is read from git's own
facts — ref names, ancestry, containment, commit recency — never from a tracked
status line. There is **no inbox layer and no recorded landed-SHA**; the partition
is single-layer, classifying each work branch by two ref-level facts:
- **settled** = the work landed, derived from **containment** (the branch's tip is
contained in the default branch).
- **pending** (decided, awaiting merge) = a **slug-paired accepted branch** exists —
a branch under a configured accepted prefix sharing the work branch's slug. The
pairing is by name, so it survives anything the forge does to commits.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| The schema-version stamp (current: `5`). It is the first line of the file, and a | ||
| migration **writes it last** — only after every N→N+1 rename and semantic change | ||
| has been applied does `init` stamp the new number, so a half-applied migration is | ||
| never mistaken for a completed one. Readers use it to decide whether a re-run of | ||
| `init` must migrate (older stamp → apply `references/migrations.md` in order, | ||
| 1→2→3→4 for a schema-1 repo) or is a plain idempotent repair (stamp already | ||
| 1→2→3→4→5 for a schema-1 repo) or is a plain idempotent repair (stamp already | ||
| current). |
There was a problem hiding this comment.
Fixed in 041d983: §Push rewritten to schema-5 local-first — default no, with the schema-aware absent read (yes only if the repo is still tsugu-schema: 4, else no) and the 4→5 migration pinning behavior described. Independently flagged by our local Codex gate (F3).
| **4b. Detect a human takeover by containment.** A `prepare/<slug>` is **taken | ||
| over** when its tip is **contained** by a **branch** that is neither the default | ||
| (nor its aliases) nor a work-prefix ref — a human carried the work onto their own | ||
| branch (`isaac/fix-thing`), which 011's slug-name pairing alone could not see (#52). | ||
| This **generalizes** slug-pairing (a tsugu accepted branch contains the tip too, so | ||
| containment catches it); slug-name pairing **stays** as the complementary catch for | ||
| the squashed/rewritten take. The check must be **precise** — a loose filter | ||
| false-positives, and the cleanup (Change C) is destructive on a wrong hit, so the | ||
| fetch-first + branch-scope + alias/work-ref exclusion below are **load-bearing**: |
There was a problem hiding this comment.
… note (PR review, spec 012) PR-phase Codex review found two git-recipes consequences of 012: - takeover recipe normalizes the <remote>/ prefix but excluded '<remote>/HEAD' — post-normalization origin/HEAD becomes bare HEAD and slipped the alias filter (false-positive). Exclude the NORMALIZED forms: <default> AND HEAD; <work-prefix>/* catches both local and normalized-remote work refs. - the 'each kept queue line is already a full remote-qualified ref' note was false after adding local enumeration — a kept line is local (prepare/foo) OR remote (origin/prepare/foo); use each verbatim, never re-prefix a remote one. Refs #52.
…he pending carve-out (spec 012; review F1+F2) Codex local review found the takeover-by-containment model described two incompatible ways: - SKILL.md:130 carved "takeover = containment OUTSIDE accepted prefixes; pending = slug-pairing" — re-splitting what SKILL.md:121's table already merges into one "decided / taken over" row, and contradicting git-recipes §4b, whose filter excludes only default + work-prefix refs (so an accepted-prefix handoff branch is caught as takeover, by design — :160's "generalizes slug-pairing"). - git-recipes §6 "Partition the queue" billed itself as "the condensed canonical form of SKILL.md's partition" but dropped the taken-over row entirely (3-way settled/pending/in-progress), and its code echoed `pending`. Resolution (per author): there is no separate *pending* state. Containment by any non-default, non-work branch — an accepted-prefix handoff OR a human's own branch — means a human now owns the work and tsugu stops managing it; both land in the same disposition (skip, surface at prune/converge, never auto-delete). Slug-pairing stays only as the complementary catch for the squash/rewrite take where the rewrite severed containment. Nothing is recorded — all derived live from refs (single-layer). - SKILL.md:130 rewritten to the merged "handoff is a takeover" framing. - git-recipes §6 table + code (echo taken-over) + prose aligned; the §4b filter is unchanged (it already catches accepted branches correctly). - Residual partition-"pending"/"decided" wording (git-recipes :127, :278) retagged taken-over. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… schema-aware absent read (spec 012; review F3) The canonical policy-field doc still documented schema-4 push semantics: the example stamped `push-prepare-branches: yes`, stated "The default is yes," and "absent → readers default to yes." Schema 5 flipped the new-install default to `no` (local-first) with a schema-aware absent read (yes only if the repo is still tsugu-schema: 4, else no). Rewrote §Push to match SKILL.md:146 and the templates/policy.md ship value (no), and reframed the branch-as-message line for local-first (the local branch is the queue on one machine; pushing is the cross-machine opt-in). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tracking only (spec 012; review F4) Three labels still framed discovery as remote-tracking-only, contradicting Change A's local-first union (git-recipes 145–152, "same read path as prepare"): - converge.md:12 invariants — "from remote-tracking refs after fetch" - SKILL.md:61 mechanics pointer — "read-queue-from-remote-refs" - SKILL.md:170 converge step 1 — "read everything from remote-tracking refs" Under local-first the work stays local (push is opt-in), so a converge that reads only remote-tracking refs would miss unpushed local-first work on the provisioned machine. Reworded all three to "local + remote work refs (local-first, the same union as prepare)," and qualified the machine-B reconstruct-from-fetch line as the cross-machine push opt-in case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al-first, takeover (spec 012; review F6) The project CLAUDE.md tsugu entry was stale after the schema bump: it still read "Schema 4 (lineage … → 008)", described state as "pending = slug-paired accepted branch," omitted local-first prepare, and the spec list ended at 011. - Schema 4 → 5; lineage extended → 011 → 012; spec list appends 012. - prepare noted as local-first (kept local by default; push is the cross-machine opt-in). - "pending = slug-paired accepted branch" → the reconciled takeover model (taken-over = a non-default/non-work branch contains the tip; slug-pairing the complementary catch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e them (review F6) The content-regression test passed green while four stale spots survived, because need/refute scan only SKILL.md and the per-file checks didn't reach them. Added anti-regression guards, each verified to fail on the pre-fix PR-tip content and pass on the fix: - F1: git-recipes §6 partition must echo taken-over, never `pending`. - F2: SKILL.md must not carry the old "outside the accepted prefixes is the takeover … not from foreign containment" carve-out. - F3: policy-and-intake §Push must read local-first + schema-aware, not "default is yes". - F4: converge.md must not frame discovery as "remote-tracking refs after fetch". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (review round 2; Codex P1) The reconciled §6 table claimed "taken over" covers a human's own branch containing the tip, but the snippet only echoed taken-over for a slug-paired $accepted ref — a human's own branch (isaac/fix-thing) fell through to in-progress, so tsugu would keep working a branch a human already took over. - Added the §4b for-each-ref --contains filter ($foreign_contains) and made the taken-over arm fire on `accepted OR foreign_contains`, so the canonical snippet is now executable and complete (settled still wins by precedence). - Fixed the remote-only `branch=<remote>/<work-prefix>/<slug>` line — the enumerated ref is taken verbatim from step 4 and may be local or remote. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…chema 5 local-first) (review round 2; Codex) The init bootstrap question still documented "default: yes" for "may agents push prep branches automatically," contradicting schema 5's local-first default (templates/policy.md ships push-prepare-branches: no). Reworded to default no, with yes as the cross-machine opt-in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cided, awaiting merge)" partition label (review round 2; Codex P3) SKILL.md:130 and git-recipes §6 reconciled the partition's second state to "taken over," but four consumers still taught the retired "pending / decided, awaiting merge" model as a distinct partition state: - README.md:134 state-model bullet - policy-and-intake.md §Accepted Prefixes - templates/policy.md §Accepted Prefixes comment - advanced.md forced-squash procedure (×2) Relabeled each to "taken over" (slug-paired handoff OR foreign containment), keeping "awaiting-merge" only where it names converge's live display, not the partition state. "awaiting-merge section"/"disposition" display wording is left intact per the agreed model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lt, retired pending label (review round 2; Codex P4) Added anti-regression guards (each verified to fail on the pre-fix PR-tip): - §6 partition must consume §4b containment ($foreign_contains), so a human's own branch reads taken-over, not in-progress — the table/code mismatch the prior `echo pending` guard couldn't catch. - init prep-branch push question must default no (schema-5), not yes. - the retired "decided, awaiting merge" partition label must be gone across the whole skill dir; README must not list "pending" as a partition state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…und 3; Codex) The new $foreign_contains pipeline ends in `grep -vE`, which exits 1 when no foreign ref contains the tip — the common in-progress case. Under the recipe's documented `set -euo pipefail`, the command substitution would then abort the script before the `else echo in-progress` arm. Appended `|| true` (the project's set-e-safe grep idiom) and added a test guard so the recipe stays runnable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(Copilot review) Copilot inline nit (r3426232558): both takeover detection and prune cleanup consume a leftover remote prepare/*, so the verb is plural. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| | **settled** | tip contained in `<remote>/<default>` — the human merged with history intact | **delete on confirm** (low risk — provably landed). Local + remote. The main post-handoff target | | ||
| | **taken-over (redundant prepare)** | a `prepare/<slug>` (local or remote) whose tip a **non-work, non-default** branch contains — a human carried the work onto their own branch | **surface + confirm each** (like *possibly-landed*) — never auto-delete; the containment signal can false-positive on a build-on-top branch. On confirm, delete local + remote. **Precedence:** if `<remote>/<default>` contains the tip it is **settled** (above); *taken-over* covers only the case where the containing ref is a **non-default** branch | |
There was a problem hiding this comment.
Resolved in ec58dd2. By design the prune taken-over (redundant prepare) bucket is the git-containment-derivable take only (a non-default, non-work branch contains the prepare tip). The slug-paired squash/rewrite handoff severs containment and is not git-derivable, so it is intentionally the separate possibly-landed (no containment) bucket (human-confirmed) — folding it into taken-over would double-cover that bucket. Added a disambiguating cross-reference here so the two stay distinct.
| - **taken-over (redundant prepare)** — a `prepare/<slug>` whose tip a **non-default, | ||
| non-work branch** contains (a human carried the work onto their own branch — see the | ||
| containment-takeover read above). Surface + confirm each, like *possibly-landed*; on | ||
| confirmation delete the redundant `prepare/<slug>` **local and remote, both | ||
| human-confirmed**; never auto-delete. Precedence: classify as *settled* when default |
There was a problem hiding this comment.
Resolved in ec58dd2. By design the prune taken-over (redundant prepare) bucket is the git-containment-derivable take only (a non-default, non-work branch contains the prepare tip). The slug-paired squash/rewrite handoff severs containment and is not git-derivable, so it is intentionally the separate possibly-landed (no containment) bucket (human-confirmed) — folding it into taken-over would double-cover that bucket. Added a disambiguating cross-reference here so the two stay distinct.
| <!-- prepare is LOCAL-FIRST by default (no): work stays on local prepare/* | ||
| branches — prepare commits locally and does not push. Set yes for the | ||
| CROSS-MACHINE OPT-IN: pushing makes the branch a message a second machine's | ||
| agent can inherit (cross-machine handoff reads remote refs), and restores |
There was a problem hiding this comment.
Fixed in 841914d: added the missing "that" — "a message that a second machine's agent can inherit."
| # work queue — LOCAL + remote, configured work prefixes (default shown); union by slug. | ||
| # Discovery reads remote work refs REGARDLESS of the push default — only PUSHING is gated | ||
| # (a leftover or opt-in-pushed remote prepare/* must still be seen; it is what takeover/prune target). | ||
| git branch --format='%(refname:short)' \ |
| contained in default) and **leftover worktrees** directly on confirm; surfaces | ||
| **dropped / possibly-landed (no containment) / orphaned-accepted** for explicit | ||
| confirmation; **stale in-progress** is surfaced read-only and pointed at | ||
| **dropped / possibly-landed (no containment) / orphaned-accepted / taken-over | ||
| (redundant prepare — a non-work, non-default branch contains the tip)** for explicit | ||
| confirmation (never auto-delete on a containment guess); **stale in-progress** is surfaced read-only and pointed at |
There was a problem hiding this comment.
Resolved in ec58dd2. By design the prune taken-over (redundant prepare) bucket is the git-containment-derivable take only (a non-default, non-work branch contains the prepare tip). The slug-paired squash/rewrite handoff severs containment and is not git-derivable, so it is intentionally the separate possibly-landed (no containment) bucket (human-confirmed) — folding it into taken-over would double-cover that bucket. Added a disambiguating cross-reference here so the two stay distinct.
| **Taken-over (redundant prepare) — surfaced, never auto-deleted.** A | ||
| `prepare/<slug>` whose tip a **non-work, non-default** branch contains (a human | ||
| carried the work onto their own branch — `isaac/fix-thing`) is **taken over**: | ||
| suppressed from auto-work and **surfaced** at `prune`/`converge` (the packet's | ||
| `## Need human decisions` / `## Public actions requiring approval`) for the human to |
There was a problem hiding this comment.
Resolved in ec58dd2. By design the prune taken-over (redundant prepare) bucket is the git-containment-derivable take only (a non-default, non-work branch contains the prepare tip). The slug-paired squash/rewrite handoff severs containment and is not git-derivable, so it is intentionally the separate possibly-landed (no containment) bucket (human-confirmed) — folding it into taken-over would double-cover that bucket. Added a disambiguating cross-reference here so the two stay distinct.
…achine's agent can inherit" (Copilot review) Copilot inline nit (policy.md:17): missing "that" made the cross-machine opt-in rationale harder to parse in the template init writes into repos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…derivable take; squash/rewrite → possibly-landed (Copilot review; spec 012) Copilot flagged that the prune "taken-over (redundant prepare)" bucket defines takeover via foreign containment only, while the reconciled partition state "taken-over" also covers the slug-pairing (squash/rewrite) catch — same word, two scopes, reader-confusion risk. Resolution (per author — the model is git-containment-based; squash/rewrite is not git-derivable right now): keep the bucket git-containment-only and add a disambiguating cross-reference. The non-git-derivable squash/rewrite handoff is the *possibly-landed (no containment)* bucket (human-confirmed), never this one. - SKILL.md prune table, git-recipes §prune-sweep bullet, notes-and-packet taken-over para: scoped to "git-containment-derivable", squash/rewrite → possibly-landed. - prune.md command desc: same one-line disambiguation. - test guards for all four (phrase absent at PR-tip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the paired-prepare cleanup (review round 5; Codex) Codex round 5, two points on the round-4 disambiguation: 1. The broad partition state `taken-over` (two sources: foreign containment + slug-pairing) shares its name with the prune bucket `taken-over (redundant prepare)` (foreign-containment only) — routing stayed ambiguous. Made it explicit in SKILL.md's prune table and git-recipes §prune-sweep: the bucket takes ONLY the foreign-containment source; the slug-pairing squash/rewrite source goes to possibly-landed. 2. Since squash/rewrite is routed to possibly-landed (not git-derivable), the surviving paired `prepare/<slug>` needs an explicit cleanup home. The possibly-landed bucket now states confirmation also deletes the paired stale prepare ref (local + remote, human-confirmed) — matching advanced.md. - Updated the round-4 test guards to the new wording + added paired-stale guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| | tip contained in `<remote>/<default>` (or its remote aliases) | **settled** — the work landed | skip; `prune` cleanup candidate | | ||
| | a slug-paired branch (by name) exists under a configured `## Accepted Prefixes` **OR** any **non-default, non-work** branch contains the tip | **decided / taken over** — a human carried the work onto their own branch | skip as a candidate; surfaced at `prune`/`converge` (see *taken-over*, Change C) | | ||
| | none of the above | **in progress** | candidate: read `context.md`, judge from the narrative | |
There was a problem hiding this comment.
Fixed in 36616c7: the partition row now reads taken over (canonical), with a parenthetical noting the two sources (own-branch carry or slug-paired handoff). Added a refute guard for the retired 'decided / taken over' hybrid.
…e hybrid "decided / taken over" label (Copilot review)
Copilot (SKILL.md:122): the partition table still used the hybrid label
"decided / taken over" while the rest of the docs use "taken over" as the
canonical state name ("decided, awaiting merge" was retired). Relabeled the row
to "taken over" and added a refute guard for the hybrid form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| **review-loop:** Assisted, not autonomous, multi-reviewer convergence loop. Local reviewers run first — a Claude subagent always, plus Codex via `codex exec review` (headless) when `codex` is on `PATH`; tmux optional for a live-watch pane — then GitHub Copilot for PR targets. Helper scripts in `skills/review-loop/scripts/` (`copilot.sh`, `pr-comments.sh`) are referenced via `${CLAUDE_PLUGIN_ROOT}`. Target scope is any changed artifact: code or design artifacts (specs, plans, docs). One slash command: `/review-loop [PR# | branch | blank]`. Spec: `docs/superpowers/specs/002-review-loop-plugin-design.md` (Codex channel superseded by `docs/superpowers/specs/003-review-loop-headless-codex-design.md`). | ||
|
|
||
| **tsugu:** Git-native skill for unattended work preparation and human–agent convergence (継ぐ — "to inherit / continue / carry forward"). Schema 4 (lineage: 004 → 005 → 006 → 007 → 008). Using git's DAG as the coordination substrate, an agent prepares engineering work privately on git branches (often while the human is away), records evidence in per-ref `context.md`, and promotes durable findings into `knowledge/`; the human then converges — reads prepared branches live, decides what becomes public, and hands the work off for landing in one human-present session. Four routines: `init` (set up the repo's committed `.tsugu/` workspace + `policy.md`; idempotent; migrates older schemas), `prepare` (private git work on the configured work-prefix branches — default `prepare/*` — that **gathers understanding** rather than finalizing: investigation, root cause, option space, trade-offs, with reference code optional/partial and a scope-only branch a first-class outcome; + tests + evidence; recurses into `.tsugu/`-bearing submodules; external silence), `converge` (read branches live, present status view, decide with the human, accept/park/drop, promote knowledge; **accept defaults to a handoff-only rename** — `git branch -m prepare/<slug> <accepted-prefix>/<slug>`, agent does not rebase/verify/push/PR — with a **human-marked maintenance exception** that unlocks the complete-to-ready path; the agent never self-classifies maintenance; invokes no skill — the human triggers skills by keyword), `prune` (human-present, read-only-until-approved sweep of unused local + remote branches: deletes settled / leftover-worktree on confirm, surfaces dropped / possibly-landed / orphaned-accepted for explicit per-item confirmation, never touches unfinished work). Committed `.tsugu/` is a **WIP-knowledge layer** — a richer, agent-maintained sibling of `AGENTS.md`/`CLAUDE.md` — holding exactly: `policy.md` + `context.md` + `knowledge/`. Everything about how Tsugu operates for one human lives in a **personal global folder** (`~/.claude/tsugu/<project-key>/`, per machine, never committed): observation sources, opt-in skills, and the converge packet (derived, regenerated live). **State is single-layer** — no status fields, no intake notes, no `runs/`, no recorded landed-SHA; settled = containment in the default branch; pending = slug-paired accepted branch. A forced-squash severs containment derivation, so the work re-surfaces live at each `converge` until the human confirms landing (retain the accepted branch; no SHA recorded). Accepted-prefixes default `feature/* bugfix/* chore/*`. **Merge commits are recommended.** **Never auto-merges** (no public coordination without approval). **Light / script-free** — recipes are documented guidance; no scripts shipped. **Invokes no user-installed skill by default** — native git + its own built-in subagents only; a human's **personal config** (`~/.claude/tsugu/<project-key>/config.md`) may opt in to named skills per machine. Four slash commands: `/tsugu:init`, `/tsugu:prepare`, `/tsugu:converge`, `/tsugu:prune`. Spec: `docs/superpowers/specs/004-tsugu-skill-design.md` + `docs/superpowers/specs/005-tsugu-agent-first-design.md` + `docs/superpowers/specs/006-tsugu-workspace-transfer-design.md` + `docs/superpowers/specs/007-tsugu-thin-core-design.md` + `docs/superpowers/specs/008-tsugu-submodule-recursion-design.md` + `docs/superpowers/specs/011-tsugu-handoff-converge-design.md`. | ||
| **tsugu:** Git-native skill for unattended work preparation and human–agent convergence (継ぐ — "to inherit / continue / carry forward"). Schema 5 (lineage: 004 → 005 → 006 → 007 → 008 → 011 → 012). Using git's DAG as the coordination substrate, an agent prepares engineering work privately on git branches (often while the human is away), records evidence in per-ref `context.md`, and promotes durable findings into `knowledge/`; the human then converges — reads prepared branches live, decides what becomes public, and hands the work off for landing in one human-present session. Four routines: `init` (set up the repo's committed `.tsugu/` workspace + `policy.md`; idempotent; migrates older schemas), `prepare` (private **local-first** git work on the configured work-prefix branches — default `prepare/*`, kept local by default and pushed only on the cross-machine `push-prepare-branches: yes` opt-in — that **gathers understanding** rather than finalizing: investigation, root cause, option space, trade-offs, with reference code optional/partial and a scope-only branch a first-class outcome; + tests + evidence; recurses into `.tsugu/`-bearing submodules; external silence), `converge` (read branches live, present status view, decide with the human, accept/park/drop, promote knowledge; **accept defaults to a handoff-only rename** — `git branch -m prepare/<slug> <accepted-prefix>/<slug>`, agent does not rebase/verify/push/PR — with a **human-marked maintenance exception** that unlocks the complete-to-ready path; the agent never self-classifies maintenance; invokes no skill — the human triggers skills by keyword), `prune` (human-present, read-only-until-approved sweep of unused local + remote branches: deletes settled / leftover-worktree on confirm, surfaces dropped / possibly-landed / orphaned-accepted for explicit per-item confirmation, never touches unfinished work). Committed `.tsugu/` is a **WIP-knowledge layer** — a richer, agent-maintained sibling of `AGENTS.md`/`CLAUDE.md` — holding exactly: `policy.md` + `context.md` + `knowledge/`. Everything about how Tsugu operates for one human lives in a **personal global folder** (`~/.claude/tsugu/<project-key>/`, per machine, never committed): observation sources, opt-in skills, and the converge packet (derived, regenerated live). **State is single-layer** — no status fields, no intake notes, no `runs/`, no recorded landed-SHA; settled = containment in the default branch; taken-over = a non-default, non-work branch (an accepted-prefix handoff **or** a human's own branch) contains the tip, so a human now owns it (slug-pairing is the complementary catch for a history-rewriting landing). A forced-squash severs containment derivation, so the work re-surfaces live at each `converge` until the human confirms landing (retain the accepted branch; no SHA recorded). Accepted-prefixes default `feature/* bugfix/* chore/*`. **Merge commits are recommended.** **Never auto-merges** (no public coordination without approval). **Light / script-free** — recipes are documented guidance; no scripts shipped. **Invokes no user-installed skill by default** — native git + its own built-in subagents only; a human's **personal config** (`~/.claude/tsugu/<project-key>/config.md`) may opt in to named skills per machine. Four slash commands: `/tsugu:init`, `/tsugu:prepare`, `/tsugu:converge`, `/tsugu:prune`. Spec: `docs/superpowers/specs/004-tsugu-skill-design.md` + `docs/superpowers/specs/005-tsugu-agent-first-design.md` + `docs/superpowers/specs/006-tsugu-workspace-transfer-design.md` + `docs/superpowers/specs/007-tsugu-thin-core-design.md` + `docs/superpowers/specs/008-tsugu-submodule-recursion-design.md` + `docs/superpowers/specs/011-tsugu-handoff-converge-design.md` + `docs/superpowers/specs/012-tsugu-local-first-prepare-design.md`. |
There was a problem hiding this comment.
Fixed in 15231d0: CLAUDE.md's prune list now includes taken-over (redundant prepare).
| <!-- human-workflow branches the handoff RENAMES prepare/<slug> into for PRs | ||
| (converge renames, never cuts). A branch here with the same slug as a work | ||
| branch = that work is decided, awaiting merge. --> | ||
| branch = that work is taken over (a handoff the human owns; converge | ||
| surfaces it as awaiting-merge). --> |
There was a problem hiding this comment.
Fixed in 15231d0: the template comment now names the target — "RENAMES prepare/ into — / — for PRs."
| @@ -111,19 +111,23 @@ The core routine. No human is present, so Tsugu does its own git work directly a | |||
|
|
|||
| 1. **Fetch first** (`git fetch --prune <remote>`), resolving `<remote>` + `<default>`, so every read below uses fresh remote-tracking refs, not a stale checkout. | |||
There was a problem hiding this comment.
Fixed in 15231d0: step 1 reworded — fetch refreshes the remote half, and the queue is read local + remote (step 3), not remote-tracking only.
| - **Pending pairs by slug, not commits.** The accepted branch shares the work branch's slug; ref names are write-once identity, so the pending state survives anything the forge does to commits (PR-branch rebases, squashes, force-pushes). Containment in refs *outside* the configured accepted prefixes carries no derived meaning. | ||
| - **Handoff *is* a takeover; slug-pairing is the complementary catch.** Containment by **any** non-default, non-work branch is the **takeover** signal (Change B) — and that branch may equally be an **accepted-prefix** ref (a `converge` handoff) **or** a human's own branch (`isaac/fix-thing`): either way a human now owns the work and tsugu stops managing it. There is no separate *pending* classification competing with takeover. Slug-pairing stays only as the **complementary** catch for the squashed / rewritten take — where the rewrite severed containment but the accepted branch's name still pairs (ref names are write-once identity, so the pairing survives any commit rewrite the forge does). Both paths land in the **same** disposition: skip as a candidate, surface at `prune`/`converge`, never auto-delete. | ||
| - **Core assumes merge commits.** Tsugu **recommends merge commits — do not squash-merge tsugu-managed branches**; preserved history is what makes settlement containment-derivable. Accept is **mode-agnostic** (the work branch is renamed to the accepted branch in **both** `include` and `exclude` mode — see converge / Change B), so settlement reads off the **accepted branch's** containment in default in both modes. Because that disposition can only be read *while the slug-paired accepted ref survives*, **recommend disabling the forge's auto-delete-head-branch for tsugu accepted branches**. *A landing that rewrites history (squash, rebase-before-merge, force-push) — or, in `exclude` mode, the human stripping `.tsugu/` into a fresh published branch before merging — breaks containment-derived settlement; the item then settles via `prune`'s possibly-landed (no containment) — confirm bucket; see `references/advanced.md`.* | ||
| - **No marker — the partition + conservative judgment guard (see B1a).** Default handoff writes **no** marker (it renames and stops). A scheduled `prepare` is kept off handed-off work by the two-fact partition: **containment** (a merge-commit landing → settled) or **slug-pairing** (the retained accepted branch, **enumerated local + remote** → decided). For a **non-containment landing** (a rewrite, or an `exclude`-strip) the retained accepted branch still pairs by slug; the advanced narrative backstop is only a **legibility** note on the accepted branch, **not** a resume-guard (a note there can't reach a surviving remote `prepare/<slug>`). The one signal-less residual is the accepted ref **deleted** *and* B3 not yet run, so a stale remote `prepare/<slug>` survives: held to the **same 004–008 guarantee level** — `prepare`'s judgment leans conservative (leave the stale branch for `converge`; reversible; never auto-merges), and running **B3 promptly** removes the ref (once no ref survives, the item just leaves the partition — the work is already in `<default>`). *Judgment, not a written status, governs.* |
There was a problem hiding this comment.
Fixed in 15231d0: '→ decided' retired to '→ taken over' (canonical state).
| customized them. **Also enumerate the configured `## Accepted Prefixes`** | ||
| (defaults: `feature`, `bugfix`, `chore`, plus legacy `public`) into a | ||
| **separate accepted list** — these are not queue items but are needed in step 6 to | ||
| pair a work branch's slug against a decided accepted branch. **The accepted list | ||
| spans LOCAL *and* remote refs** (unlike the work queue, which is remote-tracking | ||
| only): at `converge`, the just-renamed `<accepted-prefix>/<slug>` exists **locally** | ||
| spans LOCAL *and* remote refs** — as does the work queue itself (012 unions |
There was a problem hiding this comment.
Fixed in 15231d0: 'a decided accepted branch' → 'its accepted branch (a taken-over handoff)', aligning with the SKILL.md partition terminology.
| @@ -56,8 +56,9 @@ The **human-workflow** namespaces the handoff **renames** `prepare/<slug>` into | |||
| the human's PR (converge renames, never cuts). | |||
There was a problem hiding this comment.
Fixed in 15231d0: now reads 'renames prepare/ into — / — for the human's PR', matching the template.
…rename grammar (Copilot review) Copilot's pass on the reconciled HEAD found six terminology/grammar leftovers the model retirement missed: - SKILL.md:112 prepare step 1 — "remote-tracking refs" reframed to local + remote. - SKILL.md:132 + git-recipes:100 — "→ decided" / "decided accepted branch" retired to "taken over". - SKILL.md:44 + notes-and-packet:8 — partition state-list "in progress / decided / settled" → "taken over". - git-recipes:132 + migrations:89 — legacy public/* "decided/landed" → "taken-over/landed". - policy-and-intake:56 + templates/policy.md:23 — "renames prepare/<slug> into for PRs" (missing target) → names <accepted-prefix>/<slug>. - CLAUDE.md prune list now includes the taken-over (redundant prepare) bucket. Added dir-wide guards: no residual "decided" partition label, no "into for" grammar, CLAUDE.md prune list carries taken-over. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| git branch --format='%(refname:short)' \ | ||
| | grep --extended-regexp "^(prepare)/" # local-first (default) | ||
| git branch --remotes --format='%(refname:short)' \ | ||
| | grep --extended-regexp "^<remote>/(prepare)/" # pushed mode | ||
| | grep --extended-regexp "^<remote>/(prepare)/" # cross-machine opt-in mode (pushed) |
There was a problem hiding this comment.
Fixed in b7e9339: appended || true to the work-queue enumeration greps (local + remote) so an empty queue doesn't abort the recipe under set -euo pipefail.
| git branch --format='%(refname:short)' \ | ||
| | grep --extended-regexp "^(feature|bugfix|chore|public)/" # local accepted | ||
| git branch --remotes --format='%(refname:short)' \ | ||
| | grep --extended-regexp "^<remote>/(feature|bugfix|chore|public)/" # remote accepted |
There was a problem hiding this comment.
Fixed in b7e9339: same || true applied to the accepted-branch enumeration greps. Added a test guard that no grep --extended-regexp enumeration line is left unguarded.
… true) (Copilot review) Copilot (git-recipes:118, :126): the work-queue and accepted-list enumeration greps (`git branch | grep --extended-regexp ...`) exit 1 on an empty queue — a valid outcome — which aborts the copy/pasted recipe under the set -euo pipefail the doc itself calls out in §6. Same class as the round-3 foreign_contains fix. Appended `|| true` to all four enumeration greps and added a guard that no `grep --extended-regexp` enumeration line is unguarded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements
docs/superpowers/specs/012-tsugu-local-first-prepare-design.md. Closes #52. Extends 011.Local-first prepare —
push-prepare-branchesdefault flipsyes→no;preparekeeps work on localprepare/*; discovery reads work prefixes local + remote (remote read regardless of push default — for opt-in pushes + leftovers); remote push is a cross-machine agent-collaboration opt-in. Removes the lingering remoteprepare/*that confused cold-start sessions.Human-takeover by containment — a
prepare/<slug>whose tip a non-default, non-work branch contains is taken over (generalizing 011's accepted-prefix slug-pairing to human-named branches; slug-name stays the squash catch). Detection is a fresh, branch-scoped, alias/work-ref-excluding, remote-normalizedgit for-each-ref --contains— git-native, no shipped script.Disposition = suppress-and-surface, never auto-delete — a taken-over branch is suppressed from auto-work and surfaced at
prune/converge; cleanup is local and remote, both human-confirmed (containment can false-positive on a build-on-top branch). Classification is per-ref/per-tip (a stale remote tip never suppresses a newer local in-progress tip).Auto-push invariant —
prepareauto-pushes only the work-prefix branch (when opted in), never accepted/human branches; cross-machine agent-to-agent push deferred.Schema 4 → 5 — fresh
initdefaultspush-prepare-branches: no; the 4→5 migration pins the explicityesinto existing repos (behavior never flips silently).Built subagent-driven (8 implementation tasks, spec+quality review each + a final whole-branch review); content-regression guard
tools/tsugu/test-skill-content.shat 112 anchors; cross-plugin guards pass.🤖 Generated with Claude Code