CLAUDE.md
July 25, 2026 · View on GitHub
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repo layout (post v1.3.0 pivot)
roadmapsmith shipped as a Node CLI through v0.14.x. v1.3.0 pivoted to a skills-only distribution: the npm package is now a thin shim (install.js) that delegates to skills.sh, and the actual product is the SKILL.md bundle under skills/ and plugins/roadmapsmith/skills/.
- Current shipping surface —
install.js+skills/roadmap-init/SKILL.md+skills/roadmap-update/SKILL.md. Rootpackage.jsonfiles:limits the published tarball to those three (plus the plugins/ mirror). Sync betweenskills/andplugins/roadmapsmith/skills/is enforced byscripts/sync-skills.js(called fromversionandprepublishOnlylifecycle scripts) and.github/workflows/mirror-check.yml. - Archived CLI — the pre-pivot Node CLI (parser, generator, validator, sync, tests) lives untouched in
legacy/roadmap-skill/. It is not shipped bynpm i roadmapsmithanymore; it stays in the repo for reference and manual local use.
Working on the legacy CLI
If you need to touch the archived CLI (rare — bug fix in the SKILL.md prompts is usually the right move), all commands run inside legacy/roadmap-skill/:
cd legacy/roadmap-skill
npm install
npm test # runs all tests via node --test test/*.test.js
node bin/cli.js --help
node bin/cli.js init --dry-run
node bin/cli.js generate --project-root . --dry-run --audit
node bin/cli.js validate --json --project-root ../.. --task <task-id>
node bin/cli.js sync --dry-run --project-root ../..
node bin/cli.js sync --audit --project-root ../..
node bin/cli.js doctor --project-root ../..
Run a single test file:
node --test test/validator.test.js
Test names are passed to filter:
node --test test/validator.test.js --test-name-pattern "passes when code"
Architecture (legacy CLI)
The Architecture / Invariants / Config sections below describe the archived CLI in legacy/roadmap-skill/. All src/, bin/, test/ paths are relative to that directory.
The pipeline is unidirectional:
io.walkFiles → generator.scanProject → model.createRoadmapModel → renderer.renderBody
↓
parser.parseRoadmap ← ROADMAP.md ← parser.upsertManagedBlock ← generator.generateRoadmapDocument
↓
validator.validateTasks → sync.applySync → ROADMAP.md (updated)
src/io.js — Filesystem primitives: walkFiles, detectLanguages, detectTestFrameworks, detectWorkspaces. Ignores node_modules, dist, etc.
src/generator/index.js — Orchestrates the full roadmap generation: scans the repo, builds P0/P1/P2 task candidates, merges with existing tasks (preserving checked state), calls the renderer, and wraps output in the managed block. Key entry point: generateRoadmapDocument(options).
src/validator/index.js — Multi-pass evidence scoring. Passes fire in priority order:
- Explicit backtick-quoted paths in task text
- Symbol name extraction (
function/classpatterns in text) - Code token matching (threshold scales with token count: 1/2/3 matches required)
- Test file matching via import references only (not content)
- Artifact presence (README, CHANGELOG, docs/, dist/)
- Namespace structural gate — for task IDs with known prefixes (
evh2,uxf,cls, etc.), at least one evidence file must match a path predicate for that namespace
GENERIC_TASK_TOKENS (line 18) is the blocklist that prevents common words from polluting evidence signals. Extend it when new false positives are found.
src/parser/index.js — Reads ROADMAP.md, extracts tasks with <!-- rs:task=id --> markers (stable IDs), tracks lineIndex and warningLineIndex for in-place sync. The managed block is bounded by <!-- rs:managed:start --> / <!-- rs:managed:end --> — upsertManagedBlock never touches content outside this region.
src/renderer/ — renderBody(model, profile) dispatches to compact.js or professional.js. The compact profile is the stable default; professional renders a 12-section structured roadmap with Phase→Step→Task hierarchy.
src/sync/index.js — Applies validateTasks results onto ROADMAP.md lines: marks [x] for passing tasks, appends ⚠️ attempted but validation failed: <reason> lines for failing ones. Uses line offsets to handle in-place splice.
src/config.js — Loads roadmap-skill.config.json, merges with defaults, exposes loadPlugins and collectPluginContributions. The __roadmapFileExplicit non-enumerable property tracks whether roadmapFile was explicitly set vs. defaulted (used by resolveRoadmapFile).
src/match.js — Jaccard-similarity task matching (similarityScore, threshold 0.55) for merging existing tasks with regenerated candidates. dedupeTasks resolves conflicts by: checked state wins, then lower priority number wins, then shorter text wins.
Key Invariants
checkedByIdis the only authority for task checked state. Never derive checked state from other metadata in renderers. Every<!-- rs:task=id -->emission must usecheckedState(model, id)— hardcoding[ ]silently breaks roundtrip preservation.- Task IDs are stable via
<!-- rs:task=slug -->markers. The slugification algorithm is locked; changing it breaks roundtrips. ROADMAP.mdis excluded from the evidence pool (SELF_REFERENTIAL_FILES) — its task descriptions contain the exact vocabulary being validated and would cause every task to self-validate.- TODO detection requires comment prefix (
//,#,*) to avoid false positives in non-comment code likeTODO|FIXMEin regex patterns. - Test files are matched by import references only, not by content keyword matching — test descriptions routinely mention future-task vocabulary.
- Test discovery for
npm testis scoped totest/*.test.js. Files undertest/fixtures/must never be run as tests.
Config and File Resolution
The legacy CLI reads roadmap-skill.config.json from legacy/ (moved during the v1.3.0 pivote — was at repo root pre-1.3.0). When running the CLI against this repo, omit --config — auto-discovery finds it. Passing --config with a wrong path silently falls back to defaults.
Config fields northStar, targetUser, problemStatement, etc. are forward-compatible: recognized by the agent skill today, not yet wired into the generator/validator.
Config field moduleMetadata (object keyed by lowercased module/command name) drives Section 6 ("Maturity Path") of the professional profile. Each entry is { state: string, tasks: Array<{ text, priority, id }> }. When a detected module/command name matches an entry, the renderer emits its state line and tasks; otherwise it falls back to generic "Document legacy/roadmap-skill.config.json for a working example.
PostToolUse Hook
.claude/settings.json in this repo registers a Claude-specific PostToolUse hook that runs node .claude/hooks/roadmap-sync.js after every Write/Edit operation. Treat it as a repo-local example, not as a host-agnostic integration contract.
This is a write-time hook, not the same thing as the git pre-commit hook. The write-time path is currently best-effort and depends on the host environment being able to resolve node for the spawned child process; the repo's pre-commit hook uses an absolute Node path and is stricter.
Audit Semantics
sync --audit and /roadmap-audit are read-only: they run validation, print the mismatch summary, and exit with code 2 if checkedWithoutEvidence or readyButUnchecked are non-empty. They never modify ROADMAP.md. The maintain command uses a separate internal path (options.audit) that mutates first and then prints audit output — that is intentional and distinct from the --audit flag contract.
Publishing
Release is fully automated. Two commands:
npm version patch # or minor / major
git push --follow-tags
What happens under the hood:
npm versionbumpspackage.json.- npm fires the
versionlifecycle script →scripts/sync-skills.js --fixpropagates the new version to the 4 mirrored manifests (.claude-plugin/plugin.json,.codex-plugin/plugin.json,plugins/roadmapsmith/.codex-plugin/plugin.json,skills.json) →git add -Astages them. npm versioncreates the commit + tag with all mirrors inside.git push --follow-tagsuploads commit and tag..github/workflows/release.ymltriggers onpackage.jsonchange inmain, compares local vs. published version, runsnpm publish --access public, and creates the GitHub release with auto-generated notes.- On the publish itself,
prepublishOnlyrunssync-skills.js --checkas the last safety net — publish aborts if any mirror is out of sync.
Never run npm publish locally. Never edit a mirrored manifest's version field by hand — the source of truth is package.json and drift will fail CI (.github/workflows/mirror-check.yml) and prepublishOnly.
Lessons Learned
-
source-of-truth discovery en repos con mirrors (v1.3): antes de planear cambios en un repo con multi-mirror (npm+plugin bundle, root skills/+plugins/roadmapsmith/skills/, .agents/+.claude/+.codex/), leer PRIMERO los sync/build scripts (
scripts/sync-skills.js,package.jsonfiles:,.github/workflows/mirror-check.yml) para identificar cuál path es source y cuáles son mirrors derivados. El CLAUDE.md del repo puede estar stale en paths tras un pivote. Editar el mirror en vez del source → sync automático lo pisa en el próximo release. -
LLM skill verification: dry-run el SKILL.md nuevo contra un repo real que exhiba el fallo (v1.3): para cambios de SKILL.md (prompts LLM-driven, no código),
npm test/check-skillssólo validan sintaxis y sync — no verifican output distinto contra el mismo input problemático. Protocolo post-edit: (1) identificar 1 repo real que reproducía el fallo, (2) actuar como agente ejecutando el skill nuevo step-by-step contra ese repo, (3) generar el reporte estructurado sin modificar el target, (4) confirmar que cada capacidad nueva dispara. Modo full-scan + modo short-circuit son verificaciones distintas.