Distribution
distribution is a leaf: it depends on kernel only, and knows no tool and no manifest. It
answers exactly one question — where does content come from, and how is it fetched — for
whoever asks. framework is the only context that reaches it (framework → distribution); a
plugin's own content and how it gets translated are someone else's job once it has arrived here.
What goes in
| Concept |
Location |
| A marketplace registration (name, source, scope, staleness) |
domain/marketplace.ts, domain/marketplace-source-mode.ts |
| A cached catalog fetch |
domain/ports/marketplace-cache.ts |
| The plugin catalog shape |
domain/catalog.ts (the Claude-shaped parser lives here too) |
| A reader for a non-Claude catalog shape |
domain/catalog-parsers/ |
| A port this context's callers hold |
domain/ports/ — registry, cache, trust-store, catalog-repository, fetcher, raw-catalog-fetcher |
| Add / list / refresh / register / resolve / fetch a marketplace source |
application/ |
| The concrete adapter behind one of the six ports |
infrastructure/ |
How
- This context is a leaf by construction: it must never gain an edge to
tools, translate, or
framework — tests/architecture/context-graph.arch.test.ts enforces the chain
(framework → distribution, plus everything to kernel) and fails the build the moment a new
edge appears. If a change seems to need one, the orchestration belongs to the caller
(framework), not here — see BASELINE in tests/architecture/helpers.ts for the one
documented exception, distribution->framework, and the comment there that explains it
(marketplace add --overwrite removing before adding), which is framework work that has not
yet been moved out.
- A port here follows the port/adapter rule in
.claude/rules/00-architecture/: interface only, ≤5
methods, no null in the return type unless "not found" is genuinely a normal domain state
(documented per-port, not assumed).
- An adapter owns every technical constant for its integration (API base URLs, cache TTLs,
error-pattern regexes for classifying a third-party failure) — none of that belongs in a port,
a use-case, or a domain model.
try/catch inside an adapter exists only to translate a raw
error into a typed one from kernel/errors.ts.
- A new foreign catalog shape gets its own parser in
domain/catalog-parsers/, producing the
same PluginCatalog/PluginCatalogEntry shape the Claude parser produces — callers above this
context never branch on which format a catalog came from.
- Follow the use-case rule in
.claude/rules/00-architecture/ for the application layer's shape.
Public surface
Nothing outside contexts/distribution/ may import a module this context has not declared
public — tests/architecture/context-boundary.arch.test.ts holds the list
(PUBLIC_MODULES.distribution). Measured at extraction, ten modules were reached from outside
and not one was an adapter: the adapters are wired by the composition root
(runtime/wiring/distribution.ts) alone, and stay internal for that reason. A module that
exposes its own plumbing to a caller outside the composition root is not a leaf context anymore
— keep new adapters unreachable from outside.
How it's tested
tests/contexts/distribution/ mirrors src/contexts/distribution/ — domain models and
application use-cases are unit-tier; adapters against a real temp filesystem or a mocked
network boundary are integration-tier. See the test skill for tier conventions.
- A new catalog parser needs a fixture of the real foreign format and a test asserting the parsed
PluginCatalog matches what the Claude-shaped parser would produce for an equivalent catalog.
1---2name: distribution3description: Owns where plugin and marketplace content comes from and how it is fetched, under src/contexts/distribution/ — marketplace registration, catalog parsing, and the ports/adapters that reach git and HTTP. Use when adding a new marketplace source kind, a catalog parser for a foreign format, or a fetch/cache/trust-store adapter. Do NOT use for what a tool does with fetched content — use `tools` or `translate`. Do NOT use for recording what got installed on a project — use `framework`.4---56# Distribution78`distribution` is a leaf: it depends on `kernel` only, and knows no tool and no manifest. It9answers exactly one question — where does content come from, and how is it fetched — for10whoever asks. `framework` is the only context that reaches it (`framework → distribution`); a11plugin's own content and how it gets translated are someone else's job once it has arrived here.1213## What goes in1415| Concept | Location |16|---|---|17| A marketplace registration (name, source, scope, staleness) | `domain/marketplace.ts`, `domain/marketplace-source-mode.ts` |18| A cached catalog fetch | `domain/ports/marketplace-cache.ts` |19| The plugin catalog shape | `domain/catalog.ts` (the Claude-shaped parser lives here too) |20| A reader for a non-Claude catalog shape | `domain/catalog-parsers/` |21| A port this context's callers hold | `domain/ports/` — registry, cache, trust-store, catalog-repository, fetcher, raw-catalog-fetcher |22| Add / list / refresh / register / resolve / fetch a marketplace source | `application/` |23| The concrete adapter behind one of the six ports | `infrastructure/` |2425## How2627- This context is a leaf by construction: it must never gain an edge to `tools`, `translate`, or28 `framework` — `tests/architecture/context-graph.arch.test.ts` enforces the chain29 (`framework → distribution`, plus everything to `kernel`) and fails the build the moment a new30 edge appears. If a change seems to need one, the orchestration belongs to the caller31 (`framework`), not here — see `BASELINE` in `tests/architecture/helpers.ts` for the one32 documented exception, `distribution->framework`, and the comment there that explains it33 (`marketplace add --overwrite` removing before adding), which is framework work that has not34 yet been moved out.35- A port here follows the port/adapter rule in `.claude/rules/00-architecture/`: interface only, ≤536 methods, no `null` in the return type unless "not found" is genuinely a normal domain state37 (documented per-port, not assumed).38- An adapter owns every technical constant for its integration (API base URLs, cache TTLs,39 error-pattern regexes for classifying a third-party failure) — none of that belongs in a port,40 a use-case, or a domain model. `try/catch` inside an adapter exists only to translate a raw41 error into a typed one from `kernel/errors.ts`.42- A new foreign catalog shape gets its own parser in `domain/catalog-parsers/`, producing the43 same `PluginCatalog`/`PluginCatalogEntry` shape the Claude parser produces — callers above this44 context never branch on which format a catalog came from.45- Follow the use-case rule in `.claude/rules/00-architecture/` for the application layer's shape.4647## Public surface4849Nothing outside `contexts/distribution/` may import a module this context has not declared50public — `tests/architecture/context-boundary.arch.test.ts` holds the list51(`PUBLIC_MODULES.distribution`). Measured at extraction, ten modules were reached from outside52and not one was an adapter: the adapters are wired by the composition root53(`runtime/wiring/distribution.ts`) alone, and stay internal for that reason. A module that54exposes its own plumbing to a caller outside the composition root is not a leaf context anymore55— keep new adapters unreachable from outside.5657## How it's tested5859- `tests/contexts/distribution/` mirrors `src/contexts/distribution/` — domain models and60 application use-cases are unit-tier; adapters against a real temp filesystem or a mocked61 network boundary are integration-tier. See the `test` skill for tier conventions.62- A new catalog parser needs a fixture of the real foreign format and a test asserting the parsed63 `PluginCatalog` matches what the Claude-shaped parser would produce for an equivalent catalog.