一键导入
start-ticket
Declare an active ticket so the ticket-first hook lets code edits through. Accepts `<N>` or `<owner>/<repo>#<N>`.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Declare an active ticket so the ticket-first hook lets code edits through. Accepts `<N>` or `<owner>/<repo>#<N>`.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Review a PR for quality, security, and standards compliance. Invokes the Code Reviewer agent (Rex).
Review a technical design / migration AgDR / feature spec for architectural soundness BEFORE the Build phase. Invokes the Solution Architect agent (Tariq) — the non-code analog of /code-review.
Score a review agent (Rex/Hakim/Tariq) against a labeled PR corpus — ground-truth defect-set overlap, never a prose rubric. Headline metric is approve-precision.
Security-focused PR review for vulnerabilities and best practices. Invokes the Security Reviewer agent (Shield).
Record per-PR design-review approval (UI merge gate). ONLY on an explicit per-PR designer "approved".
Record per-PR CEO approval and merge in one turn. ONLY on an explicit per-PR "approved" — never on umbrella "go".
| name | start-ticket |
| description | Declare an active ticket so the ticket-first hook lets code edits through. Accepts `<N>` or `<owner>/<repo>#<N>`. |
| disable-model-invocation | false |
| argument-hint | <issue-number> | <owner/repo>#<number> |
| effort | low |
Writes a session marker so the require-active-ticket.sh PreToolUse hook permits Edit/Write on code paths. Without it, the hook blocks edits to anything outside .claude/, docs/, projects/*/docs/, and *.md.
Marker layout (apexyard#41 + #513):
| Path | When the hook uses it |
|---|---|
<ops_root>/.claude/session/tickets/<project>/<safe-branch> | Tier 0 (#513). Edit is under <ops_root>/workspace/<project>/ AND the file's repo is on a git-worktree branch (or CLAUDE_WORKTREE_BRANCH is set). Lets parallel agents on the same project hold independent tickets. safe-branch = branch with /→__. |
<ops_root>/.claude/session/tickets/<project> | Tier 1. Edit is under <ops_root>/workspace/<project>/ AND this per-project marker exists (as a FILE). Single-agent default. |
<ops_root>/.claude/session/current-ticket | Tier 2. Fallback. Checked if neither above matched. Also the marker for ops-repo framework edits (no workspace/<name>/ prefix). |
All markers live in the ops fork (gitignored). No more .claude/session/ inside each managed-project clone. tickets/<project> is a FILE (tier 1) or a DIRECTORY holding <safe-branch> markers (tier 0) — the hook's -f tests keep the two from colliding.
This is the mechanical enforcement of the Pre-Build Gate in .claude/rules/workflow-gates.md — "do not start coding until the ticket exists".
Read the registry path via portfolio_registry, the per-project docs dir via portfolio_projects_dir, and the ideas backlog via portfolio_ideas_backlog — all from .claude/hooks/_lib-portfolio-paths.sh. Source the helper at the top of any bash block that touches those paths:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
registry=$(portfolio_registry)
Defaults match today's single-fork layout (./apexyard.projects.yaml, ./projects, ./projects/ideas-backlog.md). Adopters in split-portfolio mode override the portfolio.{registry, projects_dir, ideas_backlog} keys in .claude/project-config.json. Don't hardcode literal apexyard.projects.yaml or projects/ paths in bash blocks — the helper resolves whichever mode the adopter is in. See docs/multi-project.md.
Expected forms:
42 — plain number, resolves against the current repo. Read git remote get-url origin and extract <owner>/<repo>. If there's no origin, stop and ask for a fully-qualified reference.me2resh/flat-mate#128 — fully-qualified reference.apexyard#42 — owner defaults to the current org (parsed from the origin URL).If $ARGUMENTS is empty, stop and ask the user which issue they're starting.
Cross-repo note: ApexYard governs a portfolio of repos. If the user is in the ops repo (the apexyard fork) but the ticket lives in a managed project's own repo, they should pass the fully-qualified form so the marker records the correct tracker. Each managed project's tickets live in that project's own GitHub repo — tickets do not cross project boundaries.
Source the tracker library and call tracker_view. The library dispatches the right CLI based on .tracker.kind in .claude/project-config.{defaults,}.json — gh (default), linear, jira, asana, custom, or none. See .claude/hooks/_lib-tracker.sh and AgDR-0033.
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-tracker.sh"
issue_json=$(tracker_view "<number>" "<owner/repo>")
state=$(echo "$issue_json" | jq -r '.state // empty')
title=$(echo "$issue_json" | jq -r '.title // empty')
url=$(echo "$issue_json" | jq -r '.url // empty')
The lib emits normalised JSON: {state, title, url, labels}. Each tracker adapter parses the underlying CLI's JSON into this common shape, so the skill doesn't need to branch per-CLI.
If the lib exits non-zero with empty stdout, the issue does not exist (or the CLI isn't installed / authenticated). Stop and report the error — do not write the marker.
If state indicates the ticket is closed (gh: CLOSED; linear/jira/asana: Done / Closed / Resolved / Cancelled), warn the user and confirm before continuing (sometimes you do want to resume work on a re-opened issue).
tracker.kind = none adopters: the lib returns no data. Skip the existence check entirely; trust the user's input. Re-verify the shape against tracker_id_pattern so obvious typos still block.
From the issue title and number, generate: <type>/<TICKET-ID>-<slug> where:
<type> guessed from title prefix: [Feat] → feature, [Fix] → fix, [Docs] → docs, [Chore] → chore, default feature<TICKET-ID> is GH-<number> for GitHub Issues, or matches the project's configured ticket_prefix from apexyard.projects.yaml if set<slug> = lowercase title, kebab-case, max 40 chars, stopwords trimmed from the edgesMatch the convention in .claude/rules/git-conventions.md.
Per apexyard#41, the marker path depends on whether the ticket's tracker repo matches a registered managed project.
The ops root is the apexyard fork root — the directory containing BOTH onboarding.yaml and apexyard.projects.yaml. Walk up from CWD / the nearest git toplevel until you find it:
ops_root=""
r=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
while [ -n "$r" ] && [ "$r" != "/" ]; do
if [ -f "$r/onboarding.yaml" ] && [ -f "$r/apexyard.projects.yaml" ]; then
ops_root="$r"
break
fi
r=$(dirname "$r")
done
If not found (user is outside an apexyard fork), tell the user and stop. Starting a ticket without the fork doesn't make sense.
Given the ticket's owner/repo (from step 1), grep apexyard.projects.yaml for a project whose repo: field matches. One registry-safe way (uses yq when available, falls back to a greppy read):
if command -v yq >/dev/null 2>&1; then
project=$(yq eval ".projects[] | select(.repo == \"${OWNER_REPO}\") | .name" "$ops_root/apexyard.projects.yaml")
else
# Greppy fallback: find the `name:` whose sibling `repo:` matches.
# Strips surrounding quotes from both `name:` and `repo:` values so the
# comparison works whether the registry uses bare scalars
# (`repo: me2resh/curios-dog`) or quoted scalars (`repo: "me2resh/…"`).
project=$(awk -v r="$OWNER_REPO" '
function unquote(s) { gsub(/^["\x27]|["\x27]$/, "", s); return s }
/^[[:space:]]*- name:/ { name = unquote($3) }
/^[[:space:]]*repo:/ { if (unquote($2) == r) { print name; exit } }
' "$ops_root/apexyard.projects.yaml")
fi
Notes on the fallback:
repo: me2resh/curios-dog and repo: "me2resh/curios-dog" (and single-quoted).- name: is the FIRST key in each project entry — that matches the shape in apexyard.projects.yaml.example and every entry produced by /handover. If your registry reorders keys so repo: appears before name: in an entry, the lookup misses. Fix: move name: to the top, or install yq (the preferred path).^[[:space:]]* — nested entries under projects: parse fine at any indent level, so long as the indent is consistent within the entry.$project is now either a registered project name (e.g. curios-dog, sharppick) or empty (ticket's tracker repo isn't registered — typically because the ticket is on the ops fork itself, or a repo that's not under management).
Three tiers (apexyard#41 + #513). When the ticket maps to a registered project AND this session is running inside a git worktree (parallel agents fanned out on the same project), write a per-worktree marker so two agents on the same project don't overwrite each other's ticket (last-writer-wins). Otherwise write the per-project file (single-agent — unchanged), or the ops fallback.
if [ -n "$project" ]; then
# Detect a worktree: prefer the harness-set env var, else check whether the
# current checkout is a LINKED worktree (not the main working tree). Compare
# the ABSOLUTE git-dir against the ABSOLUTE common-dir — they differ only in a
# linked worktree. The absolute forms matter: a plain --git-dir vs
# --git-common-dir comparison false-positives in the main checkout (one comes
# back absolute, the other relative). This is the SAME detection the
# require-active-ticket.sh / require-migration-ticket.sh read side uses.
wt_branch="${CLAUDE_WORKTREE_BRANCH:-}"
if [ -z "$wt_branch" ]; then
gd=$(git rev-parse --absolute-git-dir 2>/dev/null)
gcd=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
if [ -n "$gd" ] && [ "$gd" != "$gcd" ]; then
wt_branch=$(git branch --show-current 2>/dev/null)
fi
fi
if [ -n "$wt_branch" ]; then
safe_branch="${wt_branch//\//__}" # '/' → '__' filesystem-safe
marker="$ops_root/.claude/session/tickets/$project/$safe_branch"
else
marker="$ops_root/.claude/session/tickets/$project"
fi
mkdir -p "$(dirname "$marker")"
else
marker="$ops_root/.claude/session/current-ticket"
mkdir -p "$(dirname "$marker")"
fi
Note: tickets/$project is a FILE in single-agent mode and a DIRECTORY in
worktree mode (it holds the per-branch markers). If you're switching a project
from single-agent to worktree mode and tickets/$project already exists as a
file, remove it first (rm "$ops_root/.claude/session/tickets/$project") so the
directory can be created.
Write these key=value lines to the path resolved in step 4c:
repo=<owner/repo>
number=<number>
title=<title>
url=<url>
suggested_branch=<branch>
started_at=<ISO-8601>
After writing the marker, call board_move_card so the GitHub Projects board
reflects the ticket being picked up. This is a no-op unless enable_auto_moves
is true in the fork's github_projects config.
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-project-board.sh"
board_move_card "<number>" "in_progress"
board_move_card degrades gracefully: if the board is not configured, the item
is not on the board, or gh project scope is absent, it warns to stderr and
returns 0 — it never blocks the ticket start.
Output a two-line confirmation that names the marker path so the user sees which scope this ticket is active on:
Active ticket: <owner/repo>#<number> — <title>
Marker: <marker> (per-project / ops fallback)
Suggested branch: <branch>
Do NOT create the branch automatically. The user may already be on a branch, or may want to confirm the branch name first.
.claude/session/ (including .claude/session/tickets/) is gitignored — markers are per-machine, per-clone of the ops fork./start-ticket again overwrites the marker at whichever path resolved in step 4c (per-project or fallback). That's how you switch tickets — including jumping between projects (each project's marker lives in its own file, so switching between curios-dog and sharppick doesn't lose either one's context).rm <ops_root>/.claude/session/tickets/<project>.rm <ops_root>/.claude/session/current-ticket..claude/, docs/, projects/*/docs/, any *.md) don't need a ticket — the skill is only required before touching source / config / infra..claude/session/current-ticket inside a managed-project clone (workspace/<name>/.claude/session/current-ticket), it's harmless but no longer read by the hook. Delete it or re-run /start-ticket to have the new marker written under the ops fork's .claude/session/tickets/<name>.Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.