[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6K7VP2QDRNX57KP2RH10FAW":3,"skill-same:01M6K7VP2QDRNX57KP2RH10FAW":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":51,"ownerHtmlUrl":52,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":53,"install":54,"resources":67},"01M6K7VP2QDRNX57KP2RH10FAW","ship-it",null,"Wrap up a finished round of work — verify README and CHANGELOG are current, cut a version if warranted, move completed task files into the completed\u002F folder, then create a feature branch if needed, commit, push, and open a PR. Run after `\u002Ffsad-harness:do-task` has marked one or more tasks done. Takes no arguments.","tbeack\u002Ffsad_playbook","tbeack","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F206807984?v=4","01M6K7Q3C3VRTCVC55T71A1SY9","Theo Beack","01M6K7Q3GDY8TXRQEH984SEE5W",8,"MIT","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fship-it\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"ship","it","ship it","up","parse","ls","remote","move","checkout","done","files","log","commit","fetch","branch","work","gh pr","Bash","open","create","tasks","2026-11-10 10:12:30",false,"---\nallowed-tools: Bash(git worktree:*), Bash(git -C:*), Bash(git merge:*), Bash(git merge-base:*), Bash(git add:*), Bash(git commit:*), Bash(git branch:*), Bash(git mv:*), Bash(git rev-parse:*), Bash(git checkout:*), Bash(git push:*), Bash(git pull:*), Bash(git status:*), Bash(git log:*), Bash(git remote:*), Bash(git ls-remote:*), Bash(gh pr create:*), Bash(gh pr view:*), Bash(gh pr list:*), Bash(git fetch:*), Bash(git tag:*), Bash(git diff:*), Bash(date:*)\ndescription: Wrap up a finished round of work — verify README and CHANGELOG are current, cut a version if warranted, move completed task files into the completed\u002F folder, then create a feature branch if needed, commit, push, and open a PR. Run after `\u002Ffsad-harness:do-task` has marked one or more tasks done. Takes no arguments.\n---\n\n# fsad-harness:ship-it — wrap up and ship\n\nYou run five sequential phases to close out a batch of completed work: pre-merge branch guard (Step 0.4), worktree merge (Step 0.5), pre-flight audits — README\u002FCHANGELOG\u002Fversion\u002Fcompleted-task cleanup (Step 1), commit + push + PR (Step 2), and verify-shipped (Step 3). Do not skip phases or reorder them.\n\n\"Shipped\" means all three of the following verifiable predicates hold — not that a phase list was walked:\n\n1. **Remote-HEAD parity** — the pushed branch's remote SHA (`git ls-remote`) equals local `HEAD`.\n2. **PR existence** — `gh pr view` confirms a PR exists for this branch and targets the default branch.\n3. **Version-string consistency** — every file in `cfg.version_files` carries the identical new version string (when a version was cut).\n\nStep 3 (Verify shipped) checks all three explicitly before the skill reports success — see that step for details.\n\n## Asking the user\n\nThis rule covers every question this skill asks the user: a confirmation, a choice, a missing value, a numbered decision list, or a stop-and-ask. It applies even where a later step says \"ask the user\" or \"ask exactly once\". The user may watch the decisions pane rather than the chat, so a question that only goes to chat can go unanswered.\n\n- **When `mcp__decision-tracker__decision` is in your tool list** (loaded, or named as a deferred tool; load a deferred one with `ToolSearch` first), log each question before you ask it:\n  1. Call it with `action: \"open\"`, the question (ending in `?`), and `options` when the answers are a closed set. Set `required: false` when the work can go on without an answer. A numbered list of questions opens one decision per item. Keep each returned `id`.\n  2. Ask the question in chat as this skill says. The open call does not replace the chat question.\n  3. When the user answers, in chat or as a `Decision Dn: \u003Canswer>` message from the plugin, call the tool with `action: \"close\"`, the `id` and the answer.\n  4. When the change the answer asked for is in place, call it with `action: \"implement\"`, the `id` and a `path:line`.\n- **When the tool is absent**, ask in chat only.\n- An `AskUserQuestion` call is logged on its own. Do not open it again.\n- This rule changes where a question is logged. It does not change when this skill asks, or how many times.\n\n## Step 0 — Detect the project\n\n1. Determine the current working directory.\n2. Read `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fadd-task\u002Fadd-task-projects.yaml`.\n3. Match cwd against each project's `match_paths` (expand `~`). Prefer the longest (most specific) match if multiple match.\n4. If a project matches, refer to its entry as `cfg` and the resolved project root as `project_root`. Proceed.\n5. If **no project matches**:\n   - Tell the user the project is not registered.\n   - Suggest running `\u002Ffsad-harness:add-task` from the project to register it.\n   - Stop.\n\n## Step 0.2 — Skill routing\n\nResolve which skill should actually ship this work for `cfg`'s project:\n\n1. `desired_skill` = `cfg.skills.ship` (if set) → else the YAML's top-level `defaults.skills.ship` (if set) → else `\"fsad-harness:ship-it\"`.\n2. If `desired_skill` differs from `\"fsad-harness:ship-it\"`: invoke the `Skill` tool with that skill name, passing `$ARGUMENTS` unchanged, and **stop** — none of this skill's remaining steps run in this invocation.\n3. Otherwise continue to Step 0.4.\n\n## Step 0.4 — Pre-merge branch guard\n\nStep 0.5 merges worktree branches into whatever branch is currently checked out, with no branch awareness of its own. If that happens to be `main`\u002F`master`, the merge commit lands directly on the default branch — before Step 2.0's branch guard ever runs. This step closes that gap by moving off the default branch first, whenever a merge is about to happen.\n\n1. Run `git worktree list --porcelain` from `project_root`. Parse the output to get all worktrees (each block starts with `worktree \u003Cpath>`), then filter out the main worktree — the one whose path equals `project_root`.\n2. If **no additional worktrees** exist: skip this step entirely and proceed to Step 0.5 (which will independently reach the same conclusion and pass through to Step 1).\n3. If additional worktrees exist, check the current branch: `git rev-parse --abbrev-ref HEAD`.\n   - **Not `main`\u002F`master`**: already off the default branch — proceed to Step 0.5.\n   - **Is `main`\u002F`master`**: a merge is about to happen and must not land here.\n     - Propose a branch name that does **not** depend on a version string (the version isn't cut until Step 1b):\n       - If the worktree branch names encode task IDs (e.g. `worktree-task-tbs-070` → `tbs-070`), suggest `task\u002F{task-ids}` — joining multiple IDs with `+` when merging more than one worktree (e.g. `task\u002Ftbs-070+tbs-071`).\n       - Otherwise, suggest `ship\u002F{YYYY-MM-DD}` using today's date (`date +%F`).\n     - Ask the user: \"About to merge {N} worktree branch(es) while on `{main|master}` — create and switch to `{branch}` first? (y\u002Fn or type a different name)\"\n     - On confirmation: run `git checkout -b {branch}`.\n     - If declined: stop here. Do not proceed to Step 0.5 — merging while on the default branch is the exact failure this step exists to prevent.\n4. Proceed to Step 0.5.\n\n## Step 0.5 — Merge pending worktrees\n\nThis step must never run while `HEAD` is the default branch (`main`\u002F`master`) — Step 0.4 ensures that by branching off first whenever a merge is pending. The merge itself cannot simply be deferred to run *after* Step 2.0's branch guard instead: Step 1's audits (README\u002FCHANGELOG\u002Fcompleted-task checks) read the post-merge tree, so the merge must still happen before those audits — just off the default branch rather than on it.\n\n**Main-tree writeback reconciliation.** `fsad-harness:do-task` (Step 5h) and `fsad-harness:add-task` write to the *main* tree even when their session runs inside a worktree: `todo.md` gets `[x]` marks and new bullets, and a task-detail file may be edited in place. At merge time these main-tree edits are therefore **expected to be uncommitted** — they are not a sign of a dirty tree that must be cleaned first. Meanwhile the worktree branch may carry new task-detail files whose bullets already sit in the main-tree `todo.md`, or a `todo.md` edit of its own. Reconcile as follows:\n\n- **Leave the main-tree `todo.md` \u002F task-file edits in the working tree** — do not stash, discard, or \"clean up\" them before merging. They are staged by name together with the merge result in Step 2.2 and land in the same commit batch.\n- If `git merge` refuses to start because a file the branch touches is also modified in the main tree (typically `todo.md`), commit the main-tree edit first — `git add \"{cfg.todo_file}\" && git commit -m \"todo: mark {IDs} done\"` — then re-run the merge. Committing, not stashing, is the only accepted way to clear this.\n- **`todo.md` conflict → keep both sides.** When the merge conflicts in `todo.md`, the correct resolution is almost always the union: keep every bullet from both sides, deduplicate any bullet that appears on both (keeping the `- [x]` form if either side has it), and order the bullets by task ID. Never drop a bullet to make the conflict go away.\n- A task-detail file arriving from the branch whose bullet is already in the main-tree `todo.md` is the normal case, not a conflict — nothing to reconcile.\n\n1. Run `git worktree list --porcelain` from `project_root`. Parse the output to get all worktrees (each block starts with `worktree \u003Cpath>`).\n2. Filter out the main worktree — the one whose path equals `project_root`.\n3. If **no additional worktrees** exist: skip this step entirely and proceed to Step 1.\n4. If additional worktrees exist:\n   - List them for the user: path and branch name (the `branch` field from the porcelain output), plus each branch's uncommitted-changes status (`git -C \u003Cpath> status --porcelain`, summarized as \"clean\" or \"N uncommitted files\").\n   - **Stop and ask for explicit confirmation** before touching anything:\n     > \"About to merge {N} worktree branch(es) into `{project_root}`: {list}. For each: commit any uncommitted changes, `git merge --no-ff \u003Cbranch>`, verify the merge landed, then force-remove the worktree and delete the branch. Proceed? (y\u002Fn)\"\n   - If the user declines: stop here. Do not modify or remove any worktree. Report which worktrees remain pending.\n   - On confirmation, for each worktree in order:\n     a. Check for uncommitted changes: `git -C \u003Cpath> status --porcelain`. If any are present, commit them: `git -C \u003Cpath> add -A && git -C \u003Cpath> commit -m \"\u003Cbranch-name>: parallel do-task changes\"`.\n     b. Merge the branch into the main worktree: `git merge --no-ff \u003Cbranch>`.\n     c. If the merge produces a conflict: **stop immediately**. Tell the user which file(s) conflict and provide these resolution steps:\n        1. Open each conflicting file and locate the conflict markers (`\u003C\u003C\u003C\u003C\u003C\u003C\u003C`, `=======`, `>>>>>>>`).\n        2. Edit the file to keep the correct content and remove all conflict markers. For `{cfg.todo_file}`, apply the keep-both rule above: union of both sides' bullets, `- [x]` wins over `- [ ]` for the same ID, sorted by task ID.\n        3. Stage each resolved file: `git add \u003Cfile>`.\n        4. Complete the merge commit: `git commit`.\n        5. Re-run `fsad-harness:ship-it` to continue from where it left off.\n        Do not proceed to Step 1. Do not remove the worktree — it may still hold the only copy of unmerged work.\n     d. **Verify the merge landed before removing anything**: run `git merge-base --is-ancestor \u003Cbranch> HEAD`. Exit code `0` means the branch tip is now reachable from `HEAD` — the merge is confirmed in the main tree.\n        - If this check **fails** (non-zero exit): **stop immediately**. Do not remove the worktree or delete the branch — the merge did not land as expected and the worktree is the only safe copy of that work. Report the branch name and ask the user how to proceed.\n        - If this check **passes**: only now remove the worktree (`git worktree remove --force \u003Cpath>`) and delete the branch (`git branch -d \u003Cbranch>`).\n5. After all worktrees are merged, verified, and removed: tell the user \"Worktrees merged.\" and proceed to Step 1.\n\n## Step 1 — Pre-flight audits (run concurrently)\n\n### 1.0 — Does versioning apply to this project?\n\nDecide this **before** dispatching the audits, because it determines whether Audit B and Step 1b run at all. Set `versioning = off` if **either** of the following holds:\n\n- `cfg.version_scheme` is explicitly `none` (or the YAML null forms `null` \u002F `~`). This is different from the key being **absent** — an absent key still runs Audit B and falls through to the \"No `version_scheme` configured\" prompt in 1b.\n- The CHANGELOG file does not exist on disk. Resolve `changelog_path` = `{project_root}\u002F{cfg.changelog_file}` if `cfg.changelog_file` is set, else `{project_root}\u002FCHANGELOG.md`; every later reference to the CHANGELOG in this skill means this path.\n\nWhen `versioning = off`:\n\n- Tell the user **once**, naming the reason that applied: \"Versioning skipped for this project — `version_scheme: none`.\" or \"Versioning skipped for this project — no CHANGELOG at `{changelog_path}`.\" Do not mention it again in later steps.\n- **Skip Audit B and Step 1b entirely.** Run only Audits A, C, and D.\n- **Do not create a CHANGELOG, do not invent or propose a version, and do not touch `cfg.version_files`.** A project without versioning ships as a plain commit + PR; Step 2.0 then names the branch `task\u002F…` or `ship\u002F…`, Step 2.1 item 1 and item 3 are skipped, and Step 3 predicate 3 is not applicable.\n\nOtherwise (`versioning = on`) run all four audits below.\n\nThese checks are read-only and independent of each other — none needs another's output. Dispatch them as concurrent subagents (or, if subagents aren't available in the current context, run the read passes back-to-back with no dependency ordering between them) rather than sequentially:\n\n- **Audit A — README ↔ skills-table consistency:** List all subdirectories in `{project_root}\u002Fskills\u002F` (each is a skill). Read `{project_root}\u002FREADME.md`. For every skill directory, check a corresponding row exists in the README skills table (match on directory name). Report any skills missing from the README.\n- **Audit B — CHANGELOG\u002Fversion derivation:** Read `{changelog_path}`. Find the `## [Unreleased]` section and collect everything between that heading and the next `## [` heading (the pending content). Read `cfg.version_scheme` and `cfg.version_files`; if set, read the first file in the list and extract the current version string (JSON `\"version\"` field, README version-table row, or HTML `\u003Ctitle>`). Report: pending CHANGELOG content (or \"empty\"), current version scheme, and current version string.\n- **Audit C — completed-task cross-reference:** Scan `{project_root}\u002F{cfg.task_dir}\u002F` for files matching the task filename pattern (e.g. `task-tbs-NNN.md`) not already inside `completed\u002F`. For each, look up the corresponding identifier in `cfg.todo_file`: `- [x]` → candidate to move; `- [ ]` or not found → leave in place. For each candidate, also list its AC status file (the same path with `.md` replaced by `.acs.json`) when one exists, so it moves with the task file. Report the candidate list.\n- **Audit D — skill frontmatter parse:** If `{project_root}\u002Fscripts\u002Fcheck-frontmatter.sh` exists, run it with no arguments and report its `BAD:` lines and exit code. If the script does not exist, report \"not applicable\".\n\nOnce the audits report back, apply their results serially (writes must stay serial even though the reads were concurrent):\n\n### 1a — README check\n\n- If Audit A found missing skills: tell the user which ones (e.g. `ship-it`, `next`). Ask them to update the README before continuing. Stop — do not proceed until the user confirms it's done and re-invokes.\n- If clean: tell the user \"README check passed.\" and proceed.\n\n### 1a.5 — Frontmatter check\n\n- If Audit D reported a non-zero exit: show the user its output (`BAD:` lines, or \"ruby not found\" on exit 2). A bad frontmatter block makes Claude Code drop the skill's `description`. Ask them to fix it (usually: quote the value). Stop — do not proceed until the user confirms it's done and re-invokes.\n- If exit 0: tell the user \"Frontmatter check passed.\" and proceed. If \"not applicable\", proceed silently.\n\n### 1b — CHANGELOG + version\n\nSkip this whole sub-step when Step 1.0 set `versioning = off` — go straight to 1c.\n\n1. If Audit B's pending content is **empty** (no bullets\u002Fsubsections):\n   - Tell the user: \"The [Unreleased] section has no content — there may be nothing to release. Proceed anyway? (y\u002Fn)\"\n   - No → stop. Yes → skip the version-bump sub-steps and go to 1c.\n2. If Audit B's pending content is **non-empty**:\n   - Show the user the pending content.\n   - **Propose next version** based on `cfg.version_scheme` (from Audit B):\n     - `semver`: bump the **patch** component by default (e.g., `v3.2.0 → v3.2.1`). Tell the user they can type `minor` or `major` for a different bump level.\n     - `integer`: increment the integer suffix by 1 (e.g., `v36 → v37`).\n     - `calver`: use today's date in `YYYY.MM.DD` format.\n     - **No `version_scheme` configured:** ask \"Cut a new version? If yes, what tag? (e.g. v4)\" and use whatever they provide.\n   - **Confirm:** Present \"Suggested version: **[tag]** — confirm, type a different tag, or type `no` to skip.\"\n     - `no`\u002Fskip → proceed to 1c without modifying CHANGELOG or version files.\n     - `minor`\u002F`major` (semver only) → recompute at that level and re-present.\n     - Confirmed\u002Fdifferent tag → use that tag and continue.\n   - **Version-collision check** — the confirmed tag must not already be claimed by work that hasn't shipped yet. Run `git fetch origin --tags --prune` once, then inspect **all three** of these sources and report what each found:\n     1. **Tags:** `git tag --list \"{tag}\"` — non-empty output is a collision.\n     2. **Remote release branches:** `git ls-remote --heads origin \"release\u002F*\"` — any branch named `release\u002F{tag}` (other than the branch currently checked out, if it already carries that name) is a collision: someone cut this version on another branch that hasn't merged.\n     3. **Uncommitted CHANGELOG diff:** `git diff HEAD -- \"{changelog_path}\"` — an added line matching `## [{tag}]` is a collision: a prior, unfinished ship attempt (or another session) already wrote this version into the working tree.\n     If all three are clean, continue to **Write version files**. On any collision, name the source(s) and what they contain, then ask the user to choose exactly one of two outcomes:\n     - **Renumber** — bump once more at the same level (`vX.Y.Z` → `vX.Y.(Z+1)` for a patch, etc.), re-present the new tag, and re-run this collision check on it. If three consecutive candidates collide, stop and treat it as Abort — something other than a stale tag is going on.\n     - **Abort** — stop `fsad-harness:ship-it` here with no files written; the user resolves the conflicting source (deletes the stale tag\u002Fbranch, finishes or reverts the WIP CHANGELOG edit) and re-invokes.\n     Do **not** stash the user's working-tree changes to get past a collision, and never stash silently at any point in this skill: the stash stack is shared by every worktree of the repo, so a stash made here hides WIP from the session that owns it. If the user explicitly asks to set a change aside, the only acceptable form is a tagged, untracked-inclusive one — `git stash push -u -m \"fsad-harness:ship-it: {reason}\"` — announced before it runs and listed in the handoff.\n   - **Write version files:** update every file in `cfg.version_files` with the new version string, preserving each file's existing format (JSON `\"version\"` field, README table row, HTML `\u003Ctitle>`).\n   - **Update CHANGELOG:** replace `## [Unreleased]` with `## [vN] — YYYY-MM-DD` (today's date), insert a fresh empty `## [Unreleased]` block above it, write the file. Tell the user \"CHANGELOG updated — [vN] cut.\"\n\n### 1c — Move completed task files\n\n- If Audit C found no candidates: tell the user \"No completed task files to move.\" and proceed to Step 2.\n- If candidates exist: tell the user \"Moving these to `completed\u002F`:\" followed by the list, then `git mv` each file into `{project_root}\u002F{cfg.task_dir}\u002Fcompleted\u002F`. A task file's `.acs.json` status file moves in the same step, never left behind. If it is untracked, `git add` it first so `git mv` can move it.\n\n## Step 2 — Commit, push, PR\n\n### 2.0 — Branch guard\n\n1. Run `git rev-parse --abbrev-ref HEAD` to get the current branch name.\n2. If the branch is `main` or `master`:\n   - Propose a feature branch name:\n     - If a version was cut in Step 1: suggest `release\u002F{version}` (e.g. `release\u002Fv4`).\n     - Otherwise: suggest `task\u002F{task-ids}` using any task IDs marked done during this session (e.g. `task\u002Ftbs-007`), or `ship\u002F{YYYY-MM-DD}` if no IDs are in scope.\n   - Ask the user: \"Create and switch to `{branch}`? (y\u002Fn or type a different name)\"\n   - On confirmation: run `git checkout -b {branch}`.\n3. If already on a non-default branch:\n   - Tell the user \"Already on branch `{branch}`.\"\n   - Determine whether the branch needs renaming to `release\u002F{version}` — this applies in two cases:\n     - **Provisional branch**: `{branch}` matches `^ship\u002F` or `^task\u002F` (a branch Step 0.4 created to hold a pending worktree merge off `main`), and a version was cut in Step 1.\n     - **Stale release branch**: `{branch}` matches `release\u002FvX.Y.Z`, a version was cut in Step 1, and `vX.Y.Z` does not equal the newly cut version.\n   - If either case applies:\n     - Propose renaming: \"Branch name `{branch}` should be `release\u002F{version}` — rename? (y\u002Fn)\"\n     - If yes, reuse the same rename procedure for both cases:\n       a. `git branch -m {branch} release\u002F{version}` — rename locally.\n       b. `git push origin release\u002F{version}` — push the new name.\n       c. `git push origin --delete {branch}` — delete the old remote branch (ignore error if it didn't exist remotely).\n       d. `git branch -u origin\u002Frelease\u002F{version}` — update tracking ref.\n       e. Tell the user \"Branch renamed to `release\u002F{version}`.\"\n     - If no: continue on the current branch name.\n   - Otherwise: continue without renaming.\n4. **Main-cleanliness assertion**: run `git fetch origin main` then compare `git rev-parse main` against `git rev-parse origin\u002Fmain`.\n   - **Equal**: proceed to Step 2.1.\n   - **Not equal**: stop and report. Local `main` has diverged from `origin\u002Fmain` — most likely a merge commit landed directly on `main` (the failure this guard exists to catch). Remedy: `git branch -f main origin\u002Fmain` to reset local `main` to match remote, then re-run `fsad-harness:ship-it`. Do not proceed to Step 2.1 until this passes.\n\n### 2.1 — Independent pre-PR checklist verifier\n\nBefore committing, run one more fresh, independent read pass over the three Pre-PR Checklist items (README, CHANGELOG, version files — see `CLAUDE.md`'s Pre-PR Checklist section if present). This pass must **re-read the files from disk**, not reuse conclusions from Step 1 — the point is to catch a mistake the same pass that wrote them could have missed:\n\n1. Re-read `{project_root}\u002FCHANGELOG.md`: confirm the change has an entry either under `## [Unreleased]` or in a freshly cut version block. (Skip when Step 1.0 set `versioning = off` — there is no CHANGELOG to read and none should be created.)\n2. Re-read `{project_root}\u002FREADME.md`: confirm the version table's **Current version** and **Date** match what's being shipped (if a version was cut).\n3. Re-read every file in `cfg.version_files`: confirm all carry the identical new version string (if a version was cut; skip when `versioning = off`).\n4. If any mismatch is found: report it to the user and stop — do not commit with a known Pre-PR Checklist violation. Ask whether to fix it now or abort.\n5. If every applicable item checks out: tell the user \"Pre-PR checklist verified independently.\" and proceed.\n\n### 2.2 — Commit\n\n1. Run `git status` to see what's staged and unstaged. Stage all relevant changed files by name (do **not** use `git add -A` or `git add .`):\n   - Modified: `CHANGELOG.md`, `README.md`, todo file (including the main-tree `[x]` marks and bullets written back by `fsad-harness:do-task`\u002F`fsad-harness:add-task` — see Step 0.5), any other files changed during this work session.\n   - Moved task files (already staged by `git mv`).\n2. Run `git log --oneline -5` to read recent commit message style.\n3. Draft a commit message:\n   - If a version was cut in Step 1: use `release [vN] — \u003Cone-line summary of what's in the release>`.\n   - If no version was cut: summarize the batch of completed work in one line.\n   - Append `Co-Authored-By:` with the **current model's** name and provider, derived at runtime from the executing session (e.g. the model identifying itself in this conversation) — never hardcode a specific model name\u002Fversion in the skill file itself, since that goes stale every time the underlying model changes.\n   - Pass via heredoc to avoid shell quoting issues.\n4. Show the user the draft commit message and ask for confirmation before committing.\n5. On confirmation: create the commit.\n\n### 2.3 — Push, with verify-and-retry\n\n1. Record the local HEAD SHA: `git rev-parse HEAD`.\n2. Push: `git push -u origin HEAD`.\n3. **Verify the push landed**: `git ls-remote origin {branch}` and compare the remote SHA to the local HEAD SHA from step 1.\n   - **Match**: push confirmed, continue to 2.4.\n   - **Mismatch or push failed**: retry, capped at 2–3 attempts:\n     a. If the remote moved ahead (someone else pushed): `git pull --rebase` then re-push.\n     b. If the push was simply rejected\u002Ffailed transiently: re-push directly.\n     c. After each retry, re-check `git ls-remote` against the (possibly updated) local HEAD.\n   - If still mismatched after the retry cap: **stop and ask the user** how to proceed. Do not silently continue as if the push succeeded.\n\n### 2.4 — Create PR, with verify-and-fallback\n\n1. Detect the default branch: `git remote show origin | grep 'HEAD branch' | awk '{print $NF}'` (typically `main` or `master`).\n2. Create PR with `gh pr create`:\n   - Title: same one-liner used in the commit.\n   - Include `--base {default_branch}` so the PR always targets the default branch.\n   - Body (heredoc):\n     ```\n     ## Summary\n     \u003CCHANGELOG content for this version, or a bullet-list summary if no version was cut>\n\n     ## Test plan\n     - [ ] Skill files deployed and appear in `\u002Ffsad-harness:*` command list\n     - [ ] README skills table is accurate\n     - [ ] CHANGELOG entry is present and correctly formatted\n\n     ? Generated with [Claude Code](https:\u002F\u002Fclaude.com\u002Fclaude-code)\n     ```\n3. **Verify the PR actually exists** — do not trust `gh pr create`'s exit code alone: run `gh pr view --json url,state,baseRefName` for the current branch.\n   - If it returns a PR with the expected base branch: confirmed. Record the URL.\n   - If `gh pr create` reported failure because a PR already exists for this branch: run `gh pr list --head {branch} --json url,state,baseRefName` to find the existing PR and report it instead of treating this as an error.\n   - If neither check turns up a PR: stop and tell the user PR creation could not be confirmed — do not report a PR URL that wasn't actually verified.\n4. Proceed to Step 3 (Verify shipped) once the PR is confirmed.\n\n## Step 3 — Verify shipped\n\nConfirm the three \"shipped\" predicates from the top of this document, using fresh checks (not assumptions carried over from Step 2):\n\n1. **Remote-HEAD parity**: `git ls-remote origin {branch}` — the returned SHA must equal `git rev-parse HEAD`. PASS\u002FFAIL.\n2. **PR existence**: `gh pr view --json url,state,baseRefName` — must return a PR targeting the default branch. PASS\u002FFAIL.\n3. **Version-string consistency** (only if a version was cut in Step 1; N\u002FA when Step 1.0 set `versioning = off`): re-read every file in `cfg.version_files` and confirm they all carry the identical new version string. PASS\u002FFAIL.\n\nReport all three verdicts to the user. If any predicate fails, say so explicitly — do not report the work as \"shipped\" on a partial result. Only report success when all applicable predicates pass.\n","# fsad-harness:ship-it — wrap up and ship\n\nYou run five sequential phases to close out a batch of completed work: pre-merge branch guard (Step 0.4), worktree merge (Step 0.5), pre-flight audits — README\u002FCHANGELOG\u002Fversion\u002Fcompleted-task cleanup (Step 1), commit + push + PR (Step 2), and verify-shipped (Step 3). Do not skip phases or reorder them.\n\n\"Shipped\" means all three of the following verifiable predicates hold — not that a phase list was walked:\n\n1. **Remote-HEAD parity** — the pushed branch's remote SHA (`git ls-remote`) equals local `HEAD`.\n2. **PR existence** — `gh pr view` confirms a PR exists for this branch and targets the default branch.\n3. **Version-string consistency** — every file in `cfg.version_files` carries the identical new version string (when a version was cut).\n\nStep 3 (Verify shipped) checks all three explicitly before the skill reports success — see that step for details.\n\n## Asking the user\n\nThis rule covers every question this skill asks the user: a confirmation, a choice, a missing value, a numbered decision list, or a stop-and-ask. It applies even where a later step says \"ask the user\" or \"ask exactly once\". The user may watch the decisions pane rather than the chat, so a question that only goes to chat can go unanswered.\n\n- **When `mcp__decision-tracker__decision` is in your tool list** (loaded, or named as a deferred tool; load a deferred one with `ToolSearch` first), log each question before you ask it:\n  1. Call it with `action: \"open\"`, the question (ending in `?`), and `options` when the answers are a closed set. Set `required: false` when the work can go on without an answer. A numbered list of questions opens one decision per item. Keep each returned `id`.\n  2. Ask the question in chat as this skill says. The open call does not replace the chat question.\n  3. When the user answers, in chat or as a `Decision Dn: \u003Canswer>` message from the plugin, call the tool with `action: \"close\"`, the `id` and the answer.\n  4. When the change the answer asked for is in place, call it with `action: \"implement\"`, the `id` and a `path:line`.\n- **When the tool is absent**, ask in chat only.\n- An `AskUserQuestion` call is logged on its own. Do not open it again.\n- This rule changes where a question is logged. It does not change when this skill asks, or how many times.\n\n## Step 0 — Detect the project\n\n1. Determine the current working directory.\n2. Read `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fadd-task\u002Fadd-task-projects.yaml`.\n3. Match cwd against each project's `match_paths` (expand `~`). Prefer the longest (most specific) match if multiple match.\n4. If a project matches, refer to its entry as `cfg` and the resolved project root as `project_root`. Proceed.\n5. If **no project matches**:\n   - Tell the user the project is not registered.\n   - Suggest running `\u002Ffsad-harness:add-task` from the project to register it.\n   - Stop.\n\n## Step 0.2 — Skill routing\n\nResolve which skill should actually ship this work for `cfg`'s project:\n\n1. `desired_skill` = `cfg.skills.ship` (if set) → else the YAML's top-level `defaults.skills.ship` (if set) → else `\"fsad-harness:ship-it\"`.\n2. If `desired_skill` differs from `\"fsad-harness:ship-it\"`: invoke the `Skill` tool with that skill name, passing `$ARGUMENTS` unchanged, and **stop** — none of this skill's remaining steps run in this invocation.\n3. Otherwise continue to Step 0.4.\n\n## Step 0.4 — Pre-merge branch guard\n\nStep 0.5 merges worktree branches into whatever branch is currently checked out, with no branch awareness of its own. If that happens to be `main`\u002F`master`, the merge commit lands directly on the default branch — before Step 2.0's branch guard ever runs. This step closes that gap by moving off the default branch first, whenever a merge is about to happen.\n\n1. Run `git worktree list --porcelain` from `project_root`. Parse the output to get all worktrees (each block starts with `worktree \u003Cpath>`), then filter out the main worktree — the one whose path equals `project_root`.\n2. If **no additional worktrees** exist: skip this step entirely and proceed to Step 0.5 (which will independently reach the same conclusion and pass through to Step 1).\n3. If additional worktrees exist, check the current branch: `git rev-parse --abbrev-ref HEAD`.\n   - **Not `main`\u002F`master`**: already off the default branch — proceed to Step 0.5.\n   - **Is `main`\u002F`master`**: a merge is about to happen and must not land here.\n     - Propose a branch name that does **not** depend on a version string (the version isn't cut until Step 1b):\n       - If the worktree branch names encode task IDs (e.g. `worktree-task-tbs-070` → `tbs-070`), suggest `task\u002F{task-ids}` — joining multiple IDs with `+` when merging more than one worktree (e.g. `task\u002Ftbs-070+tbs-071`).\n       - Otherwise, suggest `ship\u002F{YYYY-MM-DD}` using today's date (`date +%F`).\n     - Ask the user: \"About to merge {N} worktree branch(es) while on `{main|master}` — create and switch to `{branch}` first? (y\u002Fn or type a different name)\"\n     - On confirmation: run `git checkout -b {branch}`.\n     - If declined: stop here. Do not proceed to Step 0.5 — merging while on the default branch is the exact failure this step exists to prevent.\n4. Proceed to Step 0.5.\n\n## Step 0.5 — Merge pending worktrees\n\nThis step must never run while `HEAD` is the default branch (`main`\u002F`master`) — Step 0.4 ensures that by branching off first whenever a merge is pending. The merge itself cannot simply be deferred to run *after* Step 2.0's branch guard instead: Step 1's audits (README\u002FCHANGELOG\u002Fcompleted-task checks) read the post-merge tree, so the merge must still happen before those audits — just off the default branch rather than on it.\n\n**Main-tree writeback reconciliation.** `fsad-harness:do-task` (Step 5h) and `fsad-harness:add-task` write to the *main* tree even when their session runs inside a worktree: `todo.md` gets `[x]` marks and new bullets, and a task-detail file may be edited in place. At merge time these main-tree edits are therefore **expected to be uncommitted** — they are not a sign of a dirty tree that must be cleaned first. Meanwhile the worktree branch may carry new task-detail files whose bullets already sit in the main-tree `todo.md`, or a `todo.md` edit of its own. Reconcile as follows:\n\n- **Leave the main-tree `todo.md` \u002F task-file edits in the working tree** — do not stash, discard, or \"clean up\" them before merging. They are staged by name together with the merge result in Step 2.2 and land in the same commit batch.\n- If `git merge` refuses to start because a file the branch touches is also modified in the main tree (typically `todo.md`), commit the main-tree edit first — `git add \"{cfg.todo_file}\" && git commit -m \"todo: mark {IDs} done\"` — then re-run the merge. Committing, not stashing, is the only accepted way to clear this.\n- **`todo.md` conflict → keep both sides.** When the merge conflicts in `todo.md`, the correct resolution is almost always the union: keep every bullet from both sides, deduplicate any bullet that appears on both (keeping the `- [x]` form if either side has it), and order the bullets by task ID. Never drop a bullet to make the conflict go away.\n- A task-detail file arriving from the branch whose bullet is already in the main-tree `todo.md` is the normal case, not a conflict — nothing to reconcile.\n\n1. Run `git worktree list --porcelain` from `project_root`. Parse the output to get all worktrees (each block starts with `worktree \u003Cpath>`).\n2. Filter out the main worktree — the one whose path equals `project_root`.\n3. If **no additional worktrees** exist: skip this step entirely and proceed to Step 1.\n4. If additional worktrees exist:\n   - List them for the user: path and branch name (the `branch` field from the porcelain output), plus each branch's uncommitted-changes status (`git -C \u003Cpath> status --porcelain`, summarized as \"clean\" or \"N uncommitted files\").\n   - **Stop and ask for explicit confirmation** before touching anything:\n     > \"About to merge {N} worktree branch(es) into `{project_root}`: {list}. For each: commit any uncommitted changes, `git merge --no-ff \u003Cbranch>`, verify the merge landed, then force-remove the worktree and delete the branch. Proceed? (y\u002Fn)\"\n   - If the user declines: stop here. Do not modify or remove any worktree. Report which worktrees remain pending.\n   - On confirmation, for each worktree in order:\n     a. Check for uncommitted changes: `git -C \u003Cpath> status --porcelain`. If any are present, commit them: `git -C \u003Cpath> add -A && git -C \u003Cpath> commit -m \"\u003Cbranch-name>: parallel do-task changes\"`.\n     b. Merge the branch into the main worktree: `git merge --no-ff \u003Cbranch>`.\n     c. If the merge produces a conflict: **stop immediately**. Tell the user which file(s) conflict and provide these resolution steps:\n        1. Open each conflicting file and locate the conflict markers (`\u003C\u003C\u003C\u003C\u003C\u003C\u003C`, `=======`, `>>>>>>>`).\n        2. Edit the file to keep the correct content and remove all conflict markers. For `{cfg.todo_file}`, apply the keep-both rule above: union of both sides' bullets, `- [x]` wins over `- [ ]` for the same ID, sorted by task ID.\n        3. Stage each resolved file: `git add \u003Cfile>`.\n        4. Complete the merge commit: `git commit`.\n        5. Re-run `fsad-harness:ship-it` to continue from where it left off.\n        Do not proceed to Step 1. Do not remove the worktree — it may still hold the only copy of unmerged work.\n     d. **Verify the merge landed before removing anything**: run `git merge-base --is-ancestor \u003Cbranch> HEAD`. Exit code `0` means the branch tip is now reachable from `HEAD` — the merge is confirmed in the main tree.\n        - If this check **fails** (non-zero exit): **stop immediately**. Do not remove the worktree or delete the branch — the merge did not land as expected and the worktree is the only safe copy of that work. Report the branch name and ask the user how to proceed.\n        - If this check **passes**: only now remove the worktree (`git worktree remove --force \u003Cpath>`) and delete the branch (`git branch -d \u003Cbranch>`).\n5. After all worktrees are merged, verified, and removed: tell the user \"Worktrees merged.\" and proceed to Step 1.\n\n## Step 1 — Pre-flight audits (run concurrently)\n\n### 1.0 — Does versioning apply to this project?\n\nDecide this **before** dispatching the audits, because it determines whether Audit B and Step 1b run at all. Set `versioning = off` if **either** of the following holds:\n\n- `cfg.version_scheme` is explicitly `none` (or the YAML null forms `null` \u002F `~`). This is different from the key being **absent** — an absent key still runs Audit B and falls through to the \"No `version_scheme` configured\" prompt in 1b.\n- The CHANGELOG file does not exist on disk. Resolve `changelog_path` = `{project_root}\u002F{cfg.changelog_file}` if `cfg.changelog_file` is set, else `{project_root}\u002FCHANGELOG.md`; every later reference to the CHANGELOG in this skill means this path.\n\nWhen `versioning = off`:\n\n- Tell the user **once**, naming the reason that applied: \"Versioning skipped for this project — `version_scheme: none`.\" or \"Versioning skipped for this project — no CHANGELOG at `{changelog_path}`.\" Do not mention it again in later steps.\n- **Skip Audit B and Step 1b entirely.** Run only Audits A, C, and D.\n- **Do not create a CHANGELOG, do not invent or propose a version, and do not touch `cfg.version_files`.** A project without versioning ships as a plain commit + PR; Step 2.0 then names the branch `task\u002F…` or `ship\u002F…`, Step 2.1 item 1 and item 3 are skipped, and Step 3 predicate 3 is not applicable.\n\nOtherwise (`versioning = on`) run all four audits below.\n\nThese checks are read-only and independent of each other — none needs another's output. Dispatch them as concurrent subagents (or, if subagents aren't available in the current context, run the read passes back-to-back with no dependency ordering between them) rather than sequentially:\n\n- **Audit A — README ↔ skills-table consistency:** List all subdirectories in `{project_root}\u002Fskills\u002F` (each is a skill). Read `{project_root}\u002FREADME.md`. For every skill directory, check a corresponding row exists in the README skills table (match on directory name). Report any skills missing from the README.\n- **Audit B — CHANGELOG\u002Fversion derivation:** Read `{changelog_path}`. Find the `## [Unreleased]` section and collect everything between that heading and the next `## [` heading (the pending content). Read `cfg.version_scheme` and `cfg.version_files`; if set, read the first file in the list and extract the current version string (JSON `\"version\"` field, README version-table row, or HTML `\u003Ctitle>`). Report: pending CHANGELOG content (or \"empty\"), current version scheme, and current version string.\n- **Audit C — completed-task cross-reference:** Scan `{project_root}\u002F{cfg.task_dir}\u002F` for files matching the task filename pattern (e.g. `task-tbs-NNN.md`) not already inside `completed\u002F`. For each, look up the corresponding identifier in `cfg.todo_file`: `- [x]` → candidate to move; `- [ ]` or not found → leave in place. For each candidate, also list its AC status file (the same path with `.md` replaced by `.acs.json`) when one exists, so it moves with the task file. Report the candidate list.\n- **Audit D — skill frontmatter parse:** If `{project_root}\u002Fscripts\u002Fcheck-frontmatter.sh` exists, run it with no arguments and report its `BAD:` lines and exit code. If the script does not exist, report \"not applicable\".\n\nOnce the audits report back, apply their results serially (writes must stay serial even though the reads were concurrent):\n\n### 1a — README check\n\n- If Audit A found missing skills: tell the user which ones (e.g. `ship-it`, `next`). Ask them to update the README before continuing. Stop — do not proceed until the user confirms it's done and re-invokes.\n- If clean: tell the user \"README check passed.\" and proceed.\n\n### 1a.5 — Frontmatter check\n\n- If Audit D reported a non-zero exit: show the user its output (`BAD:` lines, or \"ruby not found\" on exit 2). A bad frontmatter block makes Claude Code drop the skill's `description`. Ask them to fix it (usually: quote the value). Stop — do not proceed until the user confirms it's done and re-invokes.\n- If exit 0: tell the user \"Frontmatter check passed.\" and proceed. If \"not applicable\", proceed silently.\n\n### 1b — CHANGELOG + version\n\nSkip this whole sub-step when Step 1.0 set `versioning = off` — go straight to 1c.\n\n1. If Audit B's pending content is **empty** (no bullets\u002Fsubsections):\n   - Tell the user: \"The [Unreleased] section has no content — there may be nothing to release. Proceed anyway? (y\u002Fn)\"\n   - No → stop. Yes → skip the version-bump sub-steps and go to 1c.\n2. If Audit B's pending content is **non-empty**:\n   - Show the user the pending content.\n   - **Propose next version** based on `cfg.version_scheme` (from Audit B):\n     - `semver`: bump the **patch** component by default (e.g., `v3.2.0 → v3.2.1`). Tell the user they can type `minor` or `major` for a different bump level.\n     - `integer`: increment the integer suffix by 1 (e.g., `v36 → v37`).\n     - `calver`: use today's date in `YYYY.MM.DD` format.\n     - **No `version_scheme` configured:** ask \"Cut a new version? If yes, what tag? (e.g. v4)\" and use whatever they provide.\n   - **Confirm:** Present \"Suggested version: **[tag]** — confirm, type a different tag, or type `no` to skip.\"\n     - `no`\u002Fskip → proceed to 1c without modifying CHANGELOG or version files.\n     - `minor`\u002F`major` (semver only) → recompute at that level and re-present.\n     - Confirmed\u002Fdifferent tag → use that tag and continue.\n   - **Version-collision check** — the confirmed tag must not already be claimed by work that hasn't shipped yet. Run `git fetch origin --tags --prune` once, then inspect **all three** of these sources and report what each found:\n     1. **Tags:** `git tag --list \"{tag}\"` — non-empty output is a collision.\n     2. **Remote release branches:** `git ls-remote --heads origin \"release\u002F*\"` — any branch named `release\u002F{tag}` (other than the branch currently checked out, if it already carries that name) is a collision: someone cut this version on another branch that hasn't merged.\n     3. **Uncommitted CHANGELOG diff:** `git diff HEAD -- \"{changelog_path}\"` — an added line matching `## [{tag}]` is a collision: a prior, unfinished ship attempt (or another session) already wrote this version into the working tree.\n     If all three are clean, continue to **Write version files**. On any collision, name the source(s) and what they contain, then ask the user to choose exactly one of two outcomes:\n     - **Renumber** — bump once more at the same level (`vX.Y.Z` → `vX.Y.(Z+1)` for a patch, etc.), re-present the new tag, and re-run this collision check on it. If three consecutive candidates collide, stop and treat it as Abort — something other than a stale tag is going on.\n     - **Abort** — stop `fsad-harness:ship-it` here with no files written; the user resolves the conflicting source (deletes the stale tag\u002Fbranch, finishes or reverts the WIP CHANGELOG edit) and re-invokes.\n     Do **not** stash the user's working-tree changes to get past a collision, and never stash silently at any point in this skill: the stash stack is shared by every worktree of the repo, so a stash made here hides WIP from the session that owns it. If the user explicitly asks to set a change aside, the only acceptable form is a tagged, untracked-inclusive one — `git stash push -u -m \"fsad-harness:ship-it: {reason}\"` — announced before it runs and listed in the handoff.\n   - **Write version files:** update every file in `cfg.version_files` with the new version string, preserving each file's existing format (JSON `\"version\"` field, README table row, HTML `\u003Ctitle>`).\n   - **Update CHANGELOG:** replace `## [Unreleased]` with `## [vN] — YYYY-MM-DD` (today's date), insert a fresh empty `## [Unreleased]` block above it, write the file. Tell the user \"CHANGELOG updated — [vN] cut.\"\n\n### 1c — Move completed task files\n\n- If Audit C found no candidates: tell the user \"No completed task files to move.\" and proceed to Step 2.\n- If candidates exist: tell the user \"Moving these to `completed\u002F`:\" followed by the list, then `git mv` each file into `{project_root}\u002F{cfg.task_dir}\u002Fcompleted\u002F`. A task file's `.acs.json` status file moves in the same step, never left behind. If it is untracked, `git add` it first so `git mv` can move it.\n\n## Step 2 — Commit, push, PR\n\n### 2.0 — Branch guard\n\n1. Run `git rev-parse --abbrev-ref HEAD` to get the current branch name.\n2. If the branch is `main` or `master`:\n   - Propose a feature branch name:\n     - If a version was cut in Step 1: suggest `release\u002F{version}` (e.g. `release\u002Fv4`).\n     - Otherwise: suggest `task\u002F{task-ids}` using any task IDs marked done during this session (e.g. `task\u002Ftbs-007`), or `ship\u002F{YYYY-MM-DD}` if no IDs are in scope.\n   - Ask the user: \"Create and switch to `{branch}`? (y\u002Fn or type a different name)\"\n   - On confirmation: run `git checkout -b {branch}`.\n3. If already on a non-default branch:\n   - Tell the user \"Already on branch `{branch}`.\"\n   - Determine whether the branch needs renaming to `release\u002F{version}` — this applies in two cases:\n     - **Provisional branch**: `{branch}` matches `^ship\u002F` or `^task\u002F` (a branch Step 0.4 created to hold a pending worktree merge off `main`), and a version was cut in Step 1.\n     - **Stale release branch**: `{branch}` matches `release\u002FvX.Y.Z`, a version was cut in Step 1, and `vX.Y.Z` does not equal the newly cut version.\n   - If either case applies:\n     - Propose renaming: \"Branch name `{branch}` should be `release\u002F{version}` — rename? (y\u002Fn)\"\n     - If yes, reuse the same rename procedure for both cases:\n       a. `git branch -m {branch} release\u002F{version}` — rename locally.\n       b. `git push origin release\u002F{version}` — push the new name.\n       c. `git push origin --delete {branch}` — delete the old remote branch (ignore error if it didn't exist remotely).\n       d. `git branch -u origin\u002Frelease\u002F{version}` — update tracking ref.\n       e. Tell the user \"Branch renamed to `release\u002F{version}`.\"\n     - If no: continue on the current branch name.\n   - Otherwise: continue without renaming.\n4. **Main-cleanliness assertion**: run `git fetch origin main` then compare `git rev-parse main` against `git rev-parse origin\u002Fmain`.\n   - **Equal**: proceed to Step 2.1.\n   - **Not equal**: stop and report. Local `main` has diverged from `origin\u002Fmain` — most likely a merge commit landed directly on `main` (the failure this guard exists to catch). Remedy: `git branch -f main origin\u002Fmain` to reset local `main` to match remote, then re-run `fsad-harness:ship-it`. Do not proceed to Step 2.1 until this passes.\n\n### 2.1 — Independent pre-PR checklist verifier\n\nBefore committing, run one more fresh, independent read pass over the three Pre-PR Checklist items (README, CHANGELOG, version files — see `CLAUDE.md`'s Pre-PR Checklist section if present). This pass must **re-read the files from disk**, not reuse conclusions from Step 1 — the point is to catch a mistake the same pass that wrote them could have missed:\n\n1. Re-read `{project_root}\u002FCHANGELOG.md`: confirm the change has an entry either under `## [Unreleased]` or in a freshly cut version block. (Skip when Step 1.0 set `versioning = off` — there is no CHANGELOG to read and none should be created.)\n2. Re-read `{project_root}\u002FREADME.md`: confirm the version table's **Current version** and **Date** match what's being shipped (if a version was cut).\n3. Re-read every file in `cfg.version_files`: confirm all carry the identical new version string (if a version was cut; skip when `versioning = off`).\n4. If any mismatch is found: report it to the user and stop — do not commit with a known Pre-PR Checklist violation. Ask whether to fix it now or abort.\n5. If every applicable item checks out: tell the user \"Pre-PR checklist verified independently.\" and proceed.\n\n### 2.2 — Commit\n\n1. Run `git status` to see what's staged and unstaged. Stage all relevant changed files by name (do **not** use `git add -A` or `git add .`):\n   - Modified: `CHANGELOG.md`, `README.md`, todo file (including the main-tree `[x]` marks and bullets written back by `fsad-harness:do-task`\u002F`fsad-harness:add-task` — see Step 0.5), any other files changed during this work session.\n   - Moved task files (already staged by `git mv`).\n2. Run `git log --oneline -5` to read recent commit message style.\n3. Draft a commit message:\n   - If a version was cut in Step 1: use `release [vN] — \u003Cone-line summary of what's in the release>`.\n   - If no version was cut: summarize the batch of completed work in one line.\n   - Append `Co-Authored-By:` with the **current model's** name and provider, derived at runtime from the executing session (e.g. the model identifying itself in this conversation) — never hardcode a specific model name\u002Fversion in the skill file itself, since that goes stale every time the underlying model changes.\n   - Pass via heredoc to avoid shell quoting issues.\n4. Show the user the draft commit message and ask for confirmation before committing.\n5. On confirmation: create the commit.\n\n### 2.3 — Push, with verify-and-retry\n\n1. Record the local HEAD SHA: `git rev-parse HEAD`.\n2. Push: `git push -u origin HEAD`.\n3. **Verify the push landed**: `git ls-remote origin {branch}` and compare the remote SHA to the local HEAD SHA from step 1.\n   - **Match**: push confirmed, continue to 2.4.\n   - **Mismatch or push failed**: retry, capped at 2–3 attempts:\n     a. If the remote moved ahead (someone else pushed): `git pull --rebase` then re-push.\n     b. If the push was simply rejected\u002Ffailed transiently: re-push directly.\n     c. After each retry, re-check `git ls-remote` against the (possibly updated) local HEAD.\n   - If still mismatched after the retry cap: **stop and ask the user** how to proceed. Do not silently continue as if the push succeeded.\n\n### 2.4 — Create PR, with verify-and-fallback\n\n1. Detect the default branch: `git remote show origin | grep 'HEAD branch' | awk '{print $NF}'` (typically `main` or `master`).\n2. Create PR with `gh pr create`:\n   - Title: same one-liner used in the commit.\n   - Include `--base {default_branch}` so the PR always targets the default branch.\n   - Body (heredoc):\n     ```\n     ## Summary\n     \u003CCHANGELOG content for this version, or a bullet-list summary if no version was cut>\n\n     ## Test plan\n     - [ ] Skill files deployed and appear in `\u002Ffsad-harness:*` command list\n     - [ ] README skills table is accurate\n     - [ ] CHANGELOG entry is present and correctly formatted\n\n     ? Generated with [Claude Code](https:\u002F\u002Fclaude.com\u002Fclaude-code)\n     ```\n3. **Verify the PR actually exists** — do not trust `gh pr create`'s exit code alone: run `gh pr view --json url,state,baseRefName` for the current branch.\n   - If it returns a PR with the expected base branch: confirmed. Record the URL.\n   - If `gh pr create` reported failure because a PR already exists for this branch: run `gh pr list --head {branch} --json url,state,baseRefName` to find the existing PR and report it instead of treating this as an error.\n   - If neither check turns up a PR: stop and tell the user PR creation could not be confirmed — do not report a PR URL that wasn't actually verified.\n4. Proceed to Step 3 (Verify shipped) once the PR is confirmed.\n\n## Step 3 — Verify shipped\n\nConfirm the three \"shipped\" predicates from the top of this document, using fresh checks (not assumptions carried over from Step 2):\n\n1. **Remote-HEAD parity**: `git ls-remote origin {branch}` — the returned SHA must equal `git rev-parse HEAD`. PASS\u002FFAIL.\n2. **PR existence**: `gh pr view --json url,state,baseRefName` — must return a PR targeting the default branch. PASS\u002FFAIL.\n3. **Version-string consistency** (only if a version was cut in Step 1; N\u002FA when Step 1.0 set `versioning = off`): re-read every file in `cfg.version_files` and confirm they all carry the identical new version string. PASS\u002FFAIL.\n\nReport all three verdicts to the user. If any predicate fails, say so explicitly — do not report the work as \"shipped\" on a partial result. Only report success when all applicable predicates pass.\n",{"description":7,"allowed-tools":45},"Bash(git worktree:*), Bash(git -C:*), Bash(git merge:*), Bash(git merge-base:*), Bash(git add:*), Bash(git commit:*), Bash(git branch:*), Bash(git mv:*), Bash(git rev-parse:*), Bash(git checkout:*), Bash(git push:*), Bash(git pull:*), Bash(git status:*), Bash(git log:*), Bash(git remote:*), Bash(git ls-remote:*), Bash(gh pr create:*), Bash(gh pr view:*), Bash(gh pr list:*), Bash(git fetch:*), Bash(git tag:*), Bash(git diff:*), Bash(date:*)","main","bf3a412c985273327682c956ca35341c64ce9f8c","skills\u002Fship-it",4,"https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook","https:\u002F\u002Ffsad-playbook.vercel.app","https:\u002F\u002Fgithub.com\u002Ftbeack","2026-11-11 21:04:33",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add tbeack\u002Ffsad_playbook --skill ship-it",{"tool":60,"command":61},"bunx","bunx skills add tbeack\u002Ffsad_playbook --skill ship-it",{"tool":63,"command":64},"pnpm","pnpm dlx skills add tbeack\u002Ffsad_playbook --skill ship-it","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fship-it\u002FSKILL.md",true,[],[69,89,121,135,164,191],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":40},"01M6K7W5RTRMRQMWAGXNSX8DM6","sync","Pre-flight sync check for registered projects. Fetches from remote, reports uncommitted changes, and handles four states — up-to-date, ahead, behind (warns + pulls), or diverged (warns, no pull). Supports single-project (cwd detection), named-project (fsad-harness:sync key1 key2), and full-sync (fsad-harness:sync ALL) modes.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fsync\u002FSKILL.md",[],[71,76,77,78,79,80,22,81,82,83,25,84,85,86,87,88],"pull","harness","detection","projects","check","reports","project","full","states","for","changes","modes","single",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":100,"publishedAt":40},"01M6K7VY55SWBDD379T9JZVF16","spec-review","Run a detailed, adversarial multi-agent review of a written proposal or specification (spec.md, PRD, RFC, design doc, architecture proposal, business case) — reviews the document, not code. Runs a steelman\u002Finventory pass, then up to 10 specialist lenses (logic, assumptions, evidence, completeness, feasibility, alternatives, consistency, stakeholders, testability, risk), then adversarial validation that defaults to refuting each finding unless it survives, then a loop-until-dry completeness pass. Writes a single severity-ranked SPEC-REVIEW.md next to the document. Use when the user says \"review this spec\", \"adversarial review\", \"poke holes in this proposal\", \"red team this PRD\", \"critique this design doc\", \"what's wrong with this RFC\", \"stress test this plan\", or similar. Review-only — never edits the document.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fspec-review\u002FSKILL.md",[95],{"categoryNo":96,"categoryName":97,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[101,102,103,104,105,106,107,108,109,110,111,22,112,113,114,115,116,117,118,119,120],"review","spec","spec review","design","architecture","code","inventory","team","loop","never","only","specification","poke","deep","dry","proposal","lens","quick","reviews","when",{"skillNo":122,"name":19,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":126,"publishedAt":40},"01M6K7VVD3B0SM3VKJ5W26QPPV","Short alias for \u002Ffsad-harness:ship-it. Wrap up finished work, cut a version, commit, push, and open a PR.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fship\u002FSKILL.md",[],[19,31,127,77,128,37,129,34,130,131,22,132,133,134,20,85],"pr","push","wrap up","short","version","wrap","alias","cut",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":40},"01M6K7VEK1C0MK5BNDHV8S6HCA","set-context","Gather codebase and initiative context before planning starts — runs a graphify build or query when graphify is available locally, falls back to a manual repo sweep otherwise, then asks targeted questions and writes a single context.md. Invocable standalone or as fsad-harness:plan's mandatory Phase 0. Use when the user says \"set context\", \"gather context\", or as the first step of fsad-harness:plan.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fset-context\u002FSKILL.md",[],[142,143,144,145,77,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,88,162,163,120],"SET","context","set context","planning","first","graphify","plan","sweep","repo","user","use","build","back","query","as","questions","manual","standalone","path","codebase","phase","gather",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":174,"publishedAt":40},"01M6K7V17M3MQ92QZ99G294RYM","sec-review-team","Run a multi-agent security review TEAM over a codebase or recent diff. Picks a specialist roster based on detected stack signals (webapp \u002F desktop \u002F iac \u002F llm-agent \u002F container \u002F backend \u002F mobile \u002F etc.) from a library of 13 specialists. Each specialist writes structured JSONL + prose findings in parallel; input-validation-auditor and auth-authz-auditor run a loop-until-dry consensus fan-out (2-8 passes); every deduped finding is adversarially verified by a dedicated validator agent before it can appear in the report. Orchestrator consolidates into a single severity-ranked REPORT.md with deduped root issues, archived per-run under a known-findings.jsonl ledger that drives auto re-review. Distinct from the built-in `\u002Fsecurity-review` (which is a single-pass branch review). Use when the user says \"multi-agent security review\", \"security review team\", \"sec-review team\", \"audit this branch with a team\", \"pre-release hardening team pass\", or similar. Review-only — no fixes.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fsec-review-team\u002FSKILL.md",[170,173],{"categoryNo":171,"categoryName":172,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":96,"categoryName":97,"taxonomyType":98,"taxonomyTypeDesc":99},[101,108,175,176,177,178,179,180,181,109,182,33,183,184,185,115,186,187,188,189,190],"sec review team","review team","agent security","security review","run","release","security-review","stack","consensus","auto","mobile","input","re","jsonl","which","verified",{"skillNo":192,"name":193,"nameZh":6,"description":194,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":195,"categories":196,"keywords":202,"publishedAt":40},"01M6K7TP7VTTPW9H79AEHEVPGK","sec-review-fixes","Companion to `sec-review-team`. Takes the `REPORT.md` or `findings.jsonl` from a security review run and produces fix PRs (one per finding or one consolidated) with interactive approval. Each candidate finding becomes a proposed diff + a regression test that would have caught it + a commit\u002FPR citing the finding ID. Filters by default to `severity IN (critical, high) AND confidence IN (certain, likely)`. Modes: interactive (default), `--dry-run` (produces a fix-plan document with no code changes), `--re-verify` (re-runs sec-review-team against a specific set of finding IDs and reports fixed \u002F still-present \u002F inconclusive). Safeguards: branch-only commits (never main), no force-push, clean-working-tree required, regression-test must pass before commit lands. Use when the user says \"fix the security findings\", \"apply sec-review fixes\", \"open PRs for the security report\", \"re-verify the security fixes\", or similar.","https:\u002F\u002Fgithub.com\u002Ftbeack\u002Ffsad_playbook\u002Fblob\u002Fmain\u002Fskills\u002Fsec-review-fixes\u002FSKILL.md",[197],{"categoryNo":198,"categoryName":199,"taxonomyType":200,"taxonomyTypeDesc":201},"01JBSEEDCAT000000000000034","行政与办公人员","PROFESSION","适用职业",[101,203,204,205,106,142,206,37,207,33,208,110,111,209,210,211,212,46,213,187,120],"fixes","review fixes","sec review fixes","ids","regression","fix","security report","filters","default","clean","critical"]