Skip to content
Back to skills

Map Events

ASecurity

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).

  • 20 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
ai-agentspythonrustgorubyphpc#shellbashnodegit

Works with

  • cli

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add melodic-software/claude-code-plugins --skill map-events --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Map Events?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Map Events
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/melodic-software-map-events/badge)](https://www.skillsdirectory.com/skills/melodic-software-map-events)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
description: "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)."
argument-hint: "[--unrouted-only] [--out <dir>]"
user-invocable: true
disable-model-invocation: false
shell: bash
metadata:
  workflow-stage: explore
  summary: Chart publishers, consumers, and orphan messages
---

## 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>` and
`ConfigureConsumer<C>` register the consumer class `C`; they are not messages. Inside a
`ReceiveEndpoint("q", ..)` call, on its own line or in its block, they put `C`'s consume edges on
queue `q`; a registration outside it and every publish or send edge has no queue. 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.

Read: C# (MassTransit shape). Declined: Node, Go, Python, Rust, JVM, Ruby, and PHP. Their files are
never read, so a message only they publish or consume is absent from the chart, not a finding.

## 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

```bash
"${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 past
`fanout_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 dotted name is tried as written, then
under each enclosing namespace and each `using`; `global::` allows only the as-written match. A
`Publish(` or `Send(` whose type does not resolve (a variable, an anonymous or target-typed `new`,
an undeclared or ambiguous name, a dotted name that matches no declared type) is an edge with
`resolution: unresolved` and an `unresolved` finding naming the kind, the type text or `-`, and
`file:line`. It is never omitted, and an unresolved consumer is never an orphan.

## Render

```bash
"${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 lists one line per publish or send edge, keyed by `file:line` so it
matches a `/architecture:map-flow` hop cite; map-flow reads nothing from `events.md`:
`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 written` when 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_dialect` was not read. No key was added.
- **Handoff**: that cross-process edges are in the Handoff section, keyed by `file:line` to match a map-flow hop cite.

## Interactive view

After the report, offer an interactive view of `events.json` in one sentence. The markdown and the record
stay authoritative. Build it only with `${CLAUDE_PLUGIN_ROOT}/scripts/build-view.mjs events`, never
hand-written; the publish destination comes from the `medium` cascade key. Procedure:
[`${CLAUDE_PLUGIN_ROOT}/reference/rendered-view.md`](${CLAUDE_PLUGIN_ROOT}/reference/rendered-view.md).

## 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_dialect` or 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 of `T`. Verified 2026-09-28 against
  <https://masstransit.io/documentation/concepts/consumers>. Recheck when that page stops using
  `IConsumer<T>` as the consumer contract, or when the producers page stops distinguishing
  publish from send.
- **Identity is the resolved type.** `Billing.Contracts.OrderPlaced` and
  `Shipping.Contracts.OrderPlaced` are different messages. A short name that matches two types
  stays unresolved rather than being joined, and a dotted name that matches no declared type
  (an external package's contract, for one) stays unresolved rather than being trusted.
- **An unresolved publish or send is a finding.** Dropping it would make an orphan-consumer
  finding wrong. The edge is listed with `resolution: unresolved` and satisfies no consumer.
- **A registered consumer is not a message.** `AddConsumer<C>` names the consumer class. The
  message is the `T` in `C`'s `IConsumer<T>`, so a registration never yields an orphan.
- **Findings are not the diagram.** `--unrouted-only` filters arrows. The findings list remains.
- **No dialect key.** The flowchart is mermaid whatever `landscape_dialect` says: a dotted arrow
  for broadcast, a solid arrow for point-to-point.
- **A reformatted record is refused.** `render-events.sh` exits 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>`, and `ConfigureConsumer<C>`.
  A call split across lines is not joined. A `.Publish(` or `.Send(` from another library is
  still listed, not dropped.
- **Only `ReceiveEndpoint("literal", ..)` binds a queue.** A topic, exchange, subject, or
  configuration-file binding is not read, and neither is a queue name held in a variable. A
  consumer bound that way shows no queue, so no competing-consumer finding is raised for it.

Files in this skill

  • SKILL.md7.2 KB
  • evals/evals.json2.5 KB
  • scripts/collect-events.sh11.3 KB
  • scripts/collect-events.test.sh9.4 KB
  • scripts/render-events.sh6.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…