Domain: Roadmaps — Function Reference
Manages educational roadmaps sourced from roadmap.sh. Each roadmap is a directory at data/roadmaps/{id}/ containing {id}.md (frontmatter + markdown), {id}.json (visual graph of nodes/edges), and content/ subdirectory of topic files named {label}@{nodeId}.md. Progress is stored in SQLite. Five in-memory LRU caches with file-signature invalidation.
MCP Tools Exposed
Tools exposed through unified controller. Key names: roadmap_list, roadmap_get, roadmap_nodes, roadmap_node, roadmap_register, roadmap_validate, roadmap_validate_all, roadmap_repair, roadmap_repair_all, roadmap_parse_yaml, roadmap_progress, roadmap_update_progress, roadmap_update_progress_batch, roadmap_check_prerequisites, roadmap_next_topic, roadmap_analyze_structure, roadmap_export_to_gsd.
| Tool Name | Description | Key Inputs | Returns |
|---|---|---|---|
roadmap_list |
List all available roadmaps | none | { success, count, roadmaps[] } |
roadmap_get |
Get roadmap markdown content | roadmapId: string |
{ success, roadmap: { id, title, description, metadata, content, path } } |
roadmap_register |
Register a custom roadmap | roadmapId, title?, description?, markdown?, graph?, overwrite? |
{ success, message, roadmap } |
roadmap_nodes |
Get merged graph+content nodes | roadmapId: string |
Full node list with dependencies, resources, content |
roadmap_node |
Get a specific node with full content | roadmapId, nodeId |
{ success, node } |
roadmap_validate |
Validate graph+content integrity | roadmapId: string |
{ success, status, score, issues, graph, content } |
roadmap_validate_all |
Validate all or selected roadmaps | roadmap_ids?, include_passing?, max_results? |
Aggregated quality report |
roadmap_repair |
Repair roadmap data issues | roadmapId, dry_run?, fix_graph?, fix_content?, create_missing_content?, remove_self_loops? |
{ success, dryRun, changes, actions[], validation } |
roadmap_repair_all |
Batch repair roadmaps | Same as repair + confirm_apply_all?, only_statuses? |
Batch repair summary |
roadmap_progress |
Get node progress for roadmap | roadmapId: string |
{ success, roadmapId, progress: { [nodeId]: status } } |
roadmap_update_progress |
Update single node status | roadmapId, nodeId, status |
{ success, message, roadmapId, nodeId, status } |
roadmap_update_progress_batch |
Update multiple node statuses | roadmapId, updates[]: [{ node_id, status }] |
{ success, roadmapId, updated, failed, results[], errors[] } |
roadmap_check_prerequisites |
Check node prerequisites met | roadmapId, nodeId |
{ prerequisites: { total, completed, inFlight, notStarted, missing[], satisfied, statusByNode } } |
roadmap_next_topic |
AI-scored next topic recommendation | roadmapId, currentNodeId? |
{ nextTopic, recommendation, alternatives[] } |
roadmap_analyze_structure |
Structural stats and critical path | roadmapId, depth? |
{ stats, criticalPath[], topTopics[] } |
roadmap_export_to_gsd |
Export roadmap to GSD project | roadmapId, projectName, pace? |
{ success, projectPath, topicCount, totalNodes, pace, validation } |
Core Functions
getRoadmapDir(roadmapId)
Purpose: Return absolute path to roadmap folder.
Parameters: roadmapId: string
Returns: string path. Validates ID against ROADMAP_SAFE_ID_PATTERN.
getRoadmapPath(roadmapId)
Purpose: Return path to {id}/{id}.md.
Returns: string
getRoadmapJsonPath(roadmapId)
Purpose: Return path to {id}/{id}.json.
Returns: string
registerRoadmap(roadmapId, options)
Purpose: Create a new custom roadmap directory with markdown and graph files atomically (temp dir → rename).
Parameters: roadmapId: string, options: { title?, description?, markdown?, graph?, overwrite? }
Returns: { success, message, roadmap: { id, title, description, path, graphPath, contentDir } }
Side effects: Creates directory at ROADMAPS_DIR/{id}/, writes .md and .json, invalidates caches.
Notes for rewrite: Graph integrity is validated before write (no invalid IDs, no invalid edges, no duplicate node IDs).
listRoadmaps()
Purpose: Scan ROADMAPS_DIR for subdirectories with matching .md files.
Returns: { success, count, roadmaps[] } sorted by title.
getRoadmap(roadmapId)
Purpose: Read and parse roadmap markdown with LRU cache keyed on file mtime+size.
Returns: { success, roadmap: { id, title, description, metadata, content, path } }
getRoadmapNodes(roadmapId)
Purpose: Merge JSON graph nodes with content map; build dependency/dependent adjacency lists.
Returns: { success, roadmapId, totalNodes, topicCount, subtopicCount, todoCount, checklistCount, withContent, totalResources, nodes[], edges[], connections[], validationWarnings }
Notes for rewrite: Filters to MEANINGFUL_NODE_TYPES = {topic, subtopic, todo, checklist}. Each node gets dependencies[] and dependents[] from edge traversal.
getRoadmapNode(roadmapId, nodeId)
Purpose: Fetch a single node with full raw content.
Returns: { success, node: { ...node, fullContent } }
validateRoadmap(roadmapId)
Purpose: Integrity check: graph anomalies (orphan edges, self-loops, duplicates, invalid IDs) + content mapping (unmatched files, missing node content).
Returns: { success, roadmapId, status: 'pass'|'warn'|'fail', score, summary, issues, graph, content }
Algorithm: score = 100 - (errors × 25) - (warnings × 5), clamped to 0.
validateAllRoadmaps(options)
Purpose: Batch validate all or selected roadmaps; aggregates pass/warn/fail counts.
Parameters: options: { roadmap_ids?, include_passing?, max_results? }
Returns: Aggregated report with sorted results (fail < warn < pass, then by ascending score).
repairRoadmap(roadmapId, options)
Purpose: Fix detected issues: remove invalid/duplicate nodes+edges, move invalid/duplicate/unmapped content files, create placeholder content for missing nodes.
Parameters: roadmapId, dry_run? (default true), fix_graph?, fix_content?, create_missing_content?, remove_self_loops?
Returns: { success, dryRun, applied, fixesAttempted, changes, actions[], validation }
Notes for rewrite: Default is dry_run=true — safe to call for audit. Write only when dry_run=false.
repairAllRoadmaps(options)
Purpose: Batch repair. Requires confirm_apply_all=true for non-dry-run global scope.
Returns: Batch summary with before/after status per roadmap.
parseRoadmapYaml(roadmapId)
Purpose: Extract and parse YAML frontmatter (or fenced code block) from roadmap markdown.
Returns: { success, data } or error.
getProgress(roadmapId)
Purpose: Retrieve all node progress statuses from SQLite.
Returns: Promise<{ success, roadmapId, progress: { [nodeId]: status } }>
updateNodeProgress(roadmapId, nodeId, status)
Purpose: Set a single node’s progress status.
Returns: Promise<{ success, message, roadmapId, nodeId, status }>
updateProgressBatch(roadmapId, updates)
Purpose: Update multiple node progress statuses.
Parameters: roadmapId: string, updates: [{ node_id, status }]
Returns: Promise<{ success, roadmapId, updated, failed, results[], errors[] }>
checkPrerequisites(roadmapId, nodeId)
Purpose: Check if all graph dependencies of a node are completed/mastered.
Parameters: roadmapId, nodeId
Returns: Promise<{ prerequisites: { total, completed, inFlight, notStarted, missing[], satisfied, statusByNode } }>
getNextTopic(roadmapId, currentNodeId)
Purpose: AI-scored recommendation for the next topic to study.
Returns: Promise<{ success, nextTopic, recommendation: { reason, score, prerequisites[], unlocks[] }, alternatives[] }>
Notes for rewrite: See scoring algorithm below.
analyzeRoadmapStructure(roadmapId, depth)
Purpose: Stats on node types, edge complexity, resource counts, and topological levels.
Parameters: roadmapId, depth?: number (default 2)
Returns: { stats, criticalPath[], topTopics[] }
exportToGSD(roadmapId, projectName, pace)
Purpose: Export roadmap to a GSD project directory with TODOS.md and PLAN.md; creates/upserts GSD project in DB.
Parameters: roadmapId, projectName, pace: fast|normal|deep
Returns: Promise<{ success, message, projectPath, topicCount, totalNodes, pace, validation }>
initRoadmapDomain()
Purpose: Log roadmap count on startup.
Database Operations
| Operation | Query Pattern | Tables Used |
|---|---|---|
| Get progress | SELECT all progress for roadmap | roadmap_progress |
| Update progress | UPSERT node status | roadmap_progress |
| Create GSD project | INSERT or REPLACE | gsd_projects |
| Get GSD project by name | SELECT by name | gsd_projects |
All roadmap data (graph, content, metadata) is read from the filesystem, not SQLite.
Key Algorithms
LRU Cache with File Signature Invalidation
signature = mtime_ms + ":" + file_size
getCachedValue(cache, key, signature):
entry = cache.get(key)
if not entry or entry.signature != signature: return null
// LRU: move to end (delete + re-insert)
cache.delete(key); cache.set(key, entry)
return entry.value
setCachedValue(cache, key, signature, value, maxSize):
cache.delete(key) if exists; cache.set(key, { signature, value })
while cache.size > maxSize: delete oldest key
Five caches: metadata, document, graph, content, nodes. Each keyed by roadmapId.
Content File Parsing (loadContentFiles)
filename pattern: {label}@{nodeId}.md
for each file matching pattern:
extract resources via regex: - [@{type}@{title}]({url})
also extract standard markdown links
extract description: first non-heading paragraphs before resource list
store in contentMap: nodeId → { name, file, description, resources, rawContent }
Graph Integrity Analysis (analyzeGraphIntegrity)
nodeIds = Set of valid node IDs (no blank ID, de-duplicated)
for each edge:
skip if source/target blank (invalidEdgeCount++)
skip if filter excludes nodes
if source or target not in nodeIds: orphanEdges.push(...)
if source == target: selfLoopEdges.push(...)
if edgeKey already seen: duplicateEdges.push(...)
Next Topic Scoring (getNextTopic)
available = nodes where status != completed/mastered AND all prerequisites satisfied
for each available node:
score = directDependents * 10
+ transitiveDependents(depth=3) * 5
+ (type == "topic" ? 15 : 0)
+ (hasContent ? 5 : 0)
+ resources.length * 2
sort descending; return top + alternatives[1..3]
Topological Level Grouping (groupByLevel)
assigned = Set()
while assigned.size < nodes.length:
level = nodes where not assigned AND all dependencies assigned
if level is empty: break (cycle detected)
add all to assigned
push level IDs to levels[]
return levels
Graph Repair (repairRoadmap)
Nodes: deduplicate (keep first), drop blank IDs
Edges: drop blank source/target, drop orphan edges (nodes not in valid set),
drop self-loops (if removeSelfLoops), deduplicate
Content: move invalid-format files → _invalid/
move duplicate-nodeId files → _duplicates/
move files mapping to non-meaningful nodes → _unmapped/
create placeholder files for meaningful nodes with no content (if createMissingContent)