"Chart async message topology from committed C# publish, send, and consumer registrations. Orphan publishers and consumers are findings. Same short name in two namespaces is two messages. Use when: 'map events', 'who publishes this message', 'message topology', 'orphan consumer', 'publish subscribe'. Skip when: the question is an in-process call trace (map-flow) or broker hosts and replicas (a deployment view)."
Resources
2Install
npx skillscat add melodic-software/claude-code-plugins/plugins-architecture-skills-map-events Install via the SkillsCat registry.
Repository context
The current repository is both the CONSUMER and the DEFAULT SUBJECT. Collect the project root with
one Bash call, git rev-parse --show-toplevel. A failure is an unknown value; ${CLAUDE_PROJECT_DIR}
is the root either way.
Purpose
Answer "who publishes which message, and who consumes it" from committed C# call sites and handler
registrations. Every edge cites a file and a line. The scripts collect and render. Do not add an
edge the script did not emit, and do not drop a publish whose contract type did not resolve.
The shipped adapter is the MassTransit shape: Publish<T> or Publish(new T(..)) is broadcast,Send<T> or Send(new T(..)) is point-to-point, IConsumer<T> consumes T. AddConsumer<C> andConfigureConsumer<C> register the consumer class C; they are not messages. InsideReceiveEndpoint("q", ..) they put C's consume edges on queue q. A message is a type some
publish, send, or consume names; a service or consumer class is not one. Identity is the
namespace-qualified type. In-process calls are /architecture:map-flow. A cross-process hop in
that trace is a hand-off to this skill.
Resolve home
Read ${CLAUDE_PLUGIN_ROOT}/reference/config.md first. This skill writes into architecture_dir.
It reads no dialect key and adds none: it always writes events.md, a mermaid flowchart (dotted
broadcast, solid point-to-point). The authoring-formats convention keys a dialect only for data
diagrams and C4 system views. A message-topology flowchart is neither, so it follows the unkeyed
mermaid rule, as /planning:design's sequence flows do.
Run bash "${CLAUDE_PLUGIN_ROOT}/lib/resolve-convention-home.sh" --root "${CLAUDE_PROJECT_DIR}".
Exit 0 means read <home>/architecture/README.md for architecture_dir. Exit 1, 2, and 3 mean
there is no declared home.
--out <dir> wins for this run. Then a declared architecture_dir. Then one question.architecture_dir has NO default. An undeclared and unconfirmed home, including every
non-interactive run, STOPS and points at /architecture:setup.
Build the record
"${CLAUDE_SKILL_DIR}/scripts/collect-events.sh" \
--repo "<subject-repo>" --out "<architecture_dir>/events.json"The record is schema_version 1. Message lines start with {"id":. Edge lines start with{"from":. Finding lines start with {"kind":. A message line cites the file and line that
declare the type. An edge's from is the file of the publishing, sending, or consuming call site,to is the resolved message (- when unresolved), and kind is publish, send, or consume. Findings include orphan publishers, orphan consumers, fan-out pastfanout_threshold (3), competing consumers on one queue, and unresolved edges.fanout_threshold is this plugin's limit, not the broker's.
A short type name resolves through the file's namespace, its parent namespaces, and its using
directives, then through the one repo type with that name. A Publish( or Send( whose type
does not resolve (a variable, an anonymous or target-typed new, an undeclared or ambiguous name)
is an edge with resolution: unresolved and an unresolved finding naming the kind, the type
text or -, and file:line. It is never omitted.
Render
"${CLAUDE_SKILL_DIR}/scripts/render-events.sh" \
--record "<architecture_dir>/events.json" --out "<architecture_dir>"Add --unrouted-only when the invocation asked for orphans and unresolved edges only. The findings
list is still written.
The script prints:events: messages=<n> publishers=<n> consumers=<n> unresolved=<n> orphans=<n> unrouted_only=<yes|no>.
The artifact's Handoff section is the form /architecture:map-flow consumes:handoff: map-events contract=<type> direction=<publish|send> file=<path> line=<n>.
Exit 1 means the record is unreadable or not one object per line. Nothing was written.
Close with the report
- Artifacts: each path written, or
none writtenwhen the run stopped before a home existed. - Messages:
messages=from the summary. Say that identity is the resolved type. - Edges:
publishers=,consumers=,unresolved=. - Findings:
orphans=and the other finding kinds present. - Filter:
unrouted_only=yes or no. Findings are listed either way. - Dialect: mermaid flowchart.
landscape_dialectwas not read. No key was added. - Handoff: that cross-process edges are in the Handoff section for
/architecture:map-flow.
What this skill does NOT do
- In-process synchronous calls. Those are
/architecture:map-flow. - Retry, dead-letter, or delivery guarantees.
- Broker hosts, replicas, or partitions. Those are a deployment view.
- Treat two types with the same short name as one message.
- Drop an unresolved publish.
- Read
landscape_dialector add a dialect key. The flowchart is always mermaid. - Invent a home.
Next
- Trace one entry point up to the broker:
/architecture:map-flow <entry>. - The question is which deployables bind the broker:
/architecture:map-containers. - The view settles a decision worth keeping:
/architecture:record-decision.
Gotchas
- Publish and send are different. MassTransit
Publish<T>is broadcast.Send<T>is
point-to-point.IConsumer<T>is a consumer ofT. Verified 2026-09-28 against
https://masstransit.io/documentation/concepts/consumers. Recheck when that page stops usingIConsumer<T>as the consumer contract, or when the producers page stops distinguishing
publish from send. - Identity is the resolved type.
Billing.Contracts.OrderPlacedandShipping.Contracts.OrderPlacedare different messages. A short name that matches two types
stays unresolved rather than being joined. - An unresolved publish or send is a finding. Dropping it would make an orphan-consumer
finding wrong. The edge is listed withresolution: unresolvedand satisfies no consumer. - A registered consumer is not a message.
AddConsumer<C>names the consumer class. The
message is theTinC'sIConsumer<T>, so a registration never yields an orphan. - Findings are not the diagram.
--unrouted-onlyfilters arrows. The findings list remains. - No dialect key. The flowchart is mermaid whatever
landscape_dialectsays: a dotted arrow
for broadcast, a solid arrow for point-to-point. - A reformatted record is refused.
render-events.shexits 1 and writes nothing. - The scan is the MassTransit shape, one line at a time.
Publish<T>,Send<T>,.Publish(new T,.Send(new T,IConsumer<T>,AddConsumer<C>, andConfigureConsumer<C>.
A call split across lines is not joined. A.Publish(or.Send(from another library is
still listed, not dropped.