Roadmap
π Prompt-engineering and project-roadmap plugin that crafts professional xml prompts and picks, surveys, and tracks your next tasks.
npx -y skills add V-Songbird/foreman --skill roadmapAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 29 days oldThe repository was created 29 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 19 stars19 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Ongoing entry point for a project's ROADMAP.jsonl. Pick the next task to work on (reasons about dependencies and file-touch collisions like a software architect, then crafts a self-contained handoff prompt), add a new task, or review roadmap status.
SKILL.md
23.5 KB, as published. Nobody here has run it
foreman:roadmap β pick, add to, or review the project roadmap
All reads/writes to ROADMAP.jsonl at the project root go through
${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js β never Read/Edit the file
directly, the script enforces id computation and parse-before/after-write
mechanically. Skim ${CLAUDE_PLUGIN_ROOT}/roadmap-schema.md if you need
field semantics beyond what's obvious from the names.
Pre-check: if ROADMAP.jsonl doesn't exist at the project root, tell
the user to run /foreman:init first and stop here.
Call 1 β menu
Q1 β "What do you need?" Options:
Pick the next taskβ read the roadmap, reason about what to work on next, craft a handoff prompt for it.Add a taskβ append a new entry to the roadmap.Review statusβ read-only summary of where every task stands.
If args were provided and read like a task description rather than a question, treat it as a seed for "Add a task" and skip this call. If they read like a pick request or a hint about what to pick ("what's next on auth", "something quick I can finish today"), go straight to "Pick the next task" with the hint in hand β that branch says what to do with it.
Branch: Pick the next task
This branch does not investigate the codebase. At all. No Read, no
Grep, no exploring files to confirm or expand what an entry says. The
picked entry's own fields are the only input to the prompt. Verifying
those claims against reality is the handed-off session's job, at the start
of its work β that's exactly what the <truth_grounding> block in
prompt-template.md exists for. Picking a task should be fast: one
mechanical call, one question, assemble, done.
-
node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js next-candidatesβ already filtered (unblocked:plannedwith everydepends_ondone), ranked (most open work waiting behind it first βunblocks_totalcounts the whole dependency chain, not just direct dependents β then collision-free before colliding, then oldest), limited to 3 by default, with acollisionflag per candidate (itstouchesoverlaps a currently-in_progresstask's). Do not re-derive this by callinglistand reasoning over the whole file yourself β that's exactly the costnext-candidatesexists to cut.In the same message, also run
node ${CLAUDE_PLUGIN_ROOT}/scripts/render-sections.jsβ its output is project-level, not task-level, and step 3 needs it no matter which candidate wins, so batching the two mechanical calls saves a round trip. This satisfies the template's craft-time step 0 β don't run it again at assembly, reuse this call's output (and surface itswarningsthen, if any).If args carried a pick hint, pass it to the script instead of filtering yourself:
--hint "<the hint's words>". Relevance ranking is mechanical β the script scores each candidate by how many of the hint's words appear in its fields and sorts by that first, so take the returned order as given, same as the no-hint case. If the result sayshint_matched: false, say in one line that nothing matches the hint and present the returned top 3 as usual β never invent a candidate to satisfy a hint, and never let a hint surface a blocked or non-plannedentry (the script's filter already decided that).Never paste or print this JSON output into your chat response. It's input to the next step, not something to show β the full
what/touches/notes/unblocksfields are context for you to weigh candidates and craft the eventual handoff prompt, not content a human needs dumped in front of them before they've even picked a task. -
Go straight to Q1 below β no narrative recap of the candidates in prose first, the question is the presentation.
Finish-first check: if the script's in_progress array is non-empty,
work already started somewhere β offer to finish it before starting
something new. Those entries take the top option slot(s) in Q1 (at most 2;
oldest updated_at first), labeled Resume: <title> (<id>), with the
first one carrying (Recommended). Description: why plus
"in progress since <updated_at>" β plus, when the entry's notes carry the
background-agent marker (see step 5's delivery bullet below), "will try
resuming the original agent first". Preview: same fields as the candidate
preview below, plus a short excerpt of the entry's notes (prior
findings) β the reason a resume is worth previewing at all. Planned
candidates fill the remaining slots. This is a suggestion, never a gate β
picking a planned candidate proceeds exactly as before.
Single-option skip: when the menu would hold exactly one option β
candidates and resume entries combined β skip Q1 and take that entry as
the pick. Q2 then opens with it instead: prefix Q2's question with the
entry's title (<id>) and its why restated per Q1's description
rule below, so the user can still veto or redirect through Q2's escape.
Two or more options of any kind ask Q1 as usual.
Q1 β "Which task next?"
Options, one per candidate (already ranked β take the order as given,
hint or not; resume options lead when in_progress is non-empty, per the
finish-first check above):
- Label:
<title> (<id>). The first-ranked candidate's label gets(Recommended)appended β unless a resume option already carries it β it's first for a reason (most open work behind it, hint relevance, or oldest on a tie), say so with the tag instead of making the user infer it from list order alone. - Description: the entry's
whyrestated in your own everyday words, one sentence, written for a teammate who has never seen this codebase β never the field pasted verbatim. Never foldwhat/touches/notes/unblocksinto the description β none of that is a pick-time decision input if the session isn't ground-truthing anyway (that'sforeman:survey's job); it only bloats the dialog. Add "(possible file overlap with in-progress work)" to the description ifcollision:trueβ still a caution, not a blocker. - Preview: plain text built from the entry's
title,why,what,depends_on, andupdated_at, capped at ~10 lines. This is where the detail the description bullet deliberately excludes goes instead β visible only when the user focuses the option, never printed into chat. It supplements the description rule above, never replaces it. Resume options get the same fields plus thenotesexcerpt noted in the finish-first check above. A harness whoseAskUserQuestiondoesn't supportpreviewsimply ignores the field β no fallback logic needed.
Plus the standard escape to describe something else not on the list.
If the user waves a candidate off as "not yet", "later", or "not until
X" β rather than just picking a different one β offer to mark it
deferred so it stops resurfacing as a recommendation:
echo '{"id":"<id>","status":"deferred","notes":"deferred: <trigger>"}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js update-status
(capture the trigger they named in notes). Then re-run
next-candidates and re-ask Q1. Don't defer on your own judgment β only
when the user signals it; a task that's merely lower-priority stays
planned.
Resume via the original agent, before Q2: if the picked option was a
resume entry and its notes carry the background-agent marker (the phrase
"background agent" followed by the backticked id β written by step 5's
delivery bullet below), try continuing that exact agent before asking
anything else. Pull the id out of the marker and call SendMessage with
to: "<id>" and a short re-brief (current status?, plus any new context
the user just gave) instead of the destination question and prompt-crafting
steps below. On success, that is the resume β relay what the agent
reports and stop here; the resumed agent owns closing its own entry the
same as any other handoff. On any failure (success:false, or the tool
isn't available), fall back silently to the flow below exactly as if
there were no marker β go on to Q2 and craft the re-crafted prompt (Resume
variant, step 3) from the entry's notes. Never surface the SendMessage
failure itself; the re-craft path isn't a degraded fallback, it's the
original design.
Q2 β "How do you want to run this?" β ask this now, before the prompt exists, not after. There is nothing to preview yet; the destination decides how the prompt gets built and delivered, not the other way around. Options, in this order:
Execute here (Recommended)β run it in this session. Leads because it's the common case: pick a task, work it, done β no extra hop through a clipboard or a second agent.Execute with a background Agentβ offload it, get notified on completion β best for orchestration, where this session owns the commitsCopy prompt to clipboardβ just get the text, no execution
The spawn_task ban applies here β see prompt-template.md's "Delivery
mechanics" section.
Q3 β execution mode, asked only when Q2's answer was Execute here.
The other two destinations skip it entirely. "How should it run here?" β
options and their free-text rule are prompt-template.md's "Delivery
mechanics" section, verbatim.
Run now, no tracking creates no task row, so neither task-created.js
nor task-completed.js fires: the entry's opening and its close gate both
fall back to the prompt's own embedded instructions, exactly as on the
clipboard path. Say that in one line when the user picks it, so a project
running taskCloseGate: "block" knows the gate is not in play this time.
-
Craft the handoff prompt using
${CLAUDE_PLUGIN_ROOT}/prompt-template.md's XML structure, straight from the candidate's fields β no verification pass:-
task_contextgoal βtitle+why -
background/contextβwhat, plus the candidate'snoteswhen non-empty, attributed as prior recorded findings on this entry (a survey verdict, a defer trigger, a previous session's evidence) β the candidate already carries them, so this costs nothing and stops the destination re-deriving what someone already wrote down -
relevant_filesseed βtouches, passed through as-is (area-level hints, not confirmed file:line ranges β that's fine, don't upgrade them yourself) -
depends_on_docsβ when the candidate carries a non-empty one (the resolved decision-doc paths of its dependencies), list those paths in the handoff (inbackground/context) so the destination reads those decisions before starting, instead of silently re-deciding a settled question. Omit when empty. -
task_rules' first bullet defaults to: "Explorerelevant_filesfirst (seetruth_groundingabove)." β short on purpose,truth_grounding(fixed, right above it in the same prompt) already carries the full verify-before-acting mandate, restating it here would just be the same sentence twice. The remaining bullets, tone, and the verification command β ask the same waycraft-promptdoes only if genuinely not inferable from the entry; don't turn this into a second interview. One exception: when Q3 pickedTasks from the checks, the verification commands are what the split cuts on, so gather them properly instead of settling for one inferred command β a single check yields a single task. -
Decision entry β when the candidate carries
kind: "decision"(surfaced bynext-candidates), this task resolves an open question, not a build. Make the firsttask_rule(before the explore bullet): "This is a decision, not a build: resolve the open question β state the choice and the reason it wins over the alternatives β and do not write implementation code for it. The deliverable is the decision." This is the measured lever β without it, a decision-shaped entry gets implemented straight into code a real fraction of the time. It pairs withdecision_log: a decision entry's product is its doc, so when the<decision_log>block is present (below), the recorded choice lands there and the close carries thedocpath rather than"none". An entry with nokindkey is an ordinary build β add nothing. -
Model fit β a DISPATCH-time recommendation, judged now from this candidate's own
touches/what(recorded fields only, same no-investigation rule as the rest of this branch), never at pick time or when the entry was created. If.foreman/config.jsonpins a concretetargetModel(already in hand from the render-sections.js call in step 1), that project declaration is the recommendation; otherwise (inherit, the default) recommend a model perprompt-template.md's "Model fit" note β including its grounded caution for awhatthat reconciles stale, conflicting, or renamed references, which hit a real capability cliff on Haiku in every prompt format tested. For a background-Agentor clipboard destination, confirm it withcraft-prompt's Call 6 question, asked here once the verification checks are known and before assembly β same wording, same slots and substitutions (Fableincluded only whenfableEnabledistrue). The answer keeps its two jobs: it tunes the assembled prompt's elaboration, and a backgroundAgentdispatch passes it as that call's literalmodel(haiku/sonnet/opus/fable, omitted for inherit/varies). AnExecute hererun asks nothing β the work runs in this session, so no model choice exists and the resolvedtargetModeldrives elaboration unchanged. The operator's answer is the decision β never an automatic switch, never inside the assembled prompt itself (the target model never sees a description of its own expected failure modes), never a block, never a status or schema change. -
decision_logβ when step 1's render-sections result carriesdecisionLog.enabledtrue, include the template's<decision_log>block, substituting itsdirfor<dir>and this entry's id for every<entry-id>. Omit the block whenenabledis false (the default). This is the only thing that connects the entry's close to a decision doc, so itsdocfield on the close command (below) is paired with it. -
Add one more fixed paragraph right after
scope_discipline, naming this entry's id, so the destination session β not Foreman β is the one that flips it toin_progress: "This task is ROADMAP.jsonl entry<id>. Mark itin_progressbefore doing anything else β Foreman's picking flow deliberately leaves itplanneduntil you do:echo '{"id":"<id>","status":"in_progress"}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js update-statusWhen the work concludes, close the entry the same way β the status it actually earned (done,dropped,rejected) and your full findings innotes. If the work changed code, land the close inside the same commit instead of after it: stage everything (git add -A), close withstaged:true(the script folds the staged files intotouchesand stages ROADMAP.jsonl alongside), then commit once withForeman: <id>as the final line of the message β that trailer is the durable link between entry and commit, so no sha gets recorded and the roadmap never trails uncommitted:echo '{"id":"<id>","status":"<status>","staged":true,"notes":"<findings>"}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js update-statusA task that changed nothing (pure investigation) closes without staging or trailer. If the commit already landed before the close, pass"commit":"<sha>"instead ofstagedβ that path still works and auto-folds touches from the commit's diff. When this prompt carries a<decision_log>block, adddocto that close call β the decision doc's path, or"none"when nothing was decided:echo '{"id":"<id>","status":"<status>","staged":true,"notes":"<findings>","doc":"<path or none>"}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js update-statusThe entry'snotesis where the depth lives; your final chat message states the outcome and points at the entry."Resume variant β when the chosen task came from
in_progress(the finish-first check), the entry was already started by an earlier session, so swap the paragraph's opening for: "This task is ROADMAP.jsonl entry<id>, already markedin_progressby an earlier session β don't re-mark it; earlier findings may sit in itsnotes(included below), read them before re-deriving anything." and keep the closing instructions (status earned, findings innotes, staged close with theForeman: <id>trailer) unchanged. Include the entry's existingnotesinbackground/contextβ for a resume they're prior findings, exactly the context the destination shouldn't have to rebuild.
Then run
prompt-template.md's mechanical gate on the assembled prompt (its "Mechanical gate" section has the exact call β pass--entry <id>, plus--resumefor a resumed pick) and fix every error until it passes before delivering. -
-
Foreman never marks the entry
in_progressitself. It staysplannedβ even after this prompt is assembled, delivered, or copied β until whichever session actually starts the work runs theupdate-statuscall embedded in step 3 above. Picking or copying a task is not the same as starting it; only the session that begins acting on it should say so. -
Deliver via whatever Q2 picked. Each destination's mechanics are
prompt-template.md's "Delivery mechanics" section; theExecute heresub-mode is Q3's answer, andsubjectderives from the entry'stitle. Whatever the destination, open the delivery message with a brief: one or two sentences in everyday words on what is about to change and why it matters, drawn from the entry'swhyandwhatonly, restated for a teammate who has never seen this codebase β never the fields pasted verbatim. The brief is chat-only; the assembled prompt keeps every field dense and untranslated. What this skill layers on top:-
Execute here: onRun now, no tracking, nothing mechanizes the entry's status, so the prompt's own embedded instructions carry it end to end. OnTasks from the checks, the entry paragraph rides the last row only, per the splitting section.On either tracked mode, Foreman's
task-createdhook marks the entryin_progressmechanically the moment the row carrying the embedded paragraph is created (it reads the entry id out of it) β finding it alreadyin_progresswhen the embedded instruction runs is expected, and re-running that update is a harmless no-op. Still useTaskUpdate(a separate, session-local tracker) for each row's ownin_progress/completedtransitions as you go. -
Background Agent: the tool result trails with the dispatched agent's id (
agentId: a<16 hex>). Capture it immediately with one annotate call, so a later session can resume this exact agent instead of re-crafting a prompt from its notes:echo '{"id":"<id>","notes":"dispatched to background agent `<agent-id>`"}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js annotate(the script date-stamps each appended note itself β don't write one in) The phrase "background agent" followed by the backticked id is the exact marker grammar the resume flow above parses β the id's own charset (a+ lowercase hex) never needs escaping. -
Clipboard: the same "Recommended model:" line
craft-prompt's Deliver step adds, when the confirmed model is concrete.
-
Hard rule β state this explicitly if the user pushes back: this skill
always asks before doing anything β it never silently executes a task, and
it never mentions or routes to any other plugin. "Do it now" means
picking Execute here above, not this skill deciding on its own.
Branch: Add a task
- Gather via free text:
title,why,what, and optionallydepends_on(existing ids) andtouches(path/area hints). Don't force the user through every field if they've already given enough in a one-line description (args or a natural request) β ask only for what's missing. If the task reads as resolving an open question rather than building something β the phrasing is a choice ("X or Y?", "decide whetherβ¦", "pick an approach") β passkind: "decision"so the pick flow later hands it a decide-don't-build rule. A build is the default; don't ask unless the entry genuinely looks like a decision. - Before writing it, check it isn't already tracked:
echo '{"title":"...","why":"..."}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js check-duplicateβ matches carry each entry's status. On a match, name the existing id/title/status in one line and ask whether to add anyway (AskUserQuestion:Add it anyway/Never mind); arejectedmatch means the user already declined this, say so. No match: add it without comment. Ask before the write, not after βaddhas no undo,title/why/whatare immutable once written, and the only exit isupdate-status dropped, which leaves the row in the file forever. echo '{"title":"...","why":"...","what":"...","source":"user","depends_on":[...],"touches":[...]}' | node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js addβ the script computes the id, validates required fields (including that everydepends_onid already exists), and confirms the file is still well-formed after writing.- Confirm back to the user with the new task's id and title (from the
script's JSON response), and surface any
warningsthe response carries, verbatim, in the same line.
Branch: Review status
Read-only. node ${CLAUDE_PLUGIN_ROOT}/scripts/roadmap.js list --summary
β id/title/status/depends_on per entry, which is everything the render
below needs; the full entries' prose would multiply the payload for
nothing on a large roadmap. Render a compact list grouped by status
(in_progress first, then planned β noting which are blocked and on
what, derivable from depends_on plus the other entries' statuses β then
deferred, then done, dropped, rejected last). When a planned
entry's blocker resolves to an entry that is dropped or rejected β or
to an id no entry has β say so explicitly rather than calling it plain
"blocked": it will not reappear in the pick list until that dependency is
moved back with update-status, or its edge is removed with
update-deps's remove_depends_on. If any deferred
entries exist, fetch just those in full for the "waiting on what" word β
list --ids <deferred ids> β drawn from their why/notes. No writes,
no further questions.