A quick note: this was supposed to be Day 4 on March 5th. I came down with influenza — the kind that keeps you horizontal for four days — and the challenge paused. I'm back now, picking up where I left off. Day 4, dated March 9th.
I'd had the idea for this one before I got sick. Every PM I know has a roadmap health problem — not that they don't have a roadmap, but that they can't tell, at a glance, how healthy it is. Are the items well-defined? Do they have owners? Are they anchored to outcomes or just someone's pet project? Is anything embarrassingly stale?
The answer is usually "we know it's a mess, we just don't have a clean way to see it." So I built one.
What it does
You paste in your roadmap items as free text, or upload a CSV — whatever you actually have. The tool sends them to Claude with a structured prompt, gets back a health score for each item plus an overall score from 0–100, and renders a results view with flag badges, expandable reasoning, and a concrete recommendation for each item.
Every analysis is saved to a local SQLite database. The History tab shows a bar chart of all your scores over time — color-coded green, amber, and red — with a clickable list below it. You can reload any past analysis instantly.
The three score zones, shown as a score circle:
Healthy ≥ 70
At Risk 40–69
Critical < 40
The five flags
Claude can assign any combination of five flags to each item. Each one has a color so patterns jump out across the table immediately:
- Vague — the item is too ambiguous to estimate or build. "Improve performance" is vague. "Reduce P95 API latency below 200ms" is not.
- Stale — the item has been sitting untouched long enough that its assumptions are probably wrong. Claude infers this from date fields and context clues in the description.
- Unanchored — there's no connection to a business outcome, OKR, or user problem. It's a feature in search of a reason.
- Dependency Risk — the item depends on something outside the team's control: another team, a vendor, regulatory approval. If the dependency slips, this slips.
- Stakeholder-Driven — the item exists because someone important asked for it, not because it was prioritized on merit. Sometimes that's fine. Often it isn't.
Why these five? They're the most common failure modes I've seen across roadmaps — the things that, when you look back at a shipped quarter, explain why half the items got deprioritized or delivered wrong. They're also things Claude can reasonably infer from short item descriptions without needing internal context.
The prompt design
Getting consistent, parseable JSON from Claude required some care. The system prompt is short and assertive: "You are a product management expert. Analyze the roadmap items below and return ONLY valid JSON, no other text."
The user prompt sends all items as a numbered list — including any available fields like description, owner, due date, and status — and asks for a specific JSON shape:
overallScore— 0 to 100 integersummary— 2–3 sentence executive summarytopIssues— array of 3 strings, the most critical problemsitems— array withname,score,flags,reasoning, andrecommendationfor each item
Even with explicit instructions, Claude occasionally adds a brief explanation before the JSON. The parser strips everything before the first { before calling JSON.parse. If no valid JSON is found, the server returns a 500 with the raw text for debugging.
The stack
The most interesting stack decision wasn't planned — it was forced. The original design called for better-sqlite3, the standard choice for SQLite in Node. But better-sqlite3 v9 has no pre-built binaries for Node 24 on Apple Silicon, and the native build fails because the Xcode Command Line Tools on this machine don't support C++20 (which Node 24's headers now require).
Rather than downgrade Node or fight the toolchain, I switched to node:sqlite — the SQLite module that ships built into Node 24 itself. Zero install. Zero compilation. The API is nearly identical to better-sqlite3: DatabaseSync, prepare(), run(), all(), get(). One of those moments where the platform has grown up and the right answer is to stop fighting it.
The database schema
Two tables. analyses stores the top-level result — input type, raw input, overall score, summary, and top issues as a JSON string. items stores each individual item result with a foreign key back to its analysis. Reading a full analysis is a single join.
Saves are wrapped in a manual BEGIN / COMMIT / ROLLBACK transaction since node:sqlite doesn't have a built-in transaction helper (unlike better-sqlite3, which has a nice db.transaction(fn)() pattern). It's four extra lines but it's explicit and readable.
The CSV parser
Real-world roadmap CSVs are inconsistent — some use "Name", some use "Feature", some use "Title". The parser tries a list of common column names and falls back to the first column if none match. It also normalizes optional fields like description, owner, due date, and status so they make it into the Claude prompt even if the column names are non-standard.
This matters because richer item descriptions give Claude more signal. An item with a name, description, owner, and due date will get a more accurate health assessment than one with just a name.
The history dashboard
The History tab is what turns this from a one-shot tool into a feedback loop. Every time you run an analysis, the score is persisted. The bar chart shows all scores over time, with bars colored green (≥ 70), amber (40–69), or red (< 40). The list below it shows date, score, item count, and a summary snippet — click any entry to reload that full analysis in the Results view.
The value is the trend, not the absolute number. A roadmap that scores 55 today and 70 next month is improving. A roadmap that scores 80 every quarter but never changes has a different kind of problem.
See the challenge entry → This post covers the technical depth. The Day 4 challenge entry has the condensed version alongside the full 30-day log.
What I'd do next
The tool runs locally — the obvious next step is a hosted version where you can invite your team and compare roadmaps across quarters. That requires auth, multi-tenancy, and a real deployment, which is a week of work, not a day.
The other thing I'd add is a diff view: when you run a second analysis on the same roadmap, show what changed — which items improved, which got worse, which are new. Right now the history gives you the trend at the top level; a diff would make the item-level changes visible.
And honestly, I'd improve the flags. Five categories is a good starting point, but real roadmap health is more nuanced. A flag for No Success Metric (how will you know when this is done?) and Size Risk (this item is too large to ship in one cycle) would both catch things the current set misses.
What I learned
Building this on Day 4 — after a week sick — was a good reminder that complexity compounds fast when you're not at 100%. The architecture is simple on paper: Express, SQLite, one API route, one Claude call. But each layer has edge cases, and when you're going quickly, they stack up.
The most useful habit I've built across the first four days is designing for legibility. A tool that shows you something you already knew, but clearly, is often more valuable than one that discovers something new. Most PMs know their roadmap has problems. The value here is making those problems visible, named, and sortable — so there's something to act on.