Backlog Prepare
Prepare the repository for a fresh run of the tars-backlog-loop by verifying the shared git state is uncorrupted, resolving where isolated spoke workspaces will live, proving the verification gate recipe against the current baseline, and cleaning up orphaned workspaces and subagent branches.
Targets and Pre-conditions
- This skill modifies the local git repository state and writes
.tars/run.env(and may create.tars/config.yamlonly if you are documenting a new override - do not invent policy without need). - Run every step in order. Steps 1 through 4 are preconditions; abort the whole preparation if any of them fails rather than continuing with a warning.
- Config vs run.env (strict split):
.tars/config.yaml- project policy (stable across runs). Humans edit this. Prepare reads it; implement does not..tars/run.env- run facts frozen for this prepare. Prepare writes it every run; implement,tars-gate, andtars-spokeread only this file for paths, commands, land template, CI flags, and weaken banners.
Configuration schema (.tars/config.yaml)
Policy keys and skill defaults. Omit any key to keep the default. Prepare merges repo config over these defaults, then freezes concrete values into run.env.
worktree:
spoke_dir: null # or absolute path; overrides TARS_SPOKE_DIR env
sync_markers: [] # extra replicated-folder markers
shared_append_files:
- "project-words.txt"
- "CHANGELOG.md"
transfer_files:
- ".pre-commit-config.yaml"
- ".env*"
- ".tars/"
- "devenv.local.nix"
- "devenv.local.yaml"
concurrency:
heavy_commands:
- "test"
- "coverage"
- "run -a"
- "--all-files"
- "nix build"
# Opaque command overrides (optional). When set, prepare uses them instead of
# auto-detect - still wraps with devenv enter when applicable, still smokes them.
commands:
install: null
hooks: null
test: null
# Optional pre-dispatch ticket linter. See "Ticket lint contract" in step 4c.
# Receives the batch number, runs against the working tree, exits non-zero on
# a batch with errors. Null (the default) means no pre-dispatch check exists.
ticket_lint: null
land:
# {{id}} and {{title}} are substituted by the Hub at merge time.
subject_template: "chore(backlog): land ticket {{id}}"
review:
always_full:
- "hooks/**"
- "**/*secret*"
- "**/auth/**"
# Full dual-axis review when risk: high, post-conflict, rework attempts >= 2,
# or diff touches always_full. Otherwise Hub lightweight checklist.
ci:
# null = auto-detect in prepare. true/false force on/off.
check: null
block_on_red: true
# Optional opaque check command; when null, prepare may set a gh-based default.
command: null
gate:
# If true, prepare may record a weakened test command after classifying
# baseline-red product/coverage failure. Still requires reason in run.env.
allow_weaken: true
Implement never re-opens this file for gate, land, or CI decisions - only the frozen run.env keys.
Preparation Workflow
1. Shared Git Integrity Check
Run this first, before any other git command. A redirected working tree makes
git status,git branch, andgit diffreport on a different directory entirely, so every later check in this skill would be reading someone else's files without saying so.
The backlog pipeline gives each spoke a private clone precisely so that no spoke can write to the parent's git state. That does not make this check redundant. External tools still reach the shared git directory - most notably a file-sync daemon replicating .git/config and .git/hooks/ between machines, which is outside this pipeline's control - and repositories that ran earlier worktree-based versions of these skills may still carry damage.
Detect a redirected working tree:
git config --local --get-all core.worktreeAny value here is damage: it silently redirects every git command in this repository at another directory. Repair it by unsetting the key, then re-run the command to confirm it is gone:
git config --local --unset-all core.worktreeDetect a redirected hooks directory:
git config --local --get core.hooksPathUnset it if it points anywhere outside this repository. Leave it alone if it resolves inside the repository, since that may be a deliberate project setting.
Detect hijacked hook shims: hook installers (
prek,pre-commit,lefthook) bake an absolute path into each shim they write, and.git/hooks/is shared by every linked worktree. A shim installed from anywhere other than the repository root therefore points the whole repository at a foreign config.REPO_ROOT="$(git rev-parse --show-toplevel)" GIT_COMMON="$(git rev-parse --git-common-dir)" # Any absolute path in a shim that does not point inside $REPO_ROOT is suspect. grep -o -- '/[^"''' ]*' "$GIT_COMMON"/hooks/* 2>/dev/null | grep -v "$REPO_ROOT"For each matching shim, read the baked path. If it is not exactly
$REPO_ROOT/.pre-commit-config.yaml, repair it. Write through a temporary file rather than editing in place, becausesed -itakes an argument on BSD/macOS and none on GNU, so the in-place form is not portable:sed 's|--config="[^"]*"|--config="'"$REPO_ROOT"'/.pre-commit-config.yaml"|' \ "$hook" > "$hook.tars-tmp" && mv "$hook.tars-tmp" "$hook" && chmod +x "$hook"Warn on a replicated git directory: a file-sync tool that replicates
.git/config,.git/hooks/, or.git/indexbetween machines can reintroduce exactly the damage repaired above, and no amount of workspace isolation prevents it - the writer is outside this pipeline.Walk up from
$REPO_ROOTlooking for any replicated-directory marker. Mergeworktree.sync_markersfrom.tars/config.yaml(if set) over these defaults:Tool Marker at the folder root Syncthing .stfolderDropbox .dropbox,.dropbox.cacheNextcloud .sync_exclude.lst,._sync_*.dbGoogle Drive .tmp.driveupload,.tmp.drivedownloadiCloud Drive any path under ~/Library/Mobile DocumentsIf a marker is found, check whether that tool is configured to exclude
.git/(for Syncthing, the sibling.stignore; other tools keep their exclude list elsewhere, and may not support one at all). Warn unless.git/is excluded - or at minimum.git/config,.git/hooks/, and.git/index.This is a warning, not an abort, and it finds nothing on a machine that syncs no directories, which is the common case. It is the user's environment, not something this pipeline can fix.
The marker list is a convenience, not a guarantee. The hazard is any external process that mutates the repository behind git's back - a network filesystem, a backup agent that restores files, an editor's remote-sync feature. Treat an unexplained change to shared git state as this class of problem even when no marker matched.
Report every repair made. Abort on anything unrecognised in the shared git state rather than guessing at a fix.
2. Topic Branch Verification
This is the canonical description of the topic-branch policy for the whole backlog pipeline. The other backlog skills (
tars-backlog-loop,tars-backlog-audit,tars-backlog-triage,tars-backlog-implement) point back to this section instead of restating it.
To comply with branch protection policies, all backlog operations must run from a topic branch (e.g., fix/<description>, feat/<description>, chore/<description>, or a branch descriptive of the run) rather than the default branch:
Determine Default Branch: use the first of these that yields a name. They are ordered so the pipeline works on any git host, with no network and no remote at all:
# a. What the remote itself says the default is (any host, works offline once set). git symbolic-ref --quiet --short refs/remotes/origin/HEAD # -> origin/main # b. Ask the remote directly, if there is one and it is reachable. git remote show origin | sed -n 's/.*HEAD branch: //p' # c. This repository's configured default for new branches. git config --get init.defaultBranch # d. Last resort: whichever of these exists locally. git branch --list main master trunkOnly if all of the above fail, and only when the remote is a GitHub one, fall back to
gh repo view --json defaultBranchRef -q .defaultBranchRef.name. Never make the GitHub CLI a hard requirement: it is absent on many machines, useless for GitLab, Gitea, Codeberg, or a bare remote over SSH, and it needs the network.If no method yields a name, ask the user rather than guessing - picking the wrong default branch here means the protection this step exists to provide silently does not apply.
Check Current Branch: Run
git branch --show-currentto identify the active branch.Checkout Topic Branch: If the current branch is the default branch, abort or checkout/create a topic branch before making any modifications or spawning spokes. If the repository is already on a topic branch, proceed on it. Never run backlog operations directly on the default branch.
Spoke Base Branch: All spawned spokes must be branched off this active topic branch.
Merge Target: All approved spoke changes must be merged back into this topic branch.
3. Working Tree Validation
Verify that the git working tree is completely clean (no unstaged changes, no uncommitted files).
- Run
git status --porcelain. - If there are any uncommitted or unstaged changes, you MUST abort the preparation immediately and instruct the user to explicitly commit or stash their changes manually.
- Do not automatically commit or stash changes.
4. Workspace Preflight
Resolve - and record - everything the implementation phase needs to isolate its spokes. Doing this once here, rather than per spoke, means the whole run shares one answer.
4a. Resolve the spoke root
Spoke workspaces must not live inside the repository tree or inside a synced folder. Try each candidate in order and take the first one that passes a real write test. Do not attempt to detect whether you are sandboxed: there are too many confinement mechanisms to enumerate, and a write probe answers the question directly on all of them.
$TARS_SPOKE_DIR, orworktree.spoke_dirfrom.tars/config.yamlif set. An explicit override always wins. This is the injection point for a harness that already knows its writable path, such as an agent runtime that provides a session scratch directory.${XDG_CACHE_HOME:-$HOME/.cache}/tars/spokes/<repo-name>/${TMPDIR:-/tmp}/tars/spokes/<repo-name>/
Each candidate must pass both checks:
# 1. Writable?
probe="$candidate/.tars-write-probe.$$"
mkdir -p "$candidate" 2>/dev/null && : > "$probe" 2>/dev/null && rm -f "$probe"
# 2. Outside any replicated folder? Reuse the marker walk from step 1d,
# this time starting at $candidate rather than at $REPO_ROOT.
Each candidate must also have room. A batch holds up to 5 clones at once, and spokes that build produce artefacts far larger than the source - a single compiled binary can be 100 MB or more, so budget generously rather than for the checkout alone:
df -P "$candidate" | awk 'NR==2 {print $4}' # available 1K-blocks, POSIX on Linux and macOS
Abort with a clear message if free space is short. Running out mid-batch surfaces as an unrelated-looking build or test failure in whichever spoke happens to be writing at the time, and costs a diagnosis; a preflight check costs one command.
Reject a candidate that sits inside a replicated folder and move to the next one. $HOME/.cache is outside any synced tree on most setups, but $HOME itself is replicated on plenty of others - and putting spoke clones there means five live clones being replicated mid-write, which is the hazard step 1d warns about, amplified.
Abort if no candidate passes both.
4b. Choose the clone mode
Local clones hardlink their object store, but only when source and destination share a filesystem. Compare them with df -P, which is POSIX and behaves identically on Linux and macOS (stat does not - it takes -c on GNU and -f on BSD):
df -P "$REPO_ROOT" | awk 'NR==2 {print $1}'
df -P "$SPOKE_ROOT" | awk 'NR==2 {print $1}'
- Same filesystem → clone mode
hardlink: a plaingit clone. Objects are hardlinked, so the clone is nearly free and stays safe even if the parent runsgit gc, because the hardlink keeps any pruned object alive. - Different filesystem → clone mode
shared:git clone --shared. Objects are borrowed through alternates rather than copied, which matters most in a sandbox where/tmpmay be a RAM-backed tmpfs. The cost is that the parent must not rungit gcorgit prunewhile any spoke is alive, because pruning objects a spoke borrows will break it.
4c. Locate runners and verify the lock
Resolve paths inside the installed tars-backlog-implement skill directory:
| Key | Path under skill |
|---|---|
TARS_LOCK |
resources/manual/tars-lock |
TARS_GATE |
resources/manual/tars-gate |
TARS_SPOKE |
resources/manual/tars-spoke |
Always invoke helpers as sh <path> … so missing executable bits do not matter.
Confirm the lock works:
sh "$TARS_LOCK" /tmp/tars-preflight-probe true
Confirm gate and spoke scripts exist and are readable. Do not document platform-specific lock backends to the user - one consistent sh tars-gate / sh tars-spoke surface everywhere.
Ticket lint contract (optional)
The implementation phase can run a pre-dispatch check over a batch before it spawns any spoke. This pipeline does not ship such a checker and does not require one; it defines the contract and resolves the command from configuration, so any project may supply its own.
Resolve commands.ticket_lint from .tars/config.yaml, or $TARS_TICKET_LINT_COMMAND from the environment if set. Never hardcode a particular project's tool here. The command a repository supplies is its own business; the pipeline only knows the contract:
| Aspect | Contract |
|---|---|
| Invocation | Called with the batch number as its argument |
| Working dir | Repository root |
| Reads | The working tree - ticket files under .tars/issues/, as they are on disk right now |
| Not a hook | It must not be wired as a pre-commit hook: .tars/ is gitignored, so a hook would never see the tickets |
| Exit status | 0 when the batch is clean; non-zero when the batch has errors |
Suggested checks for a repo-supplied linter - the batching invariants from the canonical rules in tars-backlog-implement that are mechanically checkable and exactly what a pre-dispatch check exists to catch:
- Size: no more than 5 dispatching tickets share one
batch:number. - Path collision: within a batch, no two tickets share any normalised path across
files:andowns:(strip#Symbolsuffixes before comparing). - Dependency placement: every
dependencies:edge points at a ticket in a strictly earlier batch or already merged - never the same batch, neverfailed//wont-do/(compare IDs numerically; filenames pad, frontmatter does not). - Empty footprint: no dispatching ticket has a missing or empty
files:list unless it is alone in its batch.
A linter that checks none of these passes batches the pipeline will reject anyway; covering them moves the failure from mid-dispatch to pre-dispatch.
Freeze the resolved string into run.env as TARS_TICKET_LINT_COMMAND. When nothing is configured, freeze it as the empty string - implement then skips the check and logs that it skipped, which is the expected case for most repositories.
If a value is configured, smoke it once here (invoke it with a batch number, or with whatever no-op argument it accepts) so a broken or missing command is found now rather than at dispatch time. A linter that is configured but does not run is an error: fix it or clear the config key. Do not silently freeze a command that does not run.
4d. Resolve the repository's own commands (opaque strings)
Determine, once, the three commands every spoke and gate will need. Detect them from what the repository contains - never assume a specific toolchain in implement prose later. Prefer commands.* overrides from .tars/config.yaml when set.
Test command - what the Hub's verification gate runs. Prefer the script the repository itself treats as its full suite (
test:coverage,check,devenv testentry,cargo test,pytest,go test ./...,dotnet test, …).Install command - what a fresh clone needs before it can build or typecheck. Use the lockfile-respecting form when the ecosystem has one (
bun install --frozen-lockfile,npm ci,cargo fetch --locked,uv sync --frozen,go mod download). Empty is valid when there is no install step.Hook command - whole-repo hooks the gate runs before tests:
Repository contains Hook command .pre-commit-config.yaml,prekon PATHprek run -a.pre-commit-config.yaml,pre-commitonly (legacy)pre-commit run --all-fileslefthook.yml/lefthook.yamllefthook run pre-commit --all-files.husky/the script the hook itself runs none of the above empty - the gate runs tests only An empty hook command is a legitimate answer. The gate substitutes
:for empty steps.
Devenv projects: if devenv.nix, devenv.yaml, or devenv/default.nix is present, do not invent enter flags here. Read and follow the devenv skill, then bake its non-interactive enter recipe into the opaque install/hooks/test strings (or a single outer wrapper that those strings already include). Other skills only point at devenv; prepare is the only backlog phase that expands it.
[!IMPORTANT] A devenv command with no secrets reason dies before it starts anything. SecretSpec's
require_reasonpolicy defaults to"agents"even when the key is absent fromsecretspec.toml, so any command entering a devenv shell withoutSECRETSPEC_REASONfails with "Accessing secrets requires a reason".tars-gateevals these frozen strings blind and cannot add the missing variable, so the omission surfaces as a red gate that never ran a test - and it is discovered at gate time, several minutes into a batch, not here.Every frozen string that enters a devenv shell (
TARS_INSTALL_COMMAND,TARS_HOOK_COMMAND,TARS_TEST_COMMAND, andTARS_CI_COMMANDif it enters one) must carry, per the devenv skill's non-interactive shape:CI=true SECRETSPEC_PROVIDER=env SECRETSPEC_ENV=<ci-if-defined-else-default> \ SECRETSPEC_REASON="<why you are running this>" \ devenv --no-tui shell --quiet -- <command>
SECRETSPEC_REASON- mandatory; a short human-readable purpose, e.g."backlog gate: full test suite". There is no default and no way to omit it.SECRETSPEC_PROVIDER=env- no interactive authorization prompt.SECRETSPEC_ENV-ciwhen[profiles.ci]exists insecretspec.toml, elsedefault, else the first defined profile. Omit only when there is nosecretspec.toml.CI=true- match CI's install/auto-dependency behaviour.Freeze the same values into
run.env(TARS_SECRETSPEC_REASON,TARS_SECRETSPEC_PROVIDER,TARS_SECRETSPEC_ENV) so a later reader can see what the opaque strings carry without parsing them. The variables inside the frozen command strings are what actually take effect; therun.envkeys are the record.Before writing any command string, read it back and confirm the reason is present. This is the single most common way a devenv repository produces a gate that is red for a reason unrelated to the code.
Then check for overlap, and subtract it. Hook runners frequently include a hook that already runs the test suite - so a naive gate of <hooks> && <tests> runs the whole suite twice. Inspect the hook config: if a hook already covers typecheck, lint, build, or test, set the test command to only what the hooks do not cover. A common residue is a coverage-threshold run, which a plain test invocation does not enforce.
This matters more than ordinary waste, because the gate runs while holding the mutex. Every second spent re-running an already-green suite is a second no other spoke can test.
Check whether the repository suppresses automatic dependency installation under CI-like env - several devenv setups do. Where it does, the install command is mandatory rather than a convenience.
4e. Land commit template
Resolve land.subject_template from config (default chore(backlog): land ticket {{id}}).
If the repo enforces conventional commits (commitlint config, convco, a commit-msg hook that rejects merge( subjects), keep a conventional template. If there is no commit policy, a plain Land ticket {{id}} is fine.
Detect by reading common config files and hook samples - do not require a network. Freeze the template string into run.env as TARS_LAND_SUBJECT_TEMPLATE. Implement only substitutes {{id}} / {{title}}.
4f. CI check resolution
Resolve whether the Hub must confirm CI after each batch:
- If
ci.checkistrueorfalsein config, honour it. - If
null(default): turn on when a remote exists, the topic branch appears pushable/pushed, and a usable check is available (for exampleghauthenticated against a GitHub remote with Actions, orci.commandset). Otherwise off. - Freeze:
TARS_CI_CHECK=0|1TARS_CI_CHECK_REASON="…"(why on or off)TARS_CI_COMMAND="…"when on (opaque; Hub runs it for the batch head)TARS_CI_BLOCK_ON_RED=0|1fromci.block_on_red(default on)
When off, implement still documents that local green ≠ CI green, but does not block the loop.
4g. Baseline gate smoke (prove the recipe)
Before freezing commands, run the real gate once on the clean topic branch in the parent workspace - the same path hub will use:
Write a draft
.tars/run.envwith the paths and candidate commands (weaken flags off).On a devenv project, check the candidate strings before running anything. Every command that enters a devenv shell must already carry
SECRETSPEC_REASON(plusSECRETSPEC_PROVIDER=env,SECRETSPEC_ENVwhere asecretspec.tomlexists, andCI=true) - see step 4d. A missing reason is red: fix it before proceeding, not something to discover when the gate runs. It costs one read now and a full failed gate cycle later.Export
PRE_COMMIT_HOMEto$TARS_SPOKE_ROOT/hook-cachewhen the hook runner uses it.Run:
sh "$TARS_GATE" "$REPO_ROOT"The runner's last stdout line is a machine-parseable verdict,
TARS_GATE_RESULT=<exit> step=<step> target=<dir> log=<path>. Read the result from that line rather than from anything piped or tailed - green is exactlyTARS_GATE_RESULT=0- and read the named log file for the full output when classifying a red.Classify the result:
Outcome Action Green Freeze commands. TARS_GATE_WEAKENED=0.Red - environment / tooling (missing secrets reason, secrets prompt, missing toolchain, install failure, quoting, devenv enter) Fix or abort. Do not weaken the suite to hide this. A failure naming a missing reason ("Accessing secrets requires a reason") means a frozen string lost its SECRETSPEC_REASON- repair the string, do not work around it. Re-read the devenv skill if the project uses devenv.Red - pre-existing product / coverage / known baseline Prefer narrowing TARS_TEST_COMMANDto the green subset the repo can honestly run. Only ifgate.allow_weakenis true and no useful subset exists, setTARS_TEST_COMMANDto:(or the narrowed command),TARS_GATE_WEAKENED=1, and a preciseTARS_GATE_WEAKENED_REASON. Never write a no-op test command without both weaken keys.Red - ambiguous / flaky Re-run the full gate once. Still red → report output and ask the user rather than auto-weaken. On an ambiguous red, apply the flake policy from tars-backlog-implement (classify the failure shape → isolate the failing test → sole-failure acceptance) before falling back to asking the user. A flaky baseline should cost one classification pass, not a stalled prepare.
Re-write the final
.tars/run.envwith the frozen values. If the smoke left the working tree dirty (hook autofix), restore cleanliness before finishing prepare: the tree must be clean at the end of prepare (git status --porcelainempty). Prefer restoring autofixes withgit checkout -- ./git cleanonly for smoke dirt you caused; if unsure, abort and ask the user.
Report the smoke outcome and any weaken banner prominently in the prepare summary.
4h. Record the resolved values
Write the results to .tars/run.env in the parent workspace:
TARS_SPOKE_ROOT="…"
TARS_CLONE_MODE="hardlink" # or "shared"
TARS_LOCK="…/resources/manual/tars-lock"
TARS_GATE="…/resources/manual/tars-gate"
TARS_SPOKE="…/resources/manual/tars-spoke"
TARS_HEAVY_LOCK="…/locks/<repo-name>.heavy"
TARS_TOPIC_BRANCH="…"
TARS_PRE_COMMIT_HOME="…/hook-cache" # may be empty if unused
TARS_INSTALL_COMMAND="…" # opaque; may be :
TARS_HOOK_COMMAND="…" # opaque; may be :
TARS_TEST_COMMAND="…" # opaque; may be : only if weakened
# The PATH the baseline smoke gate ran (and went green) under. tars-gate and
# tars-spoke restore it before eval'ing the opaque commands, so the frozen
# strings resolve the same binaries in every later session. Frozen commands
# without their frozen environment are only half a freeze.
TARS_PATH="$PATH"
# Staleness stamp. Implement refuses to run when .tars/config.yaml no longer
# matches the fingerprint it was frozen from - a run.env that predates a
# config change is silently missing policy (existence is the wrong test).
# Use `cksum` for the fingerprint: it is POSIX, unlike stat -c / stat -f.
TARS_FROZEN_AT="…" # date -u +%Y-%m-%dT%H:%M:%SZ at freeze time
TARS_CONFIG_FINGERPRINT="…" # `cksum .tars/config.yaml` output; empty when no config file
# Secrets context baked INTO the three opaque strings above on a devenv project.
# Recorded here so a reader can see what they carry without parsing them; the
# copies inside the command strings are what actually take effect.
# A devenv command with no reason fails before it runs anything - see step 4d.
TARS_SECRETSPEC_REASON="backlog gate: …" # mandatory on devenv; never empty
TARS_SECRETSPEC_PROVIDER="env" # non-interactive; no authorization prompt
TARS_SECRETSPEC_ENV="ci" # or "default"/first profile; empty if no secretspec.toml
# Optional pre-dispatch ticket check. Empty = none configured; implement then
# skips the check and LOGS that it skipped. Invoked with the batch number,
# run against the working tree, non-zero exit = batch has errors.
TARS_TICKET_LINT_COMMAND=""
TARS_GATE_WEAKENED=0 # or 1
TARS_GATE_WEAKENED_REASON="" # required when weakened
# Tests the Hub has accepted as flaky THIS run (comma-separated). Prepare
# always freezes this empty; the Hub appends on each sole-failure flake
# acceptance - the one key it may update after prepare - so downstream
# gates and spokes recognise the flake instead of re-diagnosing it. Rewritten
# empty on every prepare, so a flake never outlives the run that observed it.
TARS_KNOWN_FLAKES=""
TARS_LAND_SUBJECT_TEMPLATE="chore(backlog): land ticket {{id}}"
TARS_CI_CHECK=0 # or 1
TARS_CI_CHECK_REASON="…"
TARS_CI_COMMAND="" # opaque when CI check on
TARS_CI_BLOCK_ON_RED=1
TARS_HEAVY_COMMANDS="test,coverage,run -a,--all-files,nix build"
# Comma-separated globs from review.always_full (frozen so implement never opens config)
TARS_REVIEW_ALWAYS_FULL="hooks/**,**/*secret*,**/auth/**"
Write these to disk rather than only holding them in context. A backlog run is long enough that the Hub's context may be compacted partway through, and a Hub that has forgotten where its spoke root is will resolve a different one mid-run. .tars/ is already shared into every spoke workspace, so spokes can read the same file.
5. Clean Up Orphaned Workspaces
Legacy worktrees: earlier versions of these skills gave spokes git worktrees inside or beside the repository. Remove any that remain, which also migrates a repository off the old model:
git worktree list --porcelain git worktree remove --force <path> # for each spoke worktree git worktree pruneRemoving a worktree does not delete its branch, so no spoke work is lost here - branch cleanup is sub-step 3 below, and it protects rework branches explicitly.
Stale spoke clones: delete any directory under
$TARS_SPOKE_ROOTleft behind by a previous run. Every clone is disposable scratch; spoke work durably lives on branches in the parent repository, so nothing is lost. Confirm each directory is a clone of this repository before deleting it, and never delete the spoke root itself.Leftover branches: force-delete all local branches matching
subagent-*, EXCEPT those currently referenced by active rework tickets in.tars/issues/todo/.- Scan
.tars/issues/todo/*.mdfiles to extract thebranchfield from the frontmatter. - List all subagent branches:
git branch --list 'subagent-*' - Force-delete only the branches that are NOT referenced in the active rework list:
git branch -D <branch-name>
- Scan
Once these steps are complete and verified, the repository is ready for the tars-backlog-loop skill. If TARS_GATE_WEAKENED=1, the prepare report must end with a known-residual banner quoting TARS_GATE_WEAKENED_REASON.
Portable Command Baseline
Every command this pipeline emits runs on both Linux and macOS. Three GNU-only idioms are easy to reach for and silently misbehave on BSD userland:
sed -i- takes a mandatory suffix argument on BSD/macOS. Write through a temporary file andmvit into place instead.readlink -f- GNU-only. Userealpath, or resolve the path withcdandpwd -P.stat -c- GNU spelling; BSD uses-f. For filesystem identity usedf -Pinstead.
Related Skills
- devenv - when the project has
devenv.nix/devenv.yaml, follow it to build non-interactive enter commands during step 4d. - tars-backlog-implement - consumes
run.envand runstars-gate/tars-spoke.