بنقرة واحدة
extract-features
// Six-axis Feature Inventory (routes / models / jobs / tests / UI / docs) — a "what we must preserve" spec for greenfield rewrites.
// Six-axis Feature Inventory (routes / models / jobs / tests / UI / docs) — a "what we must preserve" spec for greenfield rewrites.
| name | extract-features |
| description | Six-axis Feature Inventory (routes / models / jobs / tests / UI / docs) — a "what we must preserve" spec for greenfield rewrites. |
| argument-hint | [project-name] [--with-mockups] |
| allowed-tools | Bash, Read, Grep, Glob, Write |
Walks the target project's codebase across six discovery axes and writes a consolidated Feature Inventory. The artefact is the "what we must preserve" specification for a greenfield rewrite (different language, framework, or architecture) — instead of reverse-engineering features one route at a time, hand the inventory to the rewrite team.
This skill complements /handover:
/handover produces a high-level project assessment — origin, current health, integration plan, applicable roles. The output is the bridge between "we just inherited this codebase" and "this codebase is now governed by our normal SDLC"./extract-features produces a granular feature catalogue — every route, model, job, test name, UI screen, and documented capability the existing system exposes. The output is the input to a greenfield-rewrite spec.Run /handover first when adopting an unfamiliar repo; run /extract-features second when you've decided to rewrite it.
See also: /feature-diagram <feature-slug> — once the inventory exists, this skill emits a per-feature Mermaid sub-graph (routes + models + jobs + screens for one feature) at projects/<name>/features/<slug>.md. The inventory's Feature column gains a link to each per-feature diagram. Sibling to /c4 (system topology) and /dfd (data flows) in the architecture-doc family — different lens (per-feature slice) on the same codebase. See AgDR-0035 for the design rationale.
Discovery walks across all six axes — documentSymbol, references, definition queries — are the obvious win for LSP. With ENABLE_LSP_TOOL=1 + per-language plugin (per docs/getting-started.md § "Optional: LSP-aware code navigation"), route-handler enumeration, model walks, and test-name extraction are ~3-15× cheaper in token cost than grep + Read. Without LSP, the skill falls back to grep transparently using the framework signatures listed below. No new failure mode, just optional speed.
The skill detects the active language from package.json / pyproject.toml / Gemfile / go.mod / Cargo.toml and dispatches to the matching axis-walker logic regardless of LSP state.
Read the registry path via portfolio_registry and the per-project docs dir via portfolio_projects_dir from .claude/hooks/_lib-portfolio-paths.sh. Source the helper at the top of any bash block that touches those paths:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
projects_dir=$(portfolio_projects_dir)
Defaults match today's single-fork layout (./projects). Adopters in split-portfolio mode override the portfolio.{registry, projects_dir} keys in .claude/project-config.json — the helper resolves whichever mode they're in. See docs/multi-project.md.
Write targets (see me2resh/apexyard#373 + #443): paths documented as projects/<name>/X in this skill are canonical adopter-facing forms — implement them in bash as "${projects_dir}/<name>/X". Never construct from "${PWD}/projects/...", "$(git rev-parse --show-toplevel)/projects/...", or a literal ./projects/... — those break in split-portfolio v2 mode where projects_dir resolves to a sibling repo.
REQUIRED per-block preamble (see #443): Claude executes each bash block as a separate shell invocation. The projects_dir assignment from the Path resolution section above does NOT carry into later blocks. Every bash block that writes to a projects/<name>/X path MUST start with this three-line preamble so it's self-contained:
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
projects_dir=$(portfolio_projects_dir)
# ... now write to "${projects_dir}/<name>/X"
The Path resolution section's example sources the helper once for documentation purposes; it does not absolve later blocks from sourcing it themselves. Treat each bash fence as a fresh process.
/extract-features # current project (cwd inside workspace/<name>/)
/extract-features billing-api # registered project; resolve to workspace/billing-api/
/extract-features . # treat cwd as the project root
/extract-features billing-api --with-mockups # also emit AI-inferred ASCII wireframes per UI screen
If <project-name> is given but workspace/<name>/ doesn't exist, the skill stops and asks the user to clone the project (it does not auto-clone — that's a side-effect with cost; same convention as /handover).
--with-mockups (opt-in, default off)When set, the inventory file gains a new ## Screens section after the existing axes. For each UI screen discovered in axis 5, the skill emits a low-fidelity ASCII wireframe — boxed layout, form fields ([ ] text, [v] dropdown, [X] checkbox, [ Button ] button), tables as ASCII grids, max 80 chars wide.
The wireframes are model-inferred from static analysis — route component imports, form-field bindings, data-model field types. They're not lifts from real DOM. Two design constraints follow:
> AI-inferred sketch — verify before relying on. Source: <route-or-component-path> on its own line above the box.Backward-compat: running /extract-features without --with-mockups produces today's inventory exactly — the flag adds the ## Screens section; nothing else changes.
File-size policy: if more than 10 UI screens are detected, the skill writes one file per screen under <projects_dir>/<name>/screens/<slug>.md and the ## Screens section in the inventory becomes a linked index. Threshold rationale in AgDR-0036.
projects/<name>/feature-inventory.md ← the artefact
The file is a one-off scan, not a recurring audit. There is no audit-history rotation. If the file already exists, the skill OFFERS (default-no) to overwrite — accept only if the codebase has changed substantially since the last run.
If custom-templates/extract-features.md exists in the configured custom-templates root (per the framework's template-override layer, when available), use it as the artefact template. Otherwise use the framework default in step 4 below. Resolve via:
custom_template=""
if [[ -n "${APEXYARD_CUSTOM_TEMPLATES_ROOT:-}" ]] \
&& [[ -f "${APEXYARD_CUSTOM_TEMPLATES_ROOT}/extract-features.md" ]]; then
custom_template="${APEXYARD_CUSTOM_TEMPLATES_ROOT}/extract-features.md"
fi
If the template-override layer is not yet present in this fork, the variable is unset and the skill silently uses the default. Single-fork mode adopters are unaffected.
. → use cwd.workspace/<name>/ (the live working copy). If the workspace clone doesn't exist, prompt the user to clone first; do not auto-clone.workspace/<name>/ → use that project.If the resolved target has none of the discovery signals (no package.json, pyproject.toml, Gemfile, go.mod, Cargo.toml, no source dirs) → stop and tell the user there's nothing to scan.
Capture and report the scope: which subdirectories will be walked, which will be skipped (vendored: node_modules, vendor, .venv, target, dist, build, coverage, .next, .nuxt).
Same detection table as /handover step 3 — minimum information to dispatch the per-axis walkers:
| Signal | Stack |
|---|---|
package.json | Node — read dependencies / devDependencies to identify framework |
pyproject.toml / requirements.txt / setup.py | Python |
Gemfile / Gemfile.lock | Ruby |
go.mod | Go |
Cargo.toml | Rust |
composer.json | PHP |
pom.xml / build.gradle | JVM |
Multiple stacks in one repo (monorepo, polyglot) → walk each subroot independently and merge findings under one inventory.
Run the six axis-walkers below. Each produces a list; the consolidated matrix in step 3 dedupes across axes (a route handler covered by a test name shouldn't appear twice under different "features").
When LSP is enabled and the per-language plugin is installed, prefer documentSymbol over grep for handler / class / function enumeration. When LSP is absent, use the grep signatures listed.
Routes describe the HTTP shape of the system — every URL the system exposes is a candidate user-facing feature.
| Framework | Signature (grep fallback) |
|---|---|
| Express / Connect | app\.(get|post|put|patch|delete|all)\s*\( ; router\.(get|post|...) |
| Fastify | (fastify|app)\.(get|post|put|patch|delete|route)\s*\( |
| NestJS | @(Get|Post|Put|Patch|Delete|All|Options|Head)\s*\( |
| Hapi | server\.route\s*\( |
| Hono / Koa | app\.(get|post|...); router\.(get|post|...) |
| Next.js (Pages Router) | files under pages/api/**/*.{ts,tsx,js,jsx} |
| Next.js (App Router) | files named route.{ts,js} under app/** |
| Remix | files named loader|action exports under app/routes/** |
| FastAPI | @(app|router)\.(get|post|put|patch|delete|api_route)\s*\( |
| Flask | @(app|bp)\.route\s*\( ; @(app|bp)\.(get|post|put|patch|delete) |
| Django | urls.py → path(, re_path(, url( ; class-based views; DRF @api_view, routers.register |
| Rails | config/routes.rb → get, post, resources, resource, namespace, scope |
| Sinatra | ^\s*(get|post|put|patch|delete)\s+['"] |
| Gin | router\.(GET|POST|PUT|PATCH|DELETE|Handle)\s*\( |
| Echo | e\.(GET|POST|...) ; g\.(GET|POST|...) |
| Chi / Fiber | r\.(Get|Post|...) ; app\.(Get|Post|...) |
| Axum | Router::new()\.route\s*\( |
| Actix-web | \.service\s*\( ; web::(get|post|put|patch|delete) |
| Rocket | #\[(get|post|put|patch|delete) |
| Laravel | routes/web.php, routes/api.php → Route::(get|post|...) |
| Spring | @(GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping) |
| AWS SAM / Serverless | template.yaml / serverless.yml → Events: with Api: / HttpApi: |
| GraphQL | Query|Mutation|Subscription resolver definitions; SDL .graphql files |
For each route capture: HTTP method, path, handler symbol (file + line), and any docstring / comment immediately above. The handler name + comment is usually a strong feature signal.
Models describe the data shape of the system — every persistent entity and its relations.
| Framework | Signature |
|---|---|
| Prisma | prisma/schema.prisma → model X { ... } blocks; prisma/migrations/** |
| TypeORM | @Entity\(\) decorators; classes extending BaseEntity |
| Sequelize | sequelize.define\s*\( ; classes extending Model |
| Drizzle | pgTable|mysqlTable|sqliteTable\s*\( ; drizzle.config.{ts,js} |
| Mongoose | mongoose\.(model|Schema) ; new Schema\s*\( |
| Knex | knexfile.{js,ts}; knex.schema.createTable in migrations |
| SQLAlchemy | classes extending Base / db.Model ; __tablename__ ; Column( |
| Django ORM | models.py → classes extending models.Model |
| Active Record (Rails) | app/models/**/*.rb → class X < ApplicationRecord; db/schema.rb ; db/migrate/** |
| GORM (Go) | structs with gorm: tags; db.AutoMigrate( |
| Diesel (Rust) | table! macro; schema.rs |
| ActiveRecord-Java (JPA / Hibernate) | @Entity annotations |
| Raw SQL | migrations/**/*.sql ; db/schema.sql |
For each model capture: name, table name (if different), field list (name + type), relations (@OneToMany, belongs_to, references), and any unique / index constraints. Field names hint at features (an email_verified_at column implies email-verification flow).
Background jobs describe deferred work — usually one job = one async feature (send email, generate report, sync external system).
| Framework | Signature |
|---|---|
| BullMQ / Bull | new Queue\s*\( ; new Worker\s*\( ; \.process\s*\( |
| Bee-queue | new Queue\s*\( (with bee-queue import) |
| Agenda | agenda\.define\s*\( |
| Inngest | inngest\.createFunction\s*\( ; serve\(\{ functions: |
| node-cron | cron\.schedule\s*\( |
| Celery | @app\.task ; @shared_task ; @task |
| RQ | Queue\(.*\)\.enqueue |
| APScheduler | scheduler\.add_job ; @scheduler\.scheduled_job |
| Sidekiq | classes including Sidekiq::Worker / Sidekiq::Job ; perform\( method |
| Resque | classes with @queue = |
| Active Job (Rails) | app/jobs/**/*.rb → class X < ApplicationJob |
| AWS SQS handlers | Lambda handlers with Records[].eventSource == "aws:sqs" ; SAM Events: SQS: |
| AWS EventBridge / cron | SAM Events: Schedule: ; CloudWatch Events rules |
| Cron defs | crontab files; */N * * * * patterns in YAML / config |
| Temporal / Cadence | @workflow.defn ; @activity.defn |
| Faktory | classes including Faktory::Job |
For each job capture: name, trigger (queue name, cron expression, event source), and the handler function. Job names are typically verb-phrases that name a feature directly.
Test names are the cheapest, most accurate signal of what a system DOES — they're written by humans deliberately describing behaviour. Routes describe HTTP shape; models describe data shape; tests describe behaviour. Walking test names surfaces features that routes + models can't (e.g. "user can recover password via email", "admin can bulk-delete users with confirmation").
| Framework | Signature |
|---|---|
| Jest / Vitest / Mocha | describe\s*\(\s*['"];it\s*(\s*['"] ; test\s*\(\s*['"]` |
| Playwright / Cypress | test\s*\(\s*['"];describe\s*(\s*['"] ; it\s*\(\s*['"]` |
| pytest | def test_[a-z_]+\s*\( ; class Test\w+: |
| unittest | def test_[a-z_]+\s*\(self ; class (\w+)\s*\(\s*unittest\.TestCase |
| RSpec | describe\s+['"] ; context\s+['"] ; it\s+['"] |
| Minitest | class \w+ < (Minitest::Test|ActiveSupport::TestCase) ; def test_\w+ ; it ['"] |
| Go testing | func Test\w+\(t \*testing\.T\) ; t\.Run\s*\(\s*" |
| Cargo test | #\[test\] ; mod tests ; fn \w+\(\) inside tests module |
| JUnit | @Test annotations; method names void should_\w+ ; void test\w+ |
| PHPUnit | function test\w+\( ; @test docblocks |
For each test capture: the full describe/context/it sentence (concatenated for nested specs). Cluster the sentences — patterns like "user can …", "admin can …", "guest cannot …" reveal features and roles.
UI components describe the interaction surface of the system — every screen, form, and named interaction is a candidate feature.
| Framework | Signature |
|---|---|
| React (router) | react-router-dom <Route path=; Next.js pages/**; Next.js app/**/page.{tsx,js}; Remix app/routes/** |
| React (components) | top-level function|const \w+ = ... returning JSX in src/components/, src/screens/, src/pages/, src/views/ |
| Vue | .vue files; defineComponent\s*\( ; route files (router/index.ts) |
| Svelte / SvelteKit | routes/**/+page.svelte ; routes/**/+layout.svelte ; .svelte components |
| Angular | @Component\s*\( ; RouterModule\.forRoot\s*\(\s*\[ ; path: entries |
| Form libraries | <form> tags; useForm\(; react-hook-form register\(; formik; <Field name="; Vue v-model |
| Storybook | *.stories.{ts,tsx,js,jsx,mdx} files — story names are user-flow names |
| Tailwind / CSS modules | not features themselves, but presence indicates UI surface area |
| Mobile (RN) | react-navigation Stack.Screen name= ; <Tab.Screen name= |
| Mobile (Flutter) | MaterialPageRoute ; GoRoute ; Navigator.push |
For each screen capture: route path (if router-mapped), component name, and the form fields it contains (if any). Form-field names + labels are explicit feature signals (a confirmEmail field implies email-verification UX).
Documented features are the author's own enumeration — usually the most accurate but least complete (often stale).
Sources:
README* — look for "Features" / "What it does" sections (^##\s+Features? headers and the bullet list that follows)docs/features/** — entire directory if presentdocs/index.md / docs/README.md — top-level docsCHANGELOG* — extract ## [Unreleased] Added and historical ### Added blocksCONTRIBUTING.md — sometimes includes a feature taxonomyopenapi.yaml, swagger.json) — every operation summary is a featureenhancement / feature labels (closed) — can hint at what shipped, but treat as supplementaryFor each documented feature capture: title, source (file + line), and the description verbatim. This is the only axis where the author's intent is recorded; the other five infer it from the code.
Build a single feature matrix that dedupes findings across the six axes. The matrix columns:
| Column | Source |
|---|---|
| Feature | Inferred name (verb-phrase preferred: "Create order", "Reset password via email", "Bulk-delete users") |
| Surface | UI / API / Job / Internal — the entry point the user / operator interacts with |
| Status | Active (referenced from a current code path) / Deprecated (only in CHANGELOG / removed code) / Untested (route exists, no test names match) |
| Source | Which axes corroborated the feature — e.g. route + test, model + UI, doc only |
| Notes | Constraints, side effects, integrations the scanner spotted (e.g. "sends email via SendGrid", "Stripe webhook required", "rate-limited to 10/min") |
Deduplication rules:
route + test + UImodel onlyStatus: Documented but not found in code and a note flagging stale docsAim for 30-150 rows for a typical mid-sized app. If you produce fewer than 10 rows on a non-trivial codebase, the walker missed signatures — flag in "Coverage gaps".
Default template (used when no custom override is configured):
# {project-name} — Feature Inventory
**Date**: {YYYY-MM-DD}
**Scanner**: `/extract-features` (apexyard)
**Scope**: {repo path}
**Stack detected**: {language(s) + framework(s) from step 1}
## Coverage scope
**Walked**:
- {list of subdirectories actually scanned, e.g. `src/`, `app/`, `tests/`, `docs/`}
**Skipped** (vendored / generated / fixtures):
- {list of directories pruned, e.g. `node_modules/`, `dist/`, `.next/`, `coverage/`, `tests/fixtures/`}
**Axes that produced findings**:
- {checked list of the six axes, with `(N items)` per axis}
## Consolidated feature matrix
| # | Feature | Surface | Status | Source | Notes |
|---|---------|---------|--------|--------|-------|
| 1 | Create order | API + UI | Active | route + test + UI | POST `/api/orders`; charges Stripe; sends confirmation email |
| 2 | Reset password via email | UI + Job | Active | route + test + UI + job | one-time token expires in 1h |
| ... | ... | ... | ... | ... | ... |
## Per-axis findings
### HTTP routes / entry points ({N})
| Method | Path | Handler | File | Notes |
|--------|------|---------|------|-------|
| ... | ... | ... | ... | ... |
### Data models / DB schema ({N})
| Model | Table | Fields | Relations | File |
|-------|-------|--------|-----------|------|
| ... | ... | ... | ... | ... |
### Async jobs / queue handlers ({N})
| Job | Trigger | Handler | File |
|-----|---------|---------|------|
| ... | ... | ... | ... |
### Test names ({N})
Grouped by file or feature cluster:
#### `tests/orders.test.ts`
- Order creation › creates order with valid payload › returns 201
- Order creation › rejects invalid currency › returns 400
- Order creation › sends confirmation email on success
- ...
### UI screens / forms / interactions ({N})
| Route | Component | Fields | File |
|-------|-----------|--------|------|
| ... | ... | ... | ... |
### Documented features ({N})
| Title | Source | Description |
|-------|--------|-------------|
| ... | ... | ... |
{IF --with-mockups, INSERT the `## Screens` section here. Each wireframe carries
the mandatory disclaimer header `> AI-inferred sketch — verify before relying
on. Source: <path>` on its own line above the ASCII box. See step 4b.}
## Coverage gaps
The scanner could **not** determine these — they need human review of the existing code or stakeholder interviews:
- **Business rules embedded in code logic** — discount stacking, eligibility checks, fraud heuristics. The scanner sees the function, not the policy.
- **Integration patterns** — webhook signature schemes, retry policies, dead-letter queues unless they're explicit in IaC.
- **Permission / authorisation matrix** — which roles can do what. Routes show endpoints; only manual review of guards / middleware reveals the full matrix.
- **Configuration-driven behaviour** — feature flags, environment-specific toggles.
- **Implicit features in cron / SQS handlers** with generic names — a job called `process_queue` doesn't name a feature.
- **Data-cleanup / TTL policies** — usually only in DB triggers or cron specs.
- **Stale documented features** — entries in CHANGELOG / README that reference removed code.
## Recommended next steps
1. **Review with the previous owner** — reconcile the matrix with their mental model. Expect ~10-20% drift (features they think exist that don't, features that exist they forgot about).
2. **Write user stories per matrix row** — translate "GET /api/orders" into "As a customer, I want to view my orders". The inventory is the input; user-story authoring is a human task (consider `/feature` for each story you intend to ship in v1).
3. **Identify the smallest-coherent-subset for v1 of the rewrite** — not every feature needs to ship in the first release. Bucket features into `must-have v1` / `nice-to-have v1.x` / `defer / drop`.
4. **Validate the deferred / dropped buckets with stakeholders** — the prior-art bias makes the inventory feel exhaustive, but rewriting is also an opportunity to drop dead weight.
5. **Run `/handover {project-name}`** if not already — for the high-level project assessment and integration plan that complements this granular inventory.
6. **Use `/c4 {project-name} --level=2`** to capture the existing system's container topology — pairs well with the inventory as input to a rewrite design.
## Open questions
- {anything axis-specific the scanner couldn't resolve, e.g. "saw `app.use(authMiddleware)` but couldn't trace the auth scheme — JWT? session? OAuth?"}
--with-mockups is set)For each UI screen captured in axis 2e, build a low-fidelity ASCII wireframe. Append the section to the inventory if 10 or fewer screens; write per-screen files at <projects_dir>/<name>/screens/<slug>.md and replace the inventory's ## Screens body with a linked index if more than 10 screens.
Every wireframe carries this exact one-line header on its own line above the box:
> AI-inferred sketch — verify before relying on. Source: <route or component path>
The <route or component path> is the source detection result from the rules below — the file (or route) the wireframe was inferred from. No exceptions. A wireframe without a disclaimer is a broken artefact.
Source: value in the disclaimer)Pick the strongest signal available, in this order:
| Stack | Source |
|---|---|
| Next.js (App Router) | app/<segments>/page.{tsx,jsx,js} |
| Next.js (Pages Router) | pages/<path>.{tsx,jsx,js} |
| Remix | app/routes/<route>.{tsx,jsx,js} |
| SvelteKit | src/routes/<path>/+page.svelte |
| React Router (declared) | the route's element= component file (e.g. src/pages/LoginPage.jsx) |
| Vue Router | the route's component: SFC path (e.g. src/views/Login.vue) |
| Angular | the route's component: path |
| Plain component (no router) | the component file itself (e.g. src/components/SignupForm.tsx) |
If a screen has both a route AND a component, prefer the route (it's the user-addressable name) and reference the component in the inventory's "UI screens" table.
The wireframe is built from these signals — all available statically, no runtime probing:
| Signal | Inferred wireframe element |
|---|---|
Imported <TopNav> / <Header> / <NavBar> component | Top nav strip at the top of the box |
Imported <Sidebar> / <SideNav> / <Drawer> (persistent) | Left sidebar column |
Imported <Footer> | Footer strip at the bottom of the box |
<Modal> / <Dialog> / <Drawer> (as the root return) | Centered modal layout (smaller box, dimmed-overlay hint) |
Form components (<form>, useForm, react-hook-form, Formik, Vue v-model) | Form section with one row per field |
Form field bound to a string model field, or <input type="text|email|password"> | Field name: [ ____________________ ] |
Form field bound to a boolean model field, or <input type="checkbox"> | [ ] Field label (use [X] for default-checked) |
Form field bound to an enum model field, <select>, <Dropdown>, <RadioGroup> | Field name: [v ____________________ ] (the v denotes dropdown caret) |
Foreign-key field (Prisma @relation, belongs_to, etc.) | Field name: [v ____________________ ] |
Form field bound to number / int / float model field | Field name: [ 0__________________ ] |
<textarea> or string field with @db.Text / large size hint | Multi-line [ ___________________ ] (3 lines) |
<button type="submit"> / <Button> with submit semantics | [ Submit ] (use the actual label when known) |
| Cancel / secondary buttons | [ Cancel ] |
<Table> / <DataGrid> / <List> imports bound to a model | ASCII grid: header row from model fields, 2-3 stub data rows |
<Card> clusters in a dashboard layout | Boxed cards in a grid; chart placeholders as [░░ chart ░░] |
When a signal is absent (no nav, no footer, etc.), omit that element entirely. Do not invent layout the code doesn't imply.
Use ASCII box-drawing with +, -, |. Maximum width 80 chars (the disclaimer line is exempt). Examples:
Form-heavy screen:
+----------------------------------------------------------------------------+
| TopNav: [Logo] Home Orders Account [Log out] |
+----------------------------------------------------------------------------+
| |
| Sign in |
| |
| Email: [ _______________________________________________________ ] |
| Password: [ _______________________________________________________ ] |
| [ ] Remember me |
| |
| [ Sign in ] [ Forgot password? ] |
| |
+----------------------------------------------------------------------------+
Table-heavy screen:
+----------------------------------------------------------------------------+
| TopNav: [Logo] Home Orders Account [Log out] |
+----------------------------------------------------------------------------+
| |
| Orders [ + New order ] |
| |
| +-------+-------------+----------+--------+----------+-----------+ |
| | ID | Customer | Total | Status | Created | | |
| +-------+-------------+----------+--------+----------+-----------+ |
| | 1001 | ... | ... | ... | ... | [ View ] | |
| | 1002 | ... | ... | ... | ... | [ View ] | |
| | 1003 | ... | ... | ... | ... | [ View ] | |
| +-------+-------------+----------+--------+----------+-----------+ |
| |
+----------------------------------------------------------------------------+
Modal screen:
+----------------------------------------------------------------------------+
| ░░░░░░░░░░░░░░░░░░░░░░░ (dimmed background) ░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
| |
| +----------------------------------------------+ |
| | Confirm deletion [X] | |
| +----------------------------------------------+ |
| | | |
| | Are you sure you want to delete this | |
| | order? This action cannot be undone. | |
| | | |
| | [ Cancel ] [ Delete ] | |
| +----------------------------------------------+ |
| |
+----------------------------------------------------------------------------+
Dashboard screen (mixed cards + chart):
+----------------------------------------------------------------------------+
| TopNav: [Logo] Home Orders Account [Log out] |
+--------------+-------------------------------------------------------------+
| Sidebar | |
| - Overview | Overview |
| - Orders | |
| - Users | +----------------+ +----------------+ +----------------+ |
| - Settings | | Orders today | | Revenue (MTD) | | Active users | |
| | | [ 142 ] | | [ $12,840 ] | | [ 893 ] | |
| | +----------------+ +----------------+ +----------------+ |
| | |
| | Orders over time |
| | [░░░░░░░░░░░░░░░░░░░░ chart ░░░░░░░░░░░░░░░░░░░░░] |
| | |
+--------------+-------------------------------------------------------------+
Count the UI screens from axis 2e:
≤ 10 screens → emit all wireframes inline under ## Screens in the inventory file.
> 10 screens → emit one file per screen at <projects_dir>/<name>/screens/<slug>.md (slug from the route path or component name, kebab-case). The inventory's ## Screens section becomes a linked index:
## Screens
> AI-inferred sketches — each linked file carries its own disclaimer.
| # | Screen | Source | Wireframe |
|---|--------|--------|-----------|
| 1 | Login | `src/pages/LoginPage.jsx` | [screens/login.md](./screens/login.md) |
| 2 | Orders list | `src/pages/OrdersPage.jsx` | [screens/orders.md](./screens/orders.md) |
Each per-screen file has the disclaimer header at the top followed by the box.
The threshold is documented and overridable; see AgDR-0036.
Write projects/<name>/feature-inventory.md. If the file exists, prompt:
projects/<name>/feature-inventory.md already exists (last written {date}).
Overwrite? [y/N]
Default N. On N, write to projects/<name>/feature-inventory-{YYYY-MM-DD}.md and tell the user where the new file landed. The original is preserved.
Print a one-line summary on completion:
Feature inventory written: projects/<name>/feature-inventory.md
{N} features cataloged across {axes-with-findings}/6 axes.
{N} coverage gaps flagged. {N} recommended next steps.
When --with-mockups is set, append a second line:
ASCII wireframes emitted: {N} screen(s) {inline | under projects/<name>/screens/}.
All wireframes carry the AI-inferred disclaimer — verify before relying on.
A greenfield rewrite that proceeds without a feature inventory has two common failure modes:
The inventory is the single artefact that cuts both. It's not a spec, it's not a migration plan — it's a catalogue. The team negotiates trade-offs (drop / redesign / keep-as-is) against a known list, not against vibes.
/api/orders exists"; only a human can decide it should become "As a customer, I want to place an order with one-click checkout". Translation is design work, not scanner work./code-review or /launch-check. The inventory says what features exist; it doesn't say whether they're well-implemented, secure, or production-ready. Use the audit skills for quality signals.--with-mockups wireframes. The header is the trust contract for the artefact — the reader sees AI-inferred sketch — verify before relying on and treats it as a sketch. Without the header, an ASCII box looks more authoritative than it should. See AgDR-0036.--with-mockups to PNG/SVG. The format is ASCII by design — visual fidelity should match epistemic confidence. A pixel-perfect render of a model's guess at a screen is exactly the failure mode the flag was designed to avoid.Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.
DFD with trust boundaries + data classifications (Mermaid + optional Threat Dragon JSON). Source-of-truth for /threat-model.
Per-feature Mermaid flowchart — routes / models / jobs / screens. Consumes /extract-features inventory.
Onboard an external repo via a structured handover assessment + harnessability scoring across 5 codebase dimensions.
Self-contained HTML user-journey map (boxes/arrows with per-page modals) — preview between PRD and tech-design.
Interview-driven initiative → milestones → tasks with dependency-aware sequencing. Walks the operator from initiative-level goal through per-milestone Socratic interview, computes a topo-sorted recommended sequence, and optionally files each milestone as a Feature-shape ticket with `blocks` / `blocked by` cross-refs.
Extract a business process from registered repos via 7-axis code scan + gap-targeted interview, then emit lint-clean BPMN 2.0.