Feature Map guide

This guide covers adopting Feature Map in a repository: installing the CLI, runninginit, writing the first maps for a codebase that already exists, and keeping them accurate in CI. It also includes the instructions agents are expected to follow once the skill is installed.

CLI v1.2.7 · Updated · Plain markdown

What a feature map is

A map is a YAML file .features/<slug>.yaml with at least:

  • feature_name: matches the filename stem
  • purpose: what it does and why
  • entry_points: real file paths (and routes if useful)

Recommended: apps, user_flow, related_features, notes. Cross-app features get one map, not one map per app. related_features is how graph and impact --transitive work.

Maps are research infrastructure, not product features. They do not replace PRDs or implementation plans. They tell you where the code lives and how the pieces connect before you touch anything.

What Feature Map does not do

  • There is no stored web UI, wiki export, or database of maps. feature-map viewer opens a local, ephemeral, read-only page and never writes HTML into your repo.
  • There is no auto-generator that invents maps from the AST. Maps stay authored; agents may draft them after reading the code (see existing repositories).
  • There is no coupling to a framework. The CLI only reads .features/, .feature-map.yaml, and the filesystem.

Why a feature map, not a wiki

A Karpathy-style (or any LLM-written) wiki works: agents dump what they learned into markdown and the next session can read it. The failure mode is shape. Language models write prose. A wiki page on “signup” becomes a narrative — history, asides, duplicated README, a little architecture — and the next agent has to ingest the whole page to find the three files that matter. That is slow in wall time and expensive in tokens.

Feature Map stores the same knowledge as fields, not essays:

Need Wiki Feature Map
What is this? Buried in a paragraph purpose
Where do I start? Maybe a link, maybe not entry_points (real paths)
Which apps? Prose apps
How does a user move? A story user_flow
What else is coupled? “See also” if you are lucky related_features → graph
Is this stale? You re-read it check (paths on disk), validate

How an agent finds a feature

feature-map list                          # names only
feature-map search billing                # slugs + short snippets
feature-map show billing --section entry_points

list is a few dozen tokens. search returns hits, not chapters. --section loads one key from one YAML file. The agent opens the code at those paths instead of reading a 2k-word wiki page and then still grepping.

That is why it is more efficient:

  1. Addressable. The CLI is an index (list, search, find, impact). A wiki is a pile of documents you cat.
  2. Bounded reads. Structured keys and --section cap context. A wiki has no equivalent of “give me only the files.”
  3. Checkable. validate and check catch missing fields and dead paths. A wiki cannot tell you the controller it mentions was renamed.
  4. Stable voice. YAML fields do not grow a new introduction every time an agent “updates the docs.”

Value to the developer

  • You get a directory of the product, one slug per capability, without maintaining a second documentation site.
  • Reviews stay honest: if entry_points does not list the file you changed, the map is wrong and you fix it in the same PR.
  • Onboarding is feature-map list then show, not “read the wiki and hope the architecture section is current.”
  • You still write PRDs and plans for what to build. The map only answers where it lives and what it touches. That split keeps both artifacts small.

A wiki can still hold narrative (vision, history, runbooks). Feature Map is the index the agent is required to hit first so it does not spend the budget re-discovering the tree.

Install the CLI

Pick one:

pip install feature-map-cli
npm install -g feature-map-cli
brew install markschellhas/tap/feature-map

Requires Python 3.8+ and PyYAML. The npm package installs the PyPI CLI into a local venv. The command is feature-map whichever way you install it. The PyPI name is feature-map-cli because pip install featuremap is a different (biology) project, and feature-map is blocked on PyPI as too similar to that name.

To try unreleased changes, install from git:

pip install "git+https://github.com/markschellhas/feature-map.git"

Confirm:

feature-map --version

Bootstrap a repository

From the repo root (a git checkout, or the directory you intend as the root):

feature-map init

This is idempotent. It:

  1. Creates .features/
  2. Copies the agent skill to .agents/skills/feature-map/ (or .grok/skills/feature-map/ if that tree already exists), and mirrors it into every other known harness directory the repo already uses, plus any --skill-dir / skill_dirs: target
  3. Writes .feature-map.yaml if missing
  4. Appends (or refreshes) an AGENTS.md block that requires agents to use Feature Map before feature work, and names the deployed skill by path so an agent that only reads AGENTS.md can still find it
  5. Writes bin/feature-map, a shim that execs feature-map on PATH
  6. On an empty repo, offers to launch your agent harness to scour the code and author the first maps

Skill directories are harness-neutral. .agents/skills/ stays the primary target; a known harness is additionally mirrored only when the repo already uses it (its config directory exists), so init never seeds an agent tree nobody asked for. Known: .agents/skills, .claude/skills, .grok/skills. For anything else, name it yourself (--skill-dir .my-agent/skills, or skill_dirs: in .feature-map.yaml) and it is always written.

Useful flags: --no-agents, --no-shim, --upgrade-skill, --force, --skill-dir DIR (repeatable). -y/--yes skips the “start authoring maps?” prompt and -h/--harness <name> picks the agent harness non-interactively (claude, cursor-agent, opencode, grok, codex, gemini or pi). Answer n or pick skip to author the maps yourself.

feature-map init -y -h claude

Configure .feature-map.yaml

Declare the repo’s apps so check can resolve paths:

# .feature-map.yaml
features_dir: .features
apps:
  - api
  - web
  - mobile
required_sections:
  - purpose
  - entry_points
min_cli_version: "1.0.0"
# Extra skill mirror targets for harnesses this CLI does not know about:
# skill_dirs:
#   - .my-agent/skills

apps prefixes are used by check when resolving paths. skill_dirs adds skill mirror targets for harnesses the CLI does not know about.

Instructions for coding agents

feature-map init writes this mandate into AGENTS.md. Keep it. Do not weaken it. Downstream skills (plans, PRDs, debugging) should say:

REQUIRED SUB-SKILL: Use feature-map before proceeding.

Agents must:

  1. Before any feature work, debug, PRD, or plan, run feature-map list (or ./bin/feature-map list).
  2. If the feature name is unclear, run feature-map search <keyword> and feature-map find <path-fragment>.
  3. Read the map with feature-map <slug>, or feature-map show <slug> --section entry_points to save tokens.
  4. For cross-cutting work, run feature-map graph <slug>.
  5. After shipping architecture changes, update the relevant .features/*.yaml, then run feature-map validate and feature-map check.

Agents must not implement, plan, or “just grep the repo” when a map exists for that area. The map is authoritative; the tree is how you verify the map.

If list is empty, or search/find miss the area you are about to touch, stop and author maps first (see the next section). Do not invent architecture in a PRD or plan without writing the map.

Day-to-day commands:

feature-map list
feature-map search billing
feature-map find src/invoices
feature-map billing
feature-map show billing --section entry_points
feature-map graph billing --format mermaid
feature-map validate
feature-map check
feature-map impact src/invoices/create.py

./bin/feature-map is the same CLI if the global binary is not on PATH in a given environment.

Adopt Feature Map in an existing repository

init does not generate maps from source. On a repo that already has code, an agent (or a developer) must read the tree and author maps. This is expected and should be the first Feature Map task in that repo.

Do not emit one map per file. Cluster by user-visible capability or deployed subsystem.

Step A: inventory the repo

# top-level apps / packages
ls
# language / package manifests
# Gemfile, package.json, pyproject.toml, go.mod, pubspec.yaml, apps/, packages/

Write the apps: list in .feature-map.yaml from that inventory (api, web, ios, worker, …). Prefer directory names that check can prefix onto paths.

Step B: find seams where features show up

Scour, in order, whatever exists:

Signal Typical paths
HTTP routes config/routes.rb, **/routes.*, OpenAPI, app/controllers
App shells src/App.svelte, lib/main.dart, cmd/*/main.go, app/page.tsx
Domain models app/models, src/domain, internal/
Background jobs app/jobs, workers/, queues
CLIs bin/, cmd/, cli/
Docs / PRDs docs/, README*, existing architecture notes
Tests test names often name the feature better than folders

Use the CLI as you go:

feature-map find <controller_or_package>
feature-map search <domain word>

On a first pass those will miss (no maps yet). That is the signal to draft maps, not to skip them.

Step C: cluster into features

A feature is a slice a human would name in a standup (“signup”, “billing”, “push notifications”), usually spanning several files and sometimes several apps.

Cluster when they share:

  • the same user journey
  • the same primary models / tables
  • the same entry controllers or screens
  • a documented product name

Split when journeys, owners, or deployables diverge. Cross-app flows (one API + one mobile screen + one worker) are one map with multiple apps and entry_points, not three maps.

Step D: author each map

feature-map init signup          # scaffold .features/signup.yaml

Then replace placeholders by reading the real code. Required:

feature_name: signup
purpose: "How a new account is created and reaches the first authenticated screen."
entry_points:
  - api/src/signup/handler.go
  - web/src/routes/signup/+page.svelte
apps:
  - api
  - web
user_flow:
  primary: "Email submit → API creates user → redirect to /app"
related_features:
  - billing (plan attached after first login)
notes: >
  Status: mapped from existing code on 2026-08-27. Confirm webhook path
  before changing Stripe.

Rules while scouring:

  • Prefer paths that exist on disk. feature-map check will flag missing ones; invented paths train agents to look in the wrong place.
  • Routes (GET /signup) may appear in entry_points as extra context, but put the implementing file next to them.
  • related_features entries must start with a real slug once that map exists (billing (note), not a sentence).
  • Record uncertainty in notes instead of guessing.

Work in batches: scaffold 5–10 obvious features, validate, then fill gaps check and stats report.

feature-map validate
feature-map check --json
feature-map stats --json

validate without --strict treats missing recommended sections as warnings (exit 0). Use --strict in CI once the first pass is honest.

Step E: when to stop the first pass

Stop when:

  • Every app in .feature-map.yaml appears on at least one map
  • The main user journeys (signup, core loop, billing/admin if any) have maps
  • feature-map search <product noun> hits something for the nouns in the README
  • check only reports paths you intend to add, not typos

You do not need 100% file coverage. You need named doors into the system so the next agent does not start from zero.

Step F: keep maps alive

After that first scour:

  • New feature → feature-map init <slug> in the same PR as the code
  • Moved/renamed files → update entry_points, run check
  • Cross-feature work → update related_features and re-read graph

Greenfield repositories

If there is almost no code yet, still run feature-map init, then add a map when the first vertical slice lands. An empty .features/ plus the AGENTS.md mandate is enough: the next agent is required to author the map with the feature, not after the fact.

Capture ideas for later

feature-map idea saves a product idea as markdown in docs/ideas/, so the backlog lives in the repository next to the maps:

feature-map idea "let users export invoices as CSV"
# Saved docs/ideas/2026-09-26-let-users-export-invoices-as-csv.md

feature-map idea                      # type several lines; finish with an empty line or Ctrl-D
echo "rate-limit the public API" | feature-map idea

The filename is today’s date plus a slug of the first line. A second idea with the same slug on the same day gets -2, -3, and so on. The folder is created on first use, and .features/ does not have to exist yet. With --json the command prints {"ok": true, "path": ..., "file": ...}.

Because the ideas are committed, any agent with a checkout can pick one up, including cloud agents that never saw your terminal. A useful prompt: “Implement docs/ideas/<file>.md. Use feature-map to find the affected features first, and add or update their maps in the same PR.”

Validate feature maps in CI

# example GitHub Actions step
- run: pip install feature-map-cli
- run: feature-map validate
# optional, once maps are trusted:
# - run: feature-map validate --strict

Trigger on changes to .features/** and, if you want a backstop, on every PR.

Upgrade

feature-map update
# or, by hand:
# pip install -U feature-map-cli
# npm install -g feature-map-cli@latest
# brew upgrade markschellhas/tap/feature-map
feature-map init --upgrade-skill

feature-map update detects whether this copy came from pip, npm, Homebrew, pipx, or uv and upgrades through that channel. If you are already on the latest version it says so and exits 0. A source/editable checkout cannot be upgraded this way; install a released package instead.

--upgrade-skill overwrites the deployed SKILL.md and references from the installed package version. Re-run feature-map init to refresh the AGENTS.md marked block.

Command reference

Invocation: feature-map (on PATH) or ./bin/feature-map (repo-local shim). Global flags: --json, --version.

Command Description
list [--json] All feature slugs; JSON includes mtime and app count
show <name> [--section <key>] [--json] Full map or one top-level section
<name> Alias for show <name>
viewer [--no-open] [--timeout SECONDS] Local ephemeral browser view of all maps, for humans
search <query> [--json] Full-text search across all maps
find <path-fragment> [--json] Reverse lookup by path string
graph [name] [--format mermaid|json|dot] related_features graph
validate [--strict] [--json] Structural validation
check [--json] Staleness check for entry-point paths
impact <file> [--transitive] [--json] Features referencing a file
stats [--json] Coverage statistics
init [-y] [-h <harness>] [--skill-dir DIR] Bootstrap .features/, agent skill, config, AGENTS.md, shim
init <name> [--force] Scaffold .features/<name>.yaml
init --upgrade-skill Refresh the agent skill in every deployed location
install [--json] Verify install and repo setup
update [--json] Upgrade this CLI via the package manager that installed it
idea [text] [--json] Capture an idea as markdown in docs/ideas/ (creates the folder)

Exit codes: 0 success, 1 user error (e.g. feature not found), 2 validation failure (validate --strict).

Local development

Published PyPI, npm, and Homebrew installs lag the repository. To work on the CLI itself, clone the repo and use an editable install in a virtualenv so feature-map on PATH is the code you are editing:

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"     # same as `make install`
python -m pytest -q         # same as `make test`
feature-map --version

Edits under src/feature_map/ take effect immediately. Do not iterate via npm install -g feature-map-cli or brew install: those pull the last published PyPI version, and feature-map update refuses a source/editable checkout on purpose. Publishing steps are in PUBLISH.md.

Back to the Feature Map overview, or browse the source and changelog on GitHub.