Skip to content

skill · phase: ideation

Roadmap

{{glossary:instance}}

.aidlc/roadmap/ is a first-class inbox, independent of every lifecycle phase. Items are captured cheaply now and converted into instances later. Capture is legal at any moment: mid-implementation on another instance, from a second session, or with no instance at all.

Projects have more than one person proposing work, with mixed technical depth, so the roadmap distinguishes submitted from agreed. A contributor with no checkout can author an item with the portable skill at .aidlc/resources/skills/roadmap-item/SKILL.md — loadable into any AI agent — and commit the result to inbox/.

Statuses Are Directories

.aidlc/roadmap/
  inbox/         submitted, nobody has triaged it
  backlog/       accepted, will do, not started
  in-progress/   an instance exists
  done/          shipped
  hold/          parked or declined

The directory an item sits in is its status. There is no status field, and you must never add one — two representations of one fact drift apart, and a wrong status is indistinguishable from a right one. Changing status means moving the file.

hold/ holds both parked and declined items. That conflation is deliberate; history and the git log distinguish them when anyone asks.

Item Format

One file per item: .aidlc/roadmap/<status>/YYYYMMDD-<slug>.md, beginning with exactly these keys in this order:

---
id: item-20260814-cost-tracking
title: "Cost tracking"
created_at: 2026-08-14
author: Rodrigo Asensio - rasensio@   # optional; omit rather than guess
source: human                          # human | agent | import
promoted_to: null                      # instance name once promoted, else null
depends_on: []                         # other items, by id
history:
  - { status: inbox, at: 2026-08-14 }
---
  • title is always written inside double quotes, with no exceptions and no judgement call about whether this one needs them. An unquoted title is a YAML plain scalar, and a plain scalar may not begin with a reserved indicator character — so the single most natural way to name a code defect, title: `readItem` returns null, does not parse. Nothing reports it: the whole item then goes missing from browse, from promotion lookup, from the roadmap-done action and from aidlc doctor, because an item that will not parse and an item that was never written look identical. Five of this project's first 49 items were lost this way for up to ten days. Quoting unconditionally is three keystrokes and removes the class; "quote it when it looks risky" does not, because backticks do not look risky.
  • id is assigned once and never recomputed. Renaming the file or moving it between statuses leaves it unchanged. The item- prefix keeps it textually distinct from the filename so nothing derives one from the other.
  • source records how the item was written: human directly, agent drafted by an AI agent from someone's description, import migrated from an older format.
  • depends_on names other items by id, so a dependency survives its target moving. Build order is derived from this graph when needed — there is no global order field, because concurrent capture from independent sessions cannot coordinate a sequence.
  • history is append-only and advisory. Append { status, at } when you move an item. An absent or incomplete history is never an error, and current status always comes from the directory — never from the last history entry.

Bodies are otherwise free-form. Items authored through the portable skill carry four further headings — Problem, Who it's for, What success looks like, Out of scope — which is a useful shape, not a validated one.

Project-Declared Extension Fields

A project that outgrows those eight keys declares its own in .aidlc/config.yaml, and everything that writes an item then emits them:

roadmap:
  item_fields:
    - name: requirements
      required: true
      default: []

name must be lowercase letters, digits and underscores, and may not shadow a stock key. status is refused by name — status is the directory, and a second representation of it is the one extension the layout can never allow. required defaults to true; default defaults to null.

The worked example is real. fieldmodel-apps keeps requirements traceability as data: docs/requirements-traceability.md holds the requirement IDs, every item declares which ones it delivers in requirements: [...], and scripts/check-roadmap-requirements.mjs computes coverage as a set difference. It requires the key even when empty, because a set difference over an optional key silently under-reports. Before this existed, the portable roadmap-item skill said "Do not add any other field" while that check demanded one — two individually-correct rules that no agent could satisfy at once, in a framework-owned file the project was not allowed to edit.

Declaring a field changes three things and nothing else:

  • The portable roadmap-item skill's template carries the field, and its "add no other field" rule becomes bounded rather than absolute. aidlc update re-emits it.
  • aidlc roadmap import and the roadmap-layout doctor migration write the declared default into items they create. Triage moves and roadmap-done already preserve the field, because they edit frontmatter as text rather than re-serializing it.
  • aidlc doctor reports items missing a required declared field and offers to backfill the declared default.

The framework checks presence and shape, never contents. Shape is taken from the declared default — a list default means a list value, a scalar means a scalar — and a default: null declares no shape, so only presence is checked. There is no config key that could express a rule about what goes in a field, and that absence is deliberate: requirements: [not-a-real-id] is the project's own script's business. A wrong-shaped value is reported and never rewritten, because only the person who wrote it knows what they meant.

Every Item Opens With an Executive Summary

The first section of the body, before anything else, is ## Executive Summary — six labelled lines, nothing more:

## Executive Summary

- **What it is** — the change itself, in one sentence, in words a non-engineer would use.
- **Who benefits** — the person who feels the pain today, named by role.
- **Customer value** — what they can do afterwards that they cannot do now.
- **Revenue** — Direct, Indirect, or None, then one sentence saying why.
- **Cost and risk** — rough size (hours, days, weeks) and the main way it goes wrong.
- **Recommendation** — Do now, Do later, or Don't do, plus the reason in one line.

This exists so a triage decision can be made from the top of the file. An item whose value cannot be stated in six lines is an item nobody can decide about, and it will sit in inbox/ until somebody re-derives the case from scratch.

Four rules keep it honest:

  • None is a real answer for revenue, and often the correct one. Lifecycle plumbing earns nothing directly. Writing Indirect for everything makes the field useless, which is worse than leaving it blank because it reads as answered. This is also the cheapest available check against the self-repair trap: a meta-feature that has to write Revenue — None and Customer value — none, it observes the lifecycle has declared itself.
  • Write it last, from the rest of the body. A summary drafted before the detail is a guess wearing a summary's clothes.
  • Not yet decided. for any label you cannot fill. An admitted gap beats an invented figure; the next reader can tell the difference.
  • Six lines, no more. Longer reasoning belongs in the sections below it.

Older items may predate this section. Add it when you next touch such an item — derived from what the item already says, never from fresh speculation — rather than sweeping the whole directory.

Capture

  1. Resolve the roadmap root. Always write to the primary checkout's .aidlc/roadmap/, never a worktree's copy: resolve via git rev-parse --git-common-dir (the directory containing that path is the primary checkout). Outside a git repo, use the current project root. Create inbox/ if missing.
  2. Name the file. YYYYMMDD-<slug>.md — today's date plus a short (2–4 word) slug.
  3. Handle collisions. If the filename already exists in any status directory, ask one question — extend the existing item or new item? On extend, append a dated ## Update (YYYY-MM-DD) section to it where it is; on new, suffix the slug (-2). Never silently overwrite.
  4. Write the file into inbox/, with source: human, promoted_to: null, and one history entry dated today. Record depends_on only if the braindump itself names other items; never ask for dependencies. Do not interrogate the user for structure — capture friction kills inboxes; one braindump in, one file out.
  5. Draft the executive summary from the braindump, do not extract it by interview. You write those six lines; the user does not. Ask at most one question, and only when the braindump says nothing about who wants this or why — a good one is "would this make money, save money, or is it just something that needs doing?". Anything still unknown is Not yet decided., which is the point of having the label.
  6. Confirm and return. Report the file path and the Recommendation line, then return to whatever was happening. Showing the recommendation lets the user correct a wrong read immediately, while the context is still in front of them.

Hard rule: capture never reads or writes anything under .aidlc/state/ — no claims, no phase files, no transitions log. Two sessions capturing simultaneously just produce two files; no locking exists or is needed. Do not auto-commit; staging is the user's.

If the user includes credentials, tokens, or secrets in a braindump, flag it and omit them from the file.

Browse

List each status directory, reading only frontmatter and the containing directory — never item bodies. Show each item's date, title, status, and dependencies. Group by status in lifecycle order and give a count per status. Flag inbox/ items first: they are waiting on a decision only a human can make. Then flag backlog/ items whose dependencies are all done — those are the natural next candidates. Offer to open, triage, promote, or drop.

The frontmatter-only rule holds for browsing, which is a list and must stay cheap enough to run over the whole roadmap. Triage below is the one flow allowed into the body, and only as far as the executive summary.

A filename appearing in two status directories is an error: report it rather than guessing which one is current.

Triage

Moving an inbox/ item to backlog/ means "we agree to do this". Moving it to hold/ means "not now". Move the file and append the matching history entry. Triage is the one flow that exists purely because more than one person can submit work — do not skip it by promoting straight from inbox/ unless the user says so.

Triage is the one flow that reads into the body, and it reads the executive summary and nothing else. Six labelled lines per item is what makes triaging a full inbox in one sitting possible; opening whole items is what makes triage something nobody ever gets round to. Show the summary, say which way you would go and why, and let the human decide. If an item has no executive summary, write one from what the item says before asking for a decision — a decision made without one is a decision made twice.

Read .aidlc/context/constitution.md when it exists and apply the constitution rule in aidlc-overview: an item that crosses a prohibition is the clearest hold/ there is, and naming the prohibition turns "not for us" into a reason the submitter can read.

Promote

Promotion is where the roadmap meets the lifecycle:

{{glossary:scope}}

  1. Promote from backlog/ normally; from inbox/ only when the user is explicitly skipping triage.
  2. If the item's depends_on names items that are not yet done, say so and confirm before proceeding — promoting out of order is allowed but should be deliberate.
  3. Choose a template and scope as usual (defaults from .aidlc/config.yaml; see aidlc-getting-started).
  4. Create the instance per "Creating an Instance" in aidlc-overview — including firing on-instance-start lifecycle actions.
  5. Seed the instance with the item: Full scope → the item is raw material the Ideation phase refines into that phase's required artifact (idea.md for the feature templates, research-question.md for spike); Standard scope → the item is input context for Requirements.
  6. Set promoted_to: <instance-name>, move the file to in-progress/, and append the history entry.

When the instance completes, the roadmap-done lifecycle action moves the item to done/ on its own — do not do it by hand, and do not remove that action to do it manually.

Drop

Move the item to hold/ and append the history entry. Dropping is an explicit act — never delete or overwrite an item to get rid of it.

Projecting to GitHub Issues

Optional, opt-in, and inert until a roadmap.sync section exists in .aidlc/config.yaml. When a user asks how the roadmap can be visible to people who will not clone the repo, or how it can appear where their team already triages, the answer is aidlc roadmap sync — see docs/roadmap.md.

Four things to know before you offer it:

  • It is a projection, not sync. Disk stays the source of truth; nothing is written back to disk, so no item gains a remote: field and no flow above changes. The link lives on the issue as a marker carrying the item id.
  • inbox/ and hold/ are not projected by default, because an untriaged idea and a parked one are not decisions anybody has published. Do not widen that list on a user's behalf.
  • Every write needs consent. Suggest aidlc roadmap sync --dry-run first, always. It prints the plan, names the target repository, and touches nothing.
  • The issue body carries the whole item — its frontmatter facts, its prose, and links to its file and to the items it names — inside a region marked <!-- aidlc-body:begin --><!-- aidlc-body:end -->, regenerated on every sync. Text outside that region is preserved, so a note added to an issue description survives. When somebody asks why a projected issue is out of date, the answer is that a sync has not run since the item changed, not that the body is frozen.

Editing an item's body is therefore a publishing act on a repo with roadmap.sync configured. That is not a reason to hesitate, but it is a reason not to paste anything into an item you would not put on the tracker. Item prose is sanitized on the way out — HTML comments removed, @name and #123 made inert — so no sync notifies anybody; secrets are not sanitized, and never were. If a user's braindump contains credentials, the existing hard rule applies: flag it and leave them out of the file.

aidlc roadmap import <issue-url> is the opposite direction, and it is one-shot: it writes one inbox/ item with the issue linked and its executive summary left as Not yet decided., which is then a normal triage. Nothing reconciles afterwards.