Skip to content

Rolling out the roadmap board — operator runbook

One-time setup, then the weekly rhythm. Written 2026-10-08 against plans/roadmap-threads-and-clickup-sync.md. Everything here is reversible except the two places that say otherwise.

Run every command from the spine checkout. python must be 3.11 or newer.


Phase A — land the Git work

Five branches, stacked. Open one pull request at a time and merge it before opening the next, so each review sees only its own change.

Order Branch What it is
1 docs/roadmap-revision-2026-10-08 evidence refresh: a decision recorded, finished work marked done, board state on the cards
2 docs/roadmap-threads the plan, then the restructure into domain threads
3 docs/roadmap-biz-thread the business development and reach thread
4 docs/roadmap-clickup-push the ClickUp adapter: bootstrap and push
5 docs/roadmap-clickup-pull pull, propose, the freshness budget, the governed-job proposal

Branches 3 and 5 are siblings off 2 and 4. They have been checked to merge cleanly in either order.

For each, in order:

git push -u origin <branch>
gh pr create --base main --head <branch> --title "<the commit subject>" --body-file -

Before merging number 2, decide the identifier rename with whoever reviews: items become TECH-01, AI-01, BIZ-01, OPS-01 and decisions become DEC-01. Every old identifier keeps working as a link anchor, and _docs/adr/0002-roadmap-threads-and-identifiers.md holds the full map.

One thing must happen before the weekly-deck branches merge. The deck's roadmap index (.claude/skills/dev-weekly-report/roadmap_index.py, on the open report branches) only accepts the old identifier shape. Widen its pattern to [A-Z]{2,5}-[0-9]{2,3} or the deck will silently group no roadmap items.


Phase B — fill in the placeholders

The roadmap README lists every value still marked placeholder:. Validation refuses to render an adopted roadmap that still has one, so these must be real before the dev meeting adopts it. They are fine to leave while it is a proposal.

Four thread owners, in roadmap/roadmap.toml:

Thread Needs
Technical development one accountable person
Agentic integration and governance the HSSMeister governor, by name
Business development and reach the business development lead and the marketing steward
Lab operations and access the spine maintainer, with the organisation admins

Then the four business milestone completion tests and the two business outcome tests, also in roadmap.toml, and the owner line on each BIZ- item and on AI-09. After editing:

python scripts/roadmap.py          # re-render
python scripts/roadmap.py --check  # must print ok

Phase C — set up ClickUp, once

C1. Make an automation member

In the HSSM Ecosystem workspace, add a member for the automation. Give it access only to the Spaces configured in roadmap.toml: HSSM roadmap and Venture today. Generate a personal token from that member's own settings.

Use that token, never your own. Revoking the automation then never locks a person out and never touches anything else.

export CLICKUP_API_TOKEN=...   # the automation member's token

Never commit a token. A test fails the build if a token-shaped string is ever committed.

C2. See the plan before anything is created

python scripts/roadmap_clickup.py bootstrap --dry-run

This writes nothing. It prints every object it would create.

C3. Create the Spaces, folders and lists

python scripts/roadmap_clickup.py bootstrap

This creates every Space configured in roadmap.toml, one Folder per thread, one List per milestone and a Decisions list in the default Space, then writes roadmap/state/clickup-map.json, which maps roadmap identifiers to ClickUp identifiers and holds no credential. Running it twice changes nothing.

It will finish by printing what the API cannot do. That is expected: ClickUp's v2 API cannot create a custom field or a custom status.

C4. Create one custom field in every Space

ClickUp's v2 API cannot create a custom field, so exactly one has to be made by hand in each configured Space. At Space level, create:

Field name Type in the interface
Roadmap ID Text

That is the key push matches a task on. Without it every run would create the tasks again instead of updating them, which is why it is the one hard requirement.

C5. Optional: the other ten fields

None of these blocks anything. push sets whichever exist and leaves the rest alone, so add them when a view or filter needs them, not before.

Field name Type What it buys you
Thread Dropdown group or filter by thread; the Folder already shows it
Milestone Dropdown group or filter by milestone; the List already shows it
Outcome Labels filter by the goal an item serves
Closes gap Labels filter by the gap an item closes
Legacy ID Text find an item by its 2026-09 identifier
Owner role Text see the accountable role without opening the task
GitHub links Text Area the linked plan, issues and pull requests
Source file Website jump to the item's TOML on GitHub
Intent revision Text which commit the wording came from
Propose intent change Checkbox needed for propose; tick it to ask for a wording change

The four choice fields match their options by name. bootstrap prints a warning naming any option it could not find, and skips that one field.

C5b. Statuses

Optional too. The adapter already understands ClickUp's defaults, mapping to do and open to backlog, in review to in progress, and complete and closed to done. bootstrap names any status it cannot map, and an item in one of those reads as backlog.

For the roadmap's own vocabulary, set each Space's statuses to:

Backlog  |  Ready  |  In Progress  |  Blocked  |  Done  |  Cancelled

C6. Confirm

python scripts/roadmap_clickup.py bootstrap

It should create nothing, and report only the optional fields you chose to skip. If it names Roadmap ID, that field's name or type does not match.


Phase D — the first sync

python scripts/roadmap_clickup.py push --dry-run   # read the plan
python scripts/roadmap_clickup.py push

Expect roughly 290 API calls for the full set of items and decisions. The free tier allows 100 per minute, so this takes about three minutes; the adapter backs off and retries on its own. Nothing is lost if you interrupt it, because a rerun only does what is still missing.

Then read the board back and commit the reading:

python scripts/roadmap_clickup.py pull
python scripts/roadmap.py
git add roadmap/state roadmap/*.md roadmap/threads
git commit -m "chore(roadmap): board reading $(date +%F)"

From this point the roadmap pages render from the board rather than from the offline fixture, and each page prints which reading it used and when it was taken.

Finally, clear the planned flag on the roadmap-board-snapshot budget in scripts/drift_scoreboard.py, in the same change that lands the first reading. The budget then goes amber if readings stop for ten days.


Phase E — the weekly rhythm

When Do Result
Any time Move a task, assign it, mark it blocked, comment Board state. Git never overwrites it.
Weekly pull, then roadmap.py, then commit the reading The pages and the thread paths reflect the board
After a merge that touched roadmap/ push The board shows the agreed wording
Weekly propose Writes roadmap/state/proposed-intent.md, a ready pull-request body

To change intent, edit the TOML and open a pull request. To propose a change without Git, tick Propose intent change on the task and edit it; the next propose run turns that into a Git edit for a person to open. Editing a task description without ticking the box is pointless: the next push overwrites it, because Git owns that text.

pull and propose cannot write to ClickUp. The client refuses, so a regression fails loudly rather than corrupting the board.


Phase F — take to the dev meeting

  1. Adopt the roadmap. It is still a proposal. Adoption flips status in roadmap.toml and requires every placeholder to be filled.
  2. Name an owner per thread, which is the decision that has been open longest.
  3. The two business outcomes, Known and Translated, change the vision, so the decision on who approves a vision change governs them. Until then the thread renders with a proposed banner.
  4. Who approves a vision change, which the point above depends on.

If something goes wrong

Symptom Cause Fix
no workspace named 'HSSM Ecosystem' the token belongs to a member who cannot see it use the automation member's token, or pass --workspace
push refuses, naming fields the custom fields do not exist yet finish C5, then rerun bootstrap
warning: ... has no option named ... a dropdown or label option is missing add the option, rerun push
A task appears twice its Roadmap ID field was cleared set the field back, or delete the duplicate; push keys on that field alone
pull warns not in the roadmap sources a task was created by hand on the board delete it, or file the item in Git
An item is blocked with an odd reason marked blocked with no waiting-on edge add the edge on the board, or say what it waits on

To start the board over: delete the configured Spaces in ClickUp, delete roadmap/state/clickup-map.json, and run bootstrap again. The roadmap sources are untouched by any of this.