EARS Action / Endpoint Catalog
You read one or more EARS specification .md files and produce one markdown catalog file
that lists every action (Grails) / endpoint (Spring Boot) those specs describe. Each catalog
row carries five facts:
- Action / Endpoint — the action or endpoint name (e.g.
create, save, approveMemberAdmission).
- Purpose — a one-line, plain-language summary of what that action does, so a reader can see the
whole picture across every EARS file at a glance.
- Module / Domain — the module or domain it belongs to within its EARS file.
- Source Code Reference — the source file path the EARS file records for it.
- Originating EARS File — which input
.md file it came from.
You work primarily from the EARS .md files — the front-matter, module annotations, and content
subsections already record everything you need: the entry points, the action/operation breakdown, the
source paths, and the prose that describes each operation's purpose. "Source-code reference" means the
path strings the EARS file already records, not paths you re-derive by reading source. The one
exception is the last-resort fallback in Step 1: if a controller is named with no enumerable
actions anywhere in the spec, you may open that one source file to list its real action methods rather
than emit a vague placeholder. You are project-neutral: hardcode nothing about any codebase,
framework, or domain. You handle both Grails (actions) and Spring Boot (endpoints) layouts.
Governing principles
- One row per entry point. Every action/endpoint named in an input becomes its own row. If a
controller exposes
create and save, that is two rows. A standalone action/API class is its
own endpoint, hence its own row.
- Enumerate every individual action — never lump. A controller is a bucket of distinct
operations; the catalog must show each one as its own row, never a single
(all actions) summary.
When the front-matter names a controller without listing its actions, recover the individual
operations from the spec's content (its ## Module: subsections and the action-class file names
those subsections cite — see Step 1). Only if a controller has nothing enumerable anywhere in the
spec do you fall back: open that one source file to read its real action methods, and if even that
yields nothing, record a single (all actions) row as the absolute last resort. Never guess action
names that no source supports.
- Every row gets a Purpose. Synthesize a short, code-free one-liner from the operation's EARS
subsection (its heading plus the statements/scope describing it). The Purpose column is what lets a
reader grasp the whole picture across many EARS files at once, so it must be specific to the action,
not a generic restatement of its name.
- Never drop an entry point. Every source file named in an input's entry-point header must
surface in the catalog. The self-check (Step 4) enforces this.
- Don't merge or dedupe across files. Unlike a spec merge, the catalog is a faithful inventory:
the same controller appearing in two EARS files yields rows for both, each tagged with its
originating file. Do not consolidate, renumber, or rewrite statement prose.
Workflow
Step 0 — Gather inputs (do not assume)
- Which inputs. Take the EARS sources from the user's request or arguments. They may be:
- explicit file paths or globs (e.g.
docs/ears/GroupCreation-EARS-Specification.md), or
- a directory — in which case glob it recursively for
*-EARS-Specification.md (fall back to
*.md) and treat every match as an input.
If nothing was given, list the candidate .md files and ask which to catalog.
- Output location and file name (required from the user). Gather the catalog's destination as
two distinct values and combine them into the final path:
- Output location — the directory the catalog is written to (e.g.
docs/ears/member/).
- Output file name — the catalog's file name (e.g.
EARS-Action-Endpoint-Catalog.md).
If the request already states a full path (e.g. "write it to docs/ears/Catalog.md"), split it
into its location and file name and use both verbatim. Otherwise, ask the user for the output
location and the output file name before producing the catalog — suggest docs/ears/ (or the
inputs' own directory) as the location and EARS-Action-Endpoint-Catalog.md as the file name,
which they may accept. Combine the two into <location>/<file name>; do not write anywhere until
the user supplies or confirms both. Overwrite an existing file of that name.
Read every input file in full before writing anything — you cannot catalog what you have not read.
Step 1 — Extract entry points from each file
Read references/ears-anatomy.md for the exact locations and parsing forms. In brief, for each file:
- Primary source: the front-matter entry-point header. Grails specs use
> **Source Entry Point(s):**; Spring Boot specs use > **Source Controller(s):**. Parse it into
(source-file path, [action/endpoint names]) pairs:
- Split the line into entry points on
; and on top-level , between backtick-quoted paths.
- For each backtick path, read any trailing qualifier:
(create + save actions only),
(actions \approveMemberAdmission`, `approveMemberUpdate`), (GET /members, POST /members)`, etc.
Each named action/endpoint → one entry.
- A standalone action/API class path (e.g.
CreateMemberAdmissionApiAction.groovy) with no
qualifier is itself one endpoint; use the class name as the action/endpoint.
- A controller path with no qualifier → defer action enumeration to the module step below.
- Refine with module sections — enumerate every individual action. For a controller that had no
explicit action qualifier, recover its individual operations from the spec itself, in this order:
- The matching
## Module:'s > **Entry Points:** line, if it names concrete actions (e.g.
"Grails controller actions (create, save)" → create, save).
- Otherwise, the module's content subsections. Each
### subsection under the module is a
distinct operation; its > **Source files:** line cites the action class that implements it
(e.g. ### Member Registration → CreateMemberInfoAction.groovy). Emit one row per such
operation, using the action-class name as the Action / Endpoint value (verbatim, e.g.
CreateMemberInfoAction). For the Source Code Reference, use the controller path the
front-matter named as the entry point (the action-class name in the Action column already
pinpoints the implementing class) — this keeps the source reference pointing at the real entry
point and consistent with the qualified-action rows above. See references/ears-anatomy.md §2
for the exact mapping.
- Last-resort fallback only — if a controller has no actions in its qualifier, no concrete
Entry Points: actions, and no module subsections naming action classes, open that one source
controller and list its real action methods (Grails def <name>() actions / Spring @*Mapping
handlers). Note in your final report which controllers required this. If even the source yields
nothing usable, record a single (all actions) row.
Step 2 — Assign Module / Domain to each entry
Match each entry's source-file path to the ## Module: whose > **Source files:** or
> **Entry Points:** references it, and use that module's heading name (or its > **Domain:**
value). If a source file maps to more than one module, list each module the action plausibly belongs
to. If no module references it, use the spec's overall subject (the title/System name).
Step 2.5 — Write each action's Purpose
For every row, write a one-line Purpose from the EARS file (no source reading). Find the entry's
operation in the spec — the ### subsection (or the action qualifier's flow) that describes it — and
distil what it does into a single specific phrase. Draw on the subsection heading, its EARS
statements, and the module > **Scope:** line. Keep it to roughly one line; describe the behaviour
(what it loads/validates/persists/changes), not just a reword of the action name. See
references/ears-anatomy.md "Where Purpose comes from" for the mapping.
Step 3 — Emit the catalog
Write one markdown file. ALWAYS use this structure:
# EARS Action / Endpoint Catalog
> **Generated:** <date>
> **Source EARS files (<N>):**
> - <relative/path/to/input-1.md>
> - <relative/path/to/input-2.md>
> **Note:** One row per Grails action / Spring Boot endpoint, extracted from the EARS files' own
> entry-point, module, and subsection annotations. <Add "No source code was read." only if no
> controller required the Step 1 last-resort source fallback; otherwise list the controllers that did.>
| # | Action / Endpoint | Purpose | Module / Domain | Source Code Reference | Originating EARS File |
|---|-------------------|---------|-----------------|-----------------------|-----------------------|
| 1 | create | Loads the group creation form with its reference data | Group Creation | `plugins/mf/.../GroupInfoController.groovy` | GroupCreation-EARS-Specification.md |
| 2 | save | Validates and persists a submitted group, assigning its identifiers | Group Creation | `plugins/mf/.../GroupInfoController.groovy` | GroupCreation-EARS-Specification.md |
Sort rows by originating file → module → action so related entries sit together. Keep the source
path in backticks, verbatim from the EARS file. Number the # column contiguously from 1.
Step 4 — Self-check before finishing
Run the bundled checker and fix anything it reports:
python3 ~/.claude/skills/merge-ears/scripts/check_catalog.py <output-file> \
--inputs <input1> <input2> [...]
It verifies the structural invariants: the table has the five required columns (including
Purpose), every input file appears in the Originating EARS File column, no row is missing a
Source Code Reference, Action / Endpoint, or Purpose, and every source file named in each
input's entry-point header surfaces in the catalog (no dropped entry point). Treat its findings as a
checklist — resolve each before declaring the catalog done.
Then report to the user: the output path, how many EARS files were read, how many action/endpoint
rows were produced, any controller that required the Step 1 last-resort source fallback, and any
entry point that still yielded only an (all actions) row (so they know where the source spec was
genuinely under-specified).
Anti-patterns (do not do these)
- Merging or deduping. This skill is an inventory, not a spec merge. The same controller in two
EARS files appears as rows under both — do not consolidate, renumber statements, or rewrite prose.
- Reading the codebase to enrich rows the spec already covers. Module/Domain, Source Code
Reference, and Purpose all come from the EARS
.md files. Opening .groovy/.java source is
permitted only as the documented Step 1 last-resort fallback — a controller with no enumerable
actions anywhere in the spec — and never to embellish an action the spec already describes.
- Lumping into
(all actions). Don't collapse a controller into one placeholder row when the
spec's subsections and action-class references let you list the individual operations. Reserve
(all actions) for the genuine dead end where neither the spec nor the source yields anything.
- Inventing actions. Never list an action/endpoint that no source — spec or (in the fallback)
controller — supports.
- Dropping an entry point. Every source file in an input's entry-point header must appear in the
catalog. The checker fails the run if one is missing.
1---2name: merge-ears3description: Build a catalog (index / inventory / manifest) of every Grails action or Spring Boot endpoint found across one or more EARS (Easy Approach to Requirements Syntax) specification .md files, with the source-code reference, module/domain, and originating EARS file for each. Use this whenever the user wants to list, catalog, index, inventory, or tabulate the actions/endpoints described by their EARS specs — e.g. "catalog the actions in these EARS files", "build an action/endpoint index from docs/ears/", "list every endpoint across these specs with its source file", "make a manifest of all controller actions in my EARS specs", or any request to enumerate the entry points of a set of EARS specifications into a single table. Accepts individual EARS files (paths or globs) OR a directory of EARS files — no pre-merging required. Works on any EARS files that follow the standard reverse-engineered layout, Grails or Spring Boot, not just one codebase.4---56# EARS Action / Endpoint Catalog78You read **one or more EARS specification `.md` files** and produce **one markdown catalog file**9that lists every **action** (Grails) / **endpoint** (Spring Boot) those specs describe. Each catalog10row carries five facts:11121. **Action / Endpoint** — the action or endpoint name (e.g. `create`, `save`, `approveMemberAdmission`).132. **Purpose** — a one-line, plain-language summary of what that action does, so a reader can see the14 whole picture across every EARS file at a glance.153. **Module / Domain** — the module or domain it belongs to within its EARS file.164. **Source Code Reference** — the source file path the EARS file records for it.175. **Originating EARS File** — which input `.md` file it came from.1819You work **primarily from the EARS `.md` files** — the front-matter, module annotations, and content20subsections already record everything you need: the entry points, the action/operation breakdown, the21source paths, and the prose that describes each operation's purpose. "Source-code reference" means the22path strings the EARS file already records, not paths you re-derive by reading source. The one23exception is the **last-resort fallback** in Step 1: if a controller is named with *no* enumerable24actions anywhere in the spec, you may open that one source file to list its real action methods rather25than emit a vague placeholder. You are **project-neutral**: hardcode nothing about any codebase,26framework, or domain. You handle both Grails (actions) and Spring Boot (endpoints) layouts.2728## Governing principles29301. **One row per entry point.** Every action/endpoint named in an input becomes its own row. If a31 controller exposes `create` and `save`, that is two rows. A standalone action/API class is its32 own endpoint, hence its own row.332. **Enumerate every individual action — never lump.** A controller is a bucket of distinct34 operations; the catalog must show each one as its own row, never a single `(all actions)` summary.35 When the front-matter names a controller without listing its actions, recover the individual36 operations from the spec's content (its `## Module:` subsections and the action-class file names37 those subsections cite — see Step 1). Only if a controller has *nothing* enumerable anywhere in the38 spec do you fall back: open that one source file to read its real action methods, and if even that39 yields nothing, record a single `(all actions)` row as the absolute last resort. Never guess action40 names that no source supports.413. **Every row gets a Purpose.** Synthesize a short, code-free one-liner from the operation's EARS42 subsection (its heading plus the statements/scope describing it). The Purpose column is what lets a43 reader grasp the whole picture across many EARS files at once, so it must be specific to the action,44 not a generic restatement of its name.454. **Never drop an entry point.** Every source file named in an input's entry-point header must46 surface in the catalog. The self-check (Step 4) enforces this.475. **Don't merge or dedupe across files.** Unlike a spec merge, the catalog is a faithful inventory:48 the same controller appearing in two EARS files yields rows for both, each tagged with its49 originating file. Do not consolidate, renumber, or rewrite statement prose.5051---5253## Workflow5455### Step 0 — Gather inputs (do not assume)5657- **Which inputs.** Take the EARS sources from the user's request or arguments. They may be:58 - explicit file paths or globs (e.g. `docs/ears/GroupCreation-EARS-Specification.md`), or59 - a **directory** — in which case glob it recursively for `*-EARS-Specification.md` (fall back to60 `*.md`) and treat every match as an input.61 If nothing was given, list the candidate `.md` files and ask which to catalog.62- **Output location and file name (required from the user).** Gather the catalog's destination as63 two distinct values and combine them into the final path:64 - **Output location** — the directory the catalog is written to (e.g. `docs/ears/member/`).65 - **Output file name** — the catalog's file name (e.g. `EARS-Action-Endpoint-Catalog.md`).66 If the request already states a full path (e.g. "write it to `docs/ears/Catalog.md`"), split it67 into its location and file name and use both verbatim. Otherwise, **ask the user** for the output68 location and the output file name before producing the catalog — suggest `docs/ears/` (or the69 inputs' own directory) as the location and `EARS-Action-Endpoint-Catalog.md` as the file name,70 which they may accept. Combine the two into `<location>/<file name>`; do not write anywhere until71 the user supplies or confirms both. Overwrite an existing file of that name.7273Read every input file **in full** before writing anything — you cannot catalog what you have not read.7475### Step 1 — Extract entry points from each file7677Read `references/ears-anatomy.md` for the exact locations and parsing forms. In brief, for each file:7879- **Primary source: the front-matter entry-point header.** Grails specs use80 `> **Source Entry Point(s):**`; Spring Boot specs use `> **Source Controller(s):**`. Parse it into81 `(source-file path, [action/endpoint names])` pairs:82 - Split the line into entry points on `;` and on top-level `,` between backtick-quoted paths.83 - For each backtick path, read any trailing qualifier: `(create + save actions only)`,84 `(actions \`approveMemberAdmission\`, \`approveMemberUpdate\`)`, `(GET /members, POST /members)`, etc.85 Each named action/endpoint → one entry.86 - A standalone action/API class path (e.g. `CreateMemberAdmissionApiAction.groovy`) with no87 qualifier is itself one endpoint; use the class name as the action/endpoint.88 - A controller path with **no** qualifier → defer action enumeration to the module step below.89- **Refine with module sections — enumerate every individual action.** For a controller that had no90 explicit action qualifier, recover its individual operations from the spec itself, in this order:91 1. The matching `## Module:`'s `> **Entry Points:**` line, if it names concrete actions (e.g.92 "Grails controller actions (create, save)" → `create`, `save`).93 2. Otherwise, the module's **content subsections**. Each `### subsection` under the module is a94 distinct operation; its `> **Source files:**` line cites the action class that implements it95 (e.g. `### Member Registration` → `CreateMemberInfoAction.groovy`). Emit one row per such96 operation, using the **action-class name** as the Action / Endpoint value (verbatim, e.g.97 `CreateMemberInfoAction`). For the **Source Code Reference**, use the controller path the98 front-matter named as the entry point (the action-class name in the Action column already99 pinpoints the implementing class) — this keeps the source reference pointing at the real entry100 point and consistent with the qualified-action rows above. See `references/ears-anatomy.md` §2101 for the exact mapping.102 3. **Last-resort fallback only** — if a controller has no actions in its qualifier, no concrete103 `Entry Points:` actions, and no module subsections naming action classes, open that one source104 controller and list its real action methods (Grails `def <name>()` actions / Spring `@*Mapping`105 handlers). Note in your final report which controllers required this. If even the source yields106 nothing usable, record a single `(all actions)` row.107108### Step 2 — Assign Module / Domain to each entry109110Match each entry's source-file path to the `## Module:` whose `> **Source files:**` or111`> **Entry Points:**` references it, and use that module's heading name (or its `> **Domain:**`112value). If a source file maps to more than one module, list each module the action plausibly belongs113to. If no module references it, use the spec's overall subject (the title/`System name`).114115### Step 2.5 — Write each action's Purpose116117For every row, write a one-line Purpose from the EARS file (no source reading). Find the entry's118operation in the spec — the `### subsection` (or the action qualifier's flow) that describes it — and119distil what it does into a single specific phrase. Draw on the subsection heading, its EARS120statements, and the module `> **Scope:**` line. Keep it to roughly one line; describe the *behaviour*121(what it loads/validates/persists/changes), not just a reword of the action name. See122`references/ears-anatomy.md` "Where Purpose comes from" for the mapping.123124### Step 3 — Emit the catalog125126Write one markdown file. ALWAYS use this structure:127128```markdown129# EARS Action / Endpoint Catalog130131> **Generated:** <date>132> **Source EARS files (<N>):**133> - <relative/path/to/input-1.md>134> - <relative/path/to/input-2.md>135> **Note:** One row per Grails action / Spring Boot endpoint, extracted from the EARS files' own136> entry-point, module, and subsection annotations. <Add "No source code was read." only if no137> controller required the Step 1 last-resort source fallback; otherwise list the controllers that did.>138139| # | Action / Endpoint | Purpose | Module / Domain | Source Code Reference | Originating EARS File |140|---|-------------------|---------|-----------------|-----------------------|-----------------------|141| 1 | create | Loads the group creation form with its reference data | Group Creation | `plugins/mf/.../GroupInfoController.groovy` | GroupCreation-EARS-Specification.md |142| 2 | save | Validates and persists a submitted group, assigning its identifiers | Group Creation | `plugins/mf/.../GroupInfoController.groovy` | GroupCreation-EARS-Specification.md |143```144145Sort rows by **originating file → module → action** so related entries sit together. Keep the source146path in backticks, verbatim from the EARS file. Number the `#` column contiguously from 1.147148### Step 4 — Self-check before finishing149150Run the bundled checker and fix anything it reports:151152```bash153python3 ~/.claude/skills/merge-ears/scripts/check_catalog.py <output-file> \154 --inputs <input1> <input2> [...]155```156157It verifies the structural invariants: the table has the five required columns (including158**Purpose**), every input file appears in the **Originating EARS File** column, no row is missing a159**Source Code Reference**, **Action / Endpoint**, or **Purpose**, and every source file named in each160input's entry-point header surfaces in the catalog (no dropped entry point). Treat its findings as a161checklist — resolve each before declaring the catalog done.162163Then report to the user: the output path, how many EARS files were read, how many action/endpoint164rows were produced, any controller that required the Step 1 last-resort source fallback, and any165entry point that still yielded only an `(all actions)` row (so they know where the source spec was166genuinely under-specified).167168---169170## Anti-patterns (do not do these)171172- **Merging or deduping.** This skill is an inventory, not a spec merge. The same controller in two173 EARS files appears as rows under both — do not consolidate, renumber statements, or rewrite prose.174- **Reading the codebase to enrich rows the spec already covers.** Module/Domain, Source Code175 Reference, and Purpose all come from the EARS `.md` files. Opening `.groovy`/`.java` source is176 permitted *only* as the documented Step 1 last-resort fallback — a controller with no enumerable177 actions anywhere in the spec — and never to embellish an action the spec already describes.178- **Lumping into `(all actions)`.** Don't collapse a controller into one placeholder row when the179 spec's subsections and action-class references let you list the individual operations. Reserve180 `(all actions)` for the genuine dead end where neither the spec nor the source yields anything.181- **Inventing actions.** Never list an action/endpoint that no source — spec or (in the fallback)182 controller — supports.183- **Dropping an entry point.** Every source file in an input's entry-point header must appear in the184 catalog. The checker fails the run if one is missing.