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:
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.
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¶
This writes nothing. It prints every object it would create.
C3. Create the Spaces, folders and lists¶
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:
C6. Confirm¶
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¶
- Adopt the roadmap. It is still a proposal. Adoption flips
statusinroadmap.tomland requires every placeholder to be filled. - Name an owner per thread, which is the decision that has been open longest.
- 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.
- 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.