WCAG 2.1 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
中文简介正在准备中。当前显示原文说明。
Declare an active ticket so the ticket-first hook lets code edits through. Accepts `<N>` or `<owner>/<repo>#<N>`.
中文简介正在准备中。当前显示原文说明。
实测评分 未实测
查看安装方法安装前可以查看交给 AI 代理的指令内容。
Writing: read docs/skills/shared/writing-rule.md before you write a durable artifact.
Rationale: read docs/skills/start-ticket-rationale.md for layout and resolution details.
Writes the active-ticket marker for one working tree, so the require-active-ticket.sh PreToolUse hook permits Edit/Write on code paths in that tree. Without it, the hook blocks edits to anything outside .claude/, docs/, projects/*/docs/, and *.md.
Each working tree keeps its own marker in its own git dir (AgDR-0222):
| Working tree | Marker path |
|---|---|
Main clone (the ops fork, or workspace/<project>/) | <repo>/.git/apexyard-ticket |
Linked worktree (git worktree add) | <repo>/.git/worktrees/<id>/apexyard-ticket |
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 single-fork layout (./apexyard.projects.yaml, ./projects, ./projects/ideas-backlog.md). Split-portfolio overrides portfolio.{registry, projects_dir, ideas_backlog} in .claude/project-config.json. Do not hardcode those paths — the helper resolves the mode. 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.other-org/other-repo#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.
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')
body=$(echo "$issue_json" | jq -r '.body // 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).
When the resolved project has orbit.default_planning: true, check body
for an ORBIT slice: line, including the bold form
**ORBIT slice:** \<id>``. If it has no such line, print:
WARN: This ticket names no ORBIT slice. Run /orbit slice or record ORBIT slice: none — <reason> in the issue body.
Use the project's orbit.default_planning registry value when present; fall
back to config_get '.orbit.default_planning'. Make this check after step 4b
resolves the project. Continue to write the marker even when the line is
missing. A missing or unreadable issue body also produces the warning.
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.
The marker lives in the git dir of the working tree you are in. A ticket on a managed project's repo is declared from that project's clone, or from one of its worktrees.
ops_lib="$(r="$PWD"; while [ -n "$r" ] && [ "$r" != / ]; do \
[ -f "$r/.claude/hooks/_lib-ops-root.sh" ] && { echo "$r/.claude/hooks/_lib-ops-root.sh"; break; }; \
r="${r%/*}"; done)"
if [ -z "$ops_lib" ]; then
echo "Not inside an apexyard fork (no .claude/hooks/_lib-ops-root.sh found walking up from $PWD)." >&2
exit 1
fi
# shellcheck source=/dev/null
. "$ops_lib"
ops_root=$(resolve_ops_root)
If $ops_root is still empty after sourcing (no pin, and resolve_ops_root's own internal walk also found no anchor above cwd), tell the user and stop. Starting a ticket without the fork doesn't make sense.
Given the ticket's owner/repo (from step 1), resolve the registry path via portfolio_registry (see "Path resolution" above — do NOT hardcode $ops_root/apexyard.projects.yaml; in split-portfolio v2 the registry lives in the sibling repo, not the ops fork) and grep it for a project whose repo: field matches. $ops_root is already resolved and guaranteed to carry a .claude/hooks/ tree (step 4a only succeeds when it does), so source directly from it — no second walk-up needed. One registry-safe way (uses yq when available, falls back to a greppy read):
source "$ops_root/.claude/hooks/_lib-read-config.sh"
source "$ops_root/.claude/hooks/_lib-portfolio-paths.sh"
registry=$(portfolio_registry)
if command -v yq >/dev/null 2>&1; then
project=$(yq eval ".projects[] | select(.repo == \"${OWNER_REPO}\") | .name" "$registry")
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/sample-app`) 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 } }
' "$registry")
fi
$project is now either a registered project name (e.g. sample-app, demo-svc) 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).
The marker goes into the tree that holds the code you will change.
workspace: path of its registry entry, or <workspace dir>/<project> when the entry has none.cwd_top=$(git rev-parse --show-toplevel 2>/dev/null || true)
ops_top=$(cd "$ops_root" && pwd -P)
if [ -n "$project" ] && [ "$cwd_top" = "$ops_top" ]
then
tree=$(cd "$ops_root" && bash -c '. "$1/.claude/hooks/_lib-active-ticket.sh" && active_ticket_init "$1" && active_ticket_project_clone "$2" && printf "%s" "$REPLY"' _ "$ops_root" "$project")
else
tree="${cwd_top:-$PWD}"
fi
if [ -z "$tree" ] || [ ! -d "$tree" ]
then
echo "No clone of $project at ${tree:-an unknown path}. Clone it, or run /start-ticket from inside its clone." >&2
exit 1
fi
The writer in step 5 also checks the tree. It writes the marker only when the ticket's repo belongs to the tree's registry entry. In the ops fork, it writes no ticket of a registered project. A refused write is not an error, because the old-layout marker still covers the ticket.
Do not delete an old-layout file. Step 5 writes the old-layout marker for this ticket where the old skill wrote it. So it replaces an older ticket there, as before.
Before step 5, run active_ticket_foreign_overwrite "$tree" "<owner/repo>" "<number>".
When it returns 0, ask with AskUserQuestion: stay in a worktree that
already holds your ticket (see #1534), take over, or cancel. Headless and
/fan-out cancel (marker unchanged). Same ticket or same-session switch:
no question. See step 5; docs/skills/start-ticket-rationale.md.
Write the markers in three steps. The issue title never goes on a command line. A title can hold shell syntax such as >, | tee or sed -i, and the ticket gate reads such a command as a file write. A fresh tree has no ticket yet, so the gate would block /start-ticket itself.
Run this fixed command to get the path of the ticket fields file. Replace $ops_root with the path from step 4:
bash -c '. "$1/.claude/hooks/_lib-active-ticket.sh" && active_ticket_pending_path "$1"' _ "$ops_root"
The command prints one path, <ops_root>/.claude/session/start-ticket-<id>.pending. The <id> is the session id, so each session uses its own file. Two sessions that run /start-ticket at the same time cannot swap tickets. Without a session id, the command makes a new id on each run. Run it once and use the printed path in steps 2 and 3.
Use the Write tool to write the ticket fields to the path from step 1, one key=value line each:
repo=<owner/repo>
number=<number>
title=<title>
url=<url>
suggested_branch=<branch>
Put the title on one line. Replace any newline in it with a space.
Run this fixed command. Replace $ops_root and $tree with the paths from step 4, and $pending with the path from step 1. The command takes only paths:
bash -c '. "$1/.claude/hooks/_lib-active-ticket.sh" && active_ticket_write_from_file "$2" "$3"' _ "$ops_root" "$tree" "$pending"
active_ticket_write_from_file in .claude/hooks/_lib-active-ticket.sh reads the fields and deletes the file. It refuses a file that is a symlink. It also refuses any path that is not a start-ticket-<id>.pending file in .claude/session, and leaves that path in place. It then runs the two writers. active_ticket_write writes the marker into the tree's git dir. active_ticket_write_legacy writes the old-layout marker.
When the tree fails validation, the command writes only the old-layout marker and prints a one-line note. That is not an error. The hooks read the old-layout marker for that tree as they did before.
active_ticket_write writes repo, number, title, url, suggested_branch, session_id, branch, and started_at with printf '%s\n' (reject \n/\r; title one line; first-match reads; AgDR-0257). APEXYARD_START_TICKET_TAKEOVER=1 is a cooperative signal: set it inline on the single write after take over — never export or inherit. Headless and /fan-out must not set it. Residual: an agent can still set it, spoof session id, or edit the marker (as before this PR). Legacy writer writes the same keys.
active_ticket_write refuses a tree that is not the ops fork or a registered clone. It also refuses a symlink in the path, a repo owned by another user, and a malformed .git file. It prints the reason to stderr. If it prints a hint about the sandbox, the session may not write into the git dir. Tell the user. The old-layout marker still covers the tree. See AgDR-0222 for the allowlist the user can add.
active_ticket_write_legacy prints a one-line note and writes nothing when the old-layout path is blocked. An example is a tickets/<project> file where the per-worktree marker needs a directory. Report the note to the user. Do not delete the file.
Do NOT write the marker with the Edit or Write tool. .git is a protected path for those tools.
Other skills read the active ticket of the working tree through the same resolver. Run this from the tree you are in:
bash -c '. "$1/.claude/hooks/_lib-active-ticket.sh" && active_ticket_init "$PWD" && active_ticket_lookup "$PWD" && cat "$REPLY"' _ "$ops_root"
The command prints the marker (repo=, number=, title=, url=), or nothing when no marker covers the tree. It reads the marker in the tree's git dir first, then the old-layout marker.
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 confirmation that names the marker path, so the user sees which working tree this ticket governs:
Active ticket: <owner/repo>#<number> — <title>
Marker: <tree git dir>/apexyard-ticket (this working tree only)
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.
/start-ticket again in the same tree overwrites that tree's marker when the same session switches tickets, or after take over (step 4e). A linked worktree and its main clone hold separate markers.<git dir>/apexyard-ticket. git worktree remove does it for a linked worktree./start-ticket. A marker in the main clone does not govern a linked worktree..claude/, docs/, projects/*/docs/, any *.md) don't need a ticket. The skill is only required before touching source, config, or infra.current-ticket and tickets/<project> under .claude/session/ still work; this skill still writes them. A SessionStart notice lists them. A breaking release will stop reading and writing them.Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.
还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
WCAG 2.1 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
中文简介正在准备中。当前显示原文说明。
WCAG 2.2 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
中文简介正在准备中。当前显示原文说明。
Browse / search / show / stats AgDRs across the portfolio — recalls "have we decided this before?".
中文简介正在准备中。当前显示原文说明。
Analytics audit — SDK config, event naming, funnel completeness, dashboards. Deep-dive for /launch-check analytics.
中文简介正在准备中。当前显示原文说明。
Record per-PR architecture-review approval for design-artifact PRs (required by the architecture gate). ONLY on an explicit per-PR architect "approved".
中文简介正在准备中。当前显示原文说明。
Record per-PR design-review approval (UI merge gate). ONLY on an explicit per-PR designer "approved".
中文简介正在准备中。当前显示原文说明。