Map out the capability graph for one scope of the project: survey the code (or the product's capability-map table), propose new Capability entities where the map has gaps, enrich existing ones with `locations`, grain, audience and `related` links, and report retire candidates. Runs `sdlc capability coverage` and `sdlc capability graph` read-only, dispatches one read-only sub-agent per candidate, renders one batched plan for approval, then writes through `sdlc capability create` / `sdlc capability update` and opens a PR. Invoke when capabilities are missing, ungraded, unanchored, or when the System page's capability view looks empty for an area. Usage: `/sdlc:capability-map <dir|C-NNNN|areas> [--dry-run] [--auto]`.
Resources
1Install
npx skillscat add osdlc/osdlc/capability-map Install via the SkillsCat registry.
$sdlc:capability-map
Usage:
$sdlc:capability-map <dir>— survey the packages and crates under a directory and
reconcile the capability corpus against them.$sdlc:capability-map C-NNNN— re-map one capability's subtree: enrich its members and
survey the units they anchor.$sdlc:capability-map areas— seed the top level: every row of the product entity's
capability-map table with no entity file becomes asystemroot.--dry-run— stop after the batched plan. Never creates a worktree, branch, or file.--auto— skip the confirmation prompt and run every non-skip verdict.
A scope is required. With no scope, printERROR reason="scope required: <dir>, C-NNNN, or areas" and stop. A whole-project sweep
produces a plan too large to review; run one directory at a time.
Project context (don't re-derive every run):
- The read-only substrate is two ops.
sdlc capability coverage --scope <scope>joins the
corpus against the manifest walk's packages and crates:units(with the capabilities
anchored to each),unanchored_units,unanchored_capabilities,anchor_rot, and a
per-capabilityattachmentscount (inbound wikilinks plus resolved locations).sdlc capability graphis the corpus as a tree withrelatededges and a warning per
danglingparent_key. - Writes go through
sdlc capability createandsdlc capability update. Their--helpis
the contract. Never hand-roll capability frontmatter. - The rubric is
${CLAUDE_PLUGIN_ROOT}/conventions/capability-rubric.md, shared with$sdlc:capability-review; it points at the grain table, audience rubric, locations grammar
and body shape in${CLAUDE_PLUGIN_ROOT}/lib/model/entities/capability/definition.md. Read
both once per run and point sub-agents at them; do not restate them. - Links between capabilities are untyped
relatedwikilinks. Containment isparent_key
only; children are derived. - Retirement is report-only. A zero-attachment capability lands in the plan's retire table;
closing it is a separate human decision.
1. Parse args and pre-flight
Parse <scope>, --dry-run, --auto. Classify the scope: a path that exists is a dir; a
string matching C-NNNN is a subtree; the literal areas is a seed.
Pre-flight with the report-only probe:
scripts/sdlc project preflight-worktree capability-map-<stem> \
--branch chore/capability-map-<stem> \
--project-root .<stem> is the scope's last path segment, the capability id in lower case, or areas. If dirty
is true, ask via AskUserQuestion whether to proceed (the worktree branches from main, so
uncommitted changes stay behind) or stop; default stop, showing the porcelain lines. IfanyCollision is true, stop and ask; a prior run may be unfinished.
2. Gather (read-only)
scripts/sdlc capability coverage [--scope <scope>] --project-root . --output json
scripts/sdlc capability graph --project-root . --output jsonOmit --scope for areas. Keep the warnings array from graph as the run's baseline; Step 7d
compares against it.
For seed, run coverage with no --scope and read the product entity's capability-map table
(docs/planning/products/PR-*.md, the section headed "the capability map"). Readdefinition.md (see Project context).
Build the candidate list:
| Scope | Candidates |
|---|---|
dir |
each id in unanchored_units; each id in unanchored_capabilities; each capability in capabilities whose kind is null |
subtree |
each capability in capabilities; each id in unanchored_units |
seed |
each table row whose "Capability entity" cell is not shipped and names no [[C-…]], unless an existing capability title already matches the area name |
If the list is empty, print CAPABILITY-MAP-NOOP scope=<scope> and stop.
3. Create the worktree
Skip under --dry-run. Otherwise:
scripts/sdlc project create-worktree capability-map-<stem> --branch chore/capability-map-<stem> --start-point mainThe op prints the worktree's absolute path, under the main checkout; use that path.
Use absolute paths under the worktree for every write from here on. Do not cd the parent
session into it.
4. Dispatch one sub-agent per candidate (single message, parallel)
For every candidate, dispatch one sub-agent in a single message so they run concurrently.
Use the general-purpose subagent type with the built-in default profile; these are
well-briefed reads. Sub-agents are read-only: they read files and run git log; they never
write.
Resolve the profile once, before the first dispatch:
sdlc route resolve default --in-session claude --jsonPass its alias as each sub-agent's model and its effort, when present, as each sub-agent's
effort. degraded: true means the profile's first choice is on another tool; say so in the
report. On a non-zero exit, report the command's stderr (it carries the trace) and stop. Do not
dispatch without a model: the sub-agents would run on this session's model.
Each sub-agent prompt carries:
- The project root (absolute).
- The one candidate it owns: for a unit, its
id,dir,kind,ecosystem; for a
capability, its file path and coverage row; for a seed row, the table row and the matching
"Areas in detail" row verbatim. - The paths to the rubric and
definition.md, and the instruction to apply them. - The current tree from
capability graph(ids, titles, parents), so a parent or arelated
target is chosen from what exists. - The verdict contract below.
Per-candidate verdict contract
Each sub-agent, in order:
- Read the unit (README, manifest, entry points, skill files) or the capability file.
- Decide the verdict:
new— the unit or row names an ability no capability describes.enrich— an existing capability describes it; supply the missinglocations,kind,audience,parent,related.fold— the unit belongs under an existing capability'slocationsrather than being its
own.skip— scaffolding, a fixture, a build artifact, or already covered.
- Grade with the rubric: anchors decide the grain, the surface test decides the audience,
the state table decides the state. Low confidence setsneed_human_review. - Return a strict JSON object, no surrounding prose:
{
"candidate": "<id or path verbatim>",
"verdict": "new | enrich | fold | skip",
"target": "<C-NNNN for enrich/fold, else null>",
"title": "<for new>",
"kind": "<a grain from definition.md, or null>",
"kind_confidence": "high | low",
"audience": "user | system",
"parent": "<C-NNNN or null>",
"locations": ["<five-form Location>", "..."],
"related": ["[[C-NNNN]]", "..."],
"body": {
"Summary": ["<first bullet, ends with ^summary>", "<second bullet>"],
"Statement": "<one or two paragraphs>",
"What it provides": ["<deliverable>", "..."],
"Underlying implementation": ["<realizing path or skill>", "..."]
},
"reason": "<one sentence>"
}Omit body keys the evidence does not support. Keep each response under ~400 words.
Single message — every Agent call goes in one assistant turn.
Aggregate verdicts
Merge into one list. A sub-agent that returned non-JSON becomes a skip with reasonmalformed verdict, listed in the plan.
5. Reconcile (in-session)
Apply these checks to the merged list before rendering the plan:
- Dedupe. Two
newverdicts with the same title, or with overlappinglocations, merge into
one. Anewwhose title or locations match an existing capability becomesenrichof it. - Tree. Every
parentis an existing id or anewin this plan. Aseedrow is a root
(parentnull,kindsystem,titlethe area name verbatim). No product-level root above the
areas. - Grain. A
neworenrichwithkind_confidencelow carriesneed_human_review: true. - Audience.
audience: userwith no user-facing location is flagged in the plan row. - Retire candidates. Every capability in scope, other than
open/plannedones and this
plan'snewentries, whoseattachmentsis zero after the planned enrichment is listed for
the reader. Nothing is closed. - Rot. Every
anchor_rotrow in scope becomes part of the owning capability'senrich
(drop or replace the location), or is listed under skip with the reason.
6. Build the batched plan and confirm
Render one markdown plan:
# Capability map plan — scope <scope>
<N> candidates → <A> new, <B> enrich, <F> fold, <S> skip, <R> retire candidates.
## New (<A>)
- `<title>` — kind <kind>[?] · audience <audience> · parent <C-NNNN|root>
- Locations: <n> · Related: <n> · Review: <yes|no>
## Enrich (<B>)
- C-NNNN <title> — sets: <kind|audience|parent_key|locations(+n)|related(+n)>
## Fold (<F>)
- <unit id> → C-NNNN (adds <location>)
## Retire candidates (<R>)
- C-NNNN <title> — attachments 0
## Skip (<S>)
- <candidate> — <reason>Under --dry-run, stop here and print the plan. Under --auto, proceed to Step 7.
Otherwise ask via AskUserQuestion:
Run the plan as shown — proceed with every non-skip verdict.
Walk one-by-one first — one follow-up question per new/enrich/fold item (
apply,drop,change verdict), then re-render and ask again.Cancel — tear down and stop:
scripts/sdlc project teardown-worktree capability-map-<stem> \ --branch chore/capability-map-<stem> \ --project-root <worktree-parent-repo-root>
7. Write entities
From inside the worktree, in this order: roots before children, so a child's --parent-key
names a minted id.
7a. New
scripts/sdlc capability create \
--project-root <worktree-absolute-path> \
--title "<title>" \
[--kind <grain>] \
--audience <user|system> \
[--parent-key "[[C-NNNN]]"] \
[--locations <location>]... \
[--related "[[C-NNNN]]"]... \
[--tags <tag>]... \
[--need-human-review] \
--provenance "sdlc:capability-map <scope>" \
--created-at <ISO 8601 UTC timestamp>The slug positional is omitted: create derives it from --title. Pass --need-human-review
when the verdict set it. --provenance and --created-at mark the file as authored by this
run; one timestamp (date -u +%Y-%m-%dT%H:%M:%SZ) serves every create in the plan. Capture
the printed path and the minted id; later children reference it, and Step 8 stages exactly
these paths. To preview the slug and id first, or check a
collision, call sdlc capability preview-id "<title>".
7b. Enrich and fold
scripts/sdlc capability update <C-NNNN-slug> \
--project-root <worktree-absolute-path> \
--set '{"kind": "<grain>", "locations": [...], "related": [...], "need_human_review": true}'The positional is the file basename without .md (id plus slug, as capability graph lists it); a
bare id resolves only for unslugged files. --set replaces a list field whole: pass the mergedlocations (existing minus rot plus new), not only the additions. Merge related by resolved
id, not by string: the corpus spells links slugged ([[C-V44L-priority-ordering-sorters]]) and
verdicts spell them bare ([[C-V44L]]); when a target is already present, keep the file's
spelling and do not add a second entry. A fold is an update adding the unit's directory tolocations.
7c. Fill body sections (applies under --auto too)
Each created file carries <...> placeholders. For every created path, Edit each section the
verdict's body supplied: replace the placeholder block, keep the ## <Heading> line, keep the
first Summary bullet's ^summary tag. Delete every optional section the verdict left unfilled
(Inputs, Outputs, Hook points, Underlying implementation, Notes); no placeholder
survives to the commit. The only sections that may be added are the optionalContained sub-features and Lifecycle map: a root with children in this plan getsContained sub-features, listing them as wikilinks, in place of Inputs / Outputs.
7d. Validate
For every created or updated path:
scripts/sdlc entities validate <path>Then:
scripts/sdlc capability graph --project-root <worktree-absolute-path> --output jsonwarnings must contain nothing beyond the Step 2 baseline. A new warning (a dangling parent, an
unresolved related) is a defect in the plan: fix the offending frontmatter withcapability update and re-run. Do not commit with a new warning; pre-existing corpus warnings
are not this run's to fix.
8. Commit, push, open PR
Stage only the paths captured in Steps 7a and 7b, plus whatever git status --porcelain docs/
shows after docs generate (it rewrites every index page; the pre-commit docs-drift check
rejects a stale one). Do not git add -A or glob the directory.
scripts/sdlc docs generate --project-root <worktree-absolute-path>
git add <created path>... <updated path>... <generated page>...
scripts/sdlc commit create --message - <<'EOF'
docs(capabilities): map <scope>: <A> new, <B> enriched
Via $sdlc:capability-map.
New:
- C-NNNN — <title> (<kind>)
Enriched:
- C-NNNN — <fields set>
EOF
git push -u origin chore/capability-map-<stem>
scripts/sdlc pr create --base main --head chore/capability-map-<stem> \
--title "Capability map: <scope>" --body-file <body-file>The message reaches git through the op's temp file (the - sentinel). See${CLAUDE_PLUGIN_ROOT}/conventions/commit-messages.md.
PR body shape:
## Summary
Capability map for `<scope>`: <A> new, <B> enriched, <F> folded, <S> skipped.
<R> retire candidates listed below; none closed.
## New
- C-NNNN — <title> · <kind> · <audience> · parent <C-NNNN|root>
## Enriched
- C-NNNN — <fields>
## Retire candidates (not closed)
- C-NNNN — <title>, attachments 0
## Needs human review
- C-NNNN — <why the grain was uncertain>
## Skipped
- <candidate> — <reason>
## How this was generated
`$sdlc:capability-map <scope>`: `sdlc capability coverage` and `sdlc capability graph`
read-only, one read-only sub-agent per candidate in a single message, in-session
reconciliation, one batched plan, then `sdlc capability create` / `update`, `entities validate`
on every file, and `capability graph` with no new warnings.9. Report
Print CAPABILITY-MAP-DONE pr=<url> new=<A> enriched=<B> retire_candidates=<R> and tell the
user the PR URL, the new ids and titles, the enriched ids, the retire candidates, theneed_human_review set, and the worktree path.
Acceptance criteria
- With no scope the skill prints the
ERROR reason=line and writes nothing. - Steps 1, 2, 4, 5 and 6 are read-only; the first write is Step 3's worktree, after the
candidate list is non-empty, and the first entity write follows plan approval (or--auto). - Every created or updated file passes
sdlc entities validate, andsdlc capability graph
in the worktree reports no warning beyond the Step 2 baseline. - Every
newcarrieskindorneed_human_review: true; every low-confidence grain carriesneed_human_review: true. - No capability is closed or deleted; retire candidates appear only in the plan and PR body.
- One commit contains exactly the touched capability files; the branch is pushed; the PR URL
is reported with theCAPABILITY-MAP-DONEmarker.
Failure modes
capability coverageorcapability graphfails — stop, print stderr verbatim asERROR reason="...". Nothing else runs.- Sub-agent returned non-JSON — treat as
skipwith reasonmalformed verdict; the batch
continues. capability create/updatefails — stop and surface stderr. The worktree isolates the
partial state; the user re-runs or cancels.- Validate fails after a body Edit — the Edit broke the body contract. Report the file's
findings, do not commit. capability graphwarns anew after writes — a parent or related target in the plan was
wrong. Fix withcapability updateand re-run the check; do not commit with a new warning.
Notes
- No entity writes until Step 7. Step 3 creates only the worktree; sub-agents read; the
orchestrator writes entities only after approval. - Parallelism comes from one message. Step 4 dispatches every Agent call in a single
response. - One rubric.
${CLAUDE_PLUGIN_ROOT}/conventions/capability-rubric.mdis shared with$sdlc:capability-review; a decision this skill makes is the one review would make. - Untyped links.
relatedis the only capability-to-capability link this skill writes; a
typed Relation entity is not activated by this skill. - Scope, not sweep. A directory at a time keeps the plan reviewable. Run
areasfirst on a
corpus whose top level is prose; later runs hang code under those roots. - Retirement is a report. Attachments of zero go in a table; the reader decides.
- Committing model-generated messages. See
${CLAUDE_PLUGIN_ROOT}/conventions/commit-messages.md. - Branch naming.
chore/capability-map-<stem>. See${CLAUDE_PLUGIN_ROOT}/conventions/branch-naming.md. - Worktree teardown is deferred. Cleanup after the PR merges;
$sdlc:project-cleanup
surfaces the merged branch and its worktree.