| name | roadmap |
| description | Update / create / reprioritise the product roadmap — add, remove, reorder milestones; renders a markdown table per milestone. |
| argument-hint | [add|remove|reorder|show] [item] |
| allowed-tools | Bash, Read, Edit, Write, Grep, Glob |
/roadmap — Product Roadmap
Manage the product roadmap as a single durable file. The skill is intentionally low-ceremony: a roadmap is a markdown file with milestones, each containing a table of items. /roadmap reads it, edits it, and renders it.
Path resolution
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"
projects_dir=$(portfolio_projects_dir)
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.
Write targets (see me2resh/apexyard#373 + #443): paths documented as projects/<name>/X in this skill are canonical adopter-facing forms — implement them in bash as "${projects_dir}/<name>/X". Never construct from "${PWD}/projects/...", "$(git rev-parse --show-toplevel)/projects/...", or a literal ./projects/... — those break in split-portfolio v2 mode where projects_dir resolves to a sibling repo.
REQUIRED per-block preamble (see #443): Claude executes each bash block as a separate shell invocation. The projects_dir assignment from the Path resolution section above does NOT carry into later blocks. Every bash block that writes to a projects/<name>/X path MUST start with this three-line preamble so it's self-contained:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
projects_dir=$(portfolio_projects_dir)
The Path resolution section's example sources the helper once for documentation purposes; it does not absolve later blocks from sourcing it themselves. Treat each bash fence as a fresh process.
Activated role
When /roadmap runs, activate the Head of Product role — they own roadmap prioritisation, strategic sequencing, and milestone decisions. For adding a specific item with a PRD, chain to /write-spec which activates the Product Manager instead. For items that require data-driven reprioritisation, involve the Product Analyst.
See .claude/rules/role-triggers.md for the full activation protocol.
Usage
/roadmap # show current roadmap
/roadmap add "OAuth login" Q3 P0 # add an item to a milestone
/roadmap remove "OAuth login" # remove an item
/roadmap reorder # interactive reordering
/roadmap show Q3 # show a single milestone
File location
Every roadmap lives at projects/<name>/roadmap.md inside your ops repo, where <name> matches an entry in apexyard.projects.yaml. Without --project, the skill asks which project's roadmap to operate on, listing the registry.
File format
# Product Roadmap
> Last updated: YYYY-MM-DD
> Owner: @octocat
## Now (current cycle)
| ID | Item | Priority | Status | Owner | Notes |
|----|------|----------|--------|-------|-------|
| RM-001 | CSV export | P0 | in-progress | @alice | GH#42 |
| RM-002 | OAuth login | P0 | not-started | @bob | depends on AgDR-0007 |
## Next (1-3 cycles out)
| ID | Item | Priority | Status | Owner | Notes |
|----|------|----------|--------|-------|-------|
| RM-003 | Multi-currency | P1 | not-started | – | |
## Later (3+ cycles out)
| ID | Item | Priority | Status | Owner | Notes |
|----|------|----------|--------|-------|-------|
## Done
| ID | Item | Shipped | PR |
|----|------|---------|-----|
| RM-000 | Health endpoint | 2026-04-05 | #41 |
The default milestones are Now / Next / Later / Done (Now-Next-Later format). Custom milestones (Q1/Q2/Q3 or v1.0/v1.1/v2.0) are also accepted.
Process
show (default)
-
Read the roadmap file
-
Render each non-empty milestone as a table
-
Print a footer summary:
12 items · 5 in Now · 4 in Next · 3 in Later · 8 Done
add <item> <milestone> <priority>
- Read the roadmap file
- Find the next available
RM-NNN ID
- Append a row to the right milestone's table
- Default
Status: not-started, Owner: –
- Update the
Last updated date
- Write the file back
remove <item-or-id>
- Locate the row by ID or fuzzy title match
- Confirm before deleting
- Remove the row, update the date
reorder
- List items in the chosen milestone with their position
- Ask for the new ordering (e.g.
2,1,3,4)
- Rewrite the table in the new order
move <id> <new-milestone>
- Find the row by ID
- Remove it from the current milestone
- Append it to the target milestone
- Update the date
close <id>
- Find the row by ID across Now / Next / Later
- Move it to Done
- Add
Shipped date and PR column reference (ask user for PR # if it can't be inferred)
Linking to GitHub Issues
If the user passes --with-issues, also create or update tracker issues to
mirror the roadmap. Dispatch through tracker_create (#670 / AgDR-0072,
extended by the #709 creator sweep) so the issues land in this project's
tracker — GitHub, GitLab, or a custom CLI. For a GitHub adopter this runs
gh issue create exactly as before.
Resolve the target repo
tracker_create's first argument is a concrete owner/repo slug — it is not
resolved for you. Determine it once, before the loop, the same way the other
creator skills (/feature, /bug, /task) do:
Read .claude/session/current-ticket to determine which repo we're working in.
If no active ticket, check apexyard.projects.yaml for managed projects. If only
one project, use it. If multiple, ask:
Which project should the roadmap issues be filed in?
If no projects are registered, ask for the repo in owner/repo format. Pass the
resolved value (e.g. me2resh/apexyard) as the first tracker_create argument —
never the literal {owner/repo} placeholder.
tracker_lib="$(r="$PWD"; while [ -n "$r" ] && [ "$r" != / ]; do \
[ -f "$r/.claude/hooks/_lib-tracker.sh" ] && { echo "$r/.claude/hooks/_lib-tracker.sh"; break; }; \
r="${r%/*}"; done)"
. "$tracker_lib"
owner_repo="{resolved owner/repo}"
body_file="$(mktemp)"
printf 'Tracking issue for roadmap item %s\n' "{RM-NNN}" > "$body_file"
result="$(tracker_create "$owner_repo" "[Roadmap] {item}" "$body_file" "roadmap,{priority}")"
rc=$?
rm -f "$body_file"
if [ "$rc" -eq 3 ]; then
echo "Tracker is 'none' (shape-only) — nothing was created in a tracker." >&2
printf '%s\n' "$result"
elif [ "$rc" -eq 0 ] && [ -n "$result" ]; then
ref="$(printf '%s' "$result" | jq -r '.ref')"
else
echo "Issue creation failed for '{item}' — check the tracker CLI / auth." >&2
fi
Then write the issue reference (${ref}) back into the Notes column (only on the
rc -eq 0 path above — the kind=none and failure paths leave Notes untouched).
Output format (show)
ROADMAP — example-app — last updated 2026-04-06
================================================
NOW (current cycle)
| ID | Item | Priority | Status | Owner | Notes |
|--------|-----------------|----------|-------------|--------|----------------------|
| RM-001 | CSV export | P0 | in-progress | @alice | GH#42 |
| RM-002 | OAuth login | P0 | not-started | @bob | depends on AgDR-0007 |
NEXT (1–3 cycles out)
| ID | Item | Priority | Status | Owner | Notes |
|--------|-----------------|----------|-------------|-------|-------|
| RM-003 | Multi-currency | P1 | not-started | – | |
LATER
(empty)
DONE (last 5)
| ID | Item | Shipped | PR |
|--------|-----------------|------------|-----|
| RM-000 | Health endpoint | 2026-04-05 | #41 |
Summary: 3 active · 0 later · 1 done
Owner: @octocat
Priorities
| Priority | Meaning |
|---|
| P0 | Must ship in current cycle |
| P1 | Should ship soon |
| P2 | Nice to have |
| P3 | Speculative |
Status values
| Status | Meaning |
|---|
| not-started | No work yet |
| in-progress | Active work, branch/PR exists |
| blocked | Waiting on something — note in Notes column |
| in-review | PR open, awaiting review |
| done | Shipped (auto-moves to Done milestone) |
Rules
- One file = one project's roadmap — no shared roadmaps across projects
- IDs are stable —
RM-001 never changes once assigned
- Done is append-only — items move to Done, never out of it
- Update
Last updated on every write
- Confirm destructive ops —
remove, reorder ask first
- One roadmap per project — always write to
projects/<name>/roadmap.md in the ops repo
- Don't auto-create issues unless asked —
--with-issues is opt-in
- Preserve markdown formatting — don't reflow rows or change column widths unnecessarily
Related skills
/write-spec — once a roadmap item is approved, write its PRD
/stakeholder-update — pulls "Now" and "Done" sections to summarise progress
/idea — for ideas not yet on the roadmap
Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.