Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ implementation whose only job is to prove the conventions compile, run, and fail
| `src/tests/Application.UnitTests` | The service, against a TUnit.Mocks substitute |
| `src/tests/Architecture.UnitTests` | Dependency-direction rules; a wrong `ProjectReference` fails the build |
| `src/tests/SharedTestingInfra` | `TestBuilder`; referenced automatically by every test project |
| `skills/` | Authored agent skills, installable with `npx skills add purview-dev/engineering-playbook` |
| `templates/` | Ready-to-copy starter files for a repository adopting the playbook |
| `src/Directory.Build.props` | `NamespacePrefix`, the SDK import, and `NamespaceRemoveSuffix` |
| `.config/purview-build.json` | Pipeline configuration: solution, test discovery, filter |
Expand Down Expand Up @@ -64,6 +65,15 @@ demonstrate it.
- **Fences always declare a language.** Mermaid is not enabled in `mkdocs.yml`, so raw Mermaid source goes in
a ` ```text ` block.

## Skills

Authored skills live in `skills/<name>/SKILL.md` at the repository root — not in `.agents/skills/`, which
is the gitignored mirror that packages write into. The root folder is the one the skills CLI can discover
in a git clone. A skill states when it applies in its `description`, keeps the workflow in `SKILL.md`, and
pushes detail into `references/`. See [Agent Skills Distribution](docs/wiki/Agent-Skills-Distribution.md).

This repository ships no package, so its skills travel by the git route only.

## Code conventions

`src/` follows the same conventions every Purview repository follows. The ones that bite here specifically:
Expand Down
28 changes: 24 additions & 4 deletions docs/wiki/Agent-Enablement.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,24 @@ rule the humans have not written down, and a rule that only the wiki states is o
## The `.agents/` folder

```text
skills/ authored skills, tracked in git
└── <skill-name>/
├── SKILL.md the skill itself
└── references/ optional deeper material the skill links to

.agents/
├── .gitignore generated; lists the mirrored files
├── agents/ sub-agent definitions
├── prompts/ reusable prompts
└── skills/
└── skills/ mirrored from packages; gitignored
└── <skill-name>/
├── SKILL.md the skill itself
└── references/ optional deeper material the skill links to
```

Note the split. A skill you **author** lives at the repository root in `skills/`, where it is tracked
and where a cloning installer can find it. A skill you **receive** from a package arrives in
`.agents/skills/`, where it is gitignored. The same folder cannot do both jobs, and
[Agent Skills Distribution](Agent-Skills-Distribution.md) explains why.

A **skill** is a self-contained instruction set for one kind of task. It opens with YAML frontmatter whose
`description` is the trigger — it is what tells an agent that this skill applies:

Expand Down Expand Up @@ -132,10 +140,22 @@ appears in a status output.
- **A repository with several task shapes.** Add a prompt per shape, and consider a sub-agent definition for
the one that needs a different remit rather than more instructions.
- **A repository that others copy from.** Ship your skills in a package so the guidance travels with the
code, in the same way the SDK does — the mirror mechanism is the pattern, not the SDK.
code, in the same way the SDK does — the mirror mechanism is the pattern, not the SDK. See
[Agent Skills Distribution](Agent-Skills-Distribution.md).

## The gap the mirror does not close

`.agents/` is a convention this playbook and the SDK share. It is **not** a directory any agent reads
natively — Claude Code, Codex, Copilot and the rest each look somewhere else. Mirroring a skill into
`.agents/` therefore makes it present, not active.

Closing that gap is a separate, explicit step, delegated to a tool that tracks every agent's discovery
paths so the SDK does not have to. That is the subject of
[Agent Skills Distribution](Agent-Skills-Distribution.md).

## See also

- [Agent Skills Distribution](Agent-Skills-Distribution.md)
- [Documentation Convention](Documentation-Convention.md)
- [Repository Shape](Repository-Shape.md)
- [build-sdk: Agent Folder](https://github.com/purview-dev/build-sdk/blob/main/docs/wiki/Agent-Folder.md)
297 changes: 297 additions & 0 deletions docs/wiki/Agent-Skills-Distribution.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ at all, so the two are never blurred.
| [Contracts and Data Exchange](Contracts-And-Data-Exchange.md) | How value objects, schemas and results cross a boundary. |
| [Testing and Test Data](Testing-And-Test-Data.md) | How tests are written, and where their data comes from. |
| [Agent Enablement](Agent-Enablement.md) | How a repository is made legible to a coding agent. |
| [Agent Skills Distribution](Agent-Skills-Distribution.md) | How a skill is published once and reaches both a package's consumers and an agent's own directories. |
| [Documentation Convention](Documentation-Convention.md) | How the wiki is written, structured and published. |
| [Worked Examples](Worked-Examples.md) | The reference implementation in this repository, walked end to end. |
| [Adopting This at Your Scale](Adopting-This-At-Your-Scale.md) | Which parts apply to a small, medium or large repository. |
Expand Down
1 change: 1 addition & 0 deletions docs/wiki/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Contracts and Data Exchange](Contracts-And-Data-Exchange.md)
- [Testing and Test Data](Testing-And-Test-Data.md)
- [Agent Enablement](Agent-Enablement.md)
- [Agent Skills Distribution](Agent-Skills-Distribution.md)
- [Documentation Convention](Documentation-Convention.md)
- [Worked Examples](Worked-Examples.md)
- [Adopting This at Your Scale](Adopting-This-At-Your-Scale.md)
Expand Down
102 changes: 102 additions & 0 deletions skills/purview-architecture/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
name: purview-architecture
description: >-
Use when deciding the shape of a .NET solution that uses Purview libraries — how many projects,
whether to add a repository or CQRS layer, where validation lives — or before generating substantial
implementation code. Establishes project context first, then applies DRY, KISS and architecture
proportional to the actual requirements rather than to an enterprise reference diagram.
license: MIT
metadata:
author: Purview-Dev
version: "1.0"
---

# Purview architecture

The job of this skill is to stop two failures that cost far more than they look:

- **Architecture bought in advance.** Four projects, a repository interface and a mapping layer for an
application that has one table and one caller.
- **A recommendation made without context.** Advice about database-side GUID generation given to a
stateless CLI, or a Clean Architecture split proposed for a 300-line utility.

So: establish context, then choose proportionally. Both steps are mandatory, and the first one is the
one that gets skipped.

## 1. Establish context before deciding

Before recommending a structure, adding a project, or generating substantial code, you must know four
things. **Read them out of the repository first** — asking a user a question the code already answers
is the most common way this skill is applied badly.

| Dimension | Read it from | Ask only if |
| -- | -- | -- |
| Complexity (small / medium / large) | Number of functional areas, integrations and external consumers — not file count | The requirements are genuinely not written down anywhere |
| Project type | SDK attribute, `OutputType`, `Microsoft.NET.Sdk.Web`, an AppHost project, a `Main` | The repository is empty |
| Persistence | `PackageReference` to an EF Core provider, `DbContext`, a connection string, a `Purview.Containers` resource | Persistence is being introduced now |
| Telemetry | `Purview.Telemetry.SourceGenerator`, `ILogger` injection, `ActivitySource`, an OTLP exporter | Nothing is configured and you are about to add it |

Identifier generation is a **conditional** dimension: resolve it only when persistence exists *and*
GUID keys are in play. See `purview-persistence`.

Judge complexity on requirements, not size. A 2,000-line CLI with one output format is small. A
900-line service that reconciles three upstream systems and owns money movement is not.

**Do not re-ask.** A dimension answered by repository configuration, existing source, a project file,
an earlier turn in the conversation, or a recorded architectural decision is answered. Ask concise,
targeted questions only for the unknowns that change your recommendation — and if nothing changes,
ask nothing.

Full decision procedure: `references/project-context-discovery.md`.

## 2. Choose proportionally

| Scale | Shape | What you still do not add |
| -- | -- | -- |
| Small | One project, or two when a test project needs a non-trivial subject. Direct implementations. | Repository over `DbContext`, CQRS, a mapping layer, speculative interfaces |
| Medium | Explicit boundaries where complexity already hurts — usually a domain project and a host | Event sourcing, a generic infrastructure framework, a second infrastructure implementation nobody asked for |
| Large | Modularity, independent infrastructure implementations, enforceable boundaries (see the playbook's Architecture-As-Tests page) | Patterns adopted for symmetry rather than for a named problem |

Architecture is allowed to arrive late. A boundary added when the second caller appears is cheap; a
boundary added in anticipation of a second caller that never appears is permanent.

**Never add a project for symmetry.** "Domain, Application, Infrastructure, API" is a response to a
problem, not a starting position. If you cannot name the failure a layer prevents in this codebase,
do not add the layer.

Full guidance and the per-pattern justification tests: `references/proportional-architecture.md`.

## 3. DRY, applied where it pays

Reuse the Purview libraries when they remove a real source of truth, not for coverage:

| Concern | Library | Use it when |
| -- | -- | -- |
| Operation outcomes without exceptions for control flow | `Purview.Results` | Failures are expected and the caller must handle them |
| Primitive obsession, validated identity types | `Purview.ValueObjects` | A value has rules that would otherwise be re-checked at each boundary |
| Schema shared with a TypeScript client | `Purview.ZodSharp` | A contract is duplicated between C# and TypeScript today |
| Local dependencies for dev and integration tests | `Purview.Containers` | Tests need a real database, queue or emulator |
| Logging, tracing and metrics from one declaration | `Purview.Telemetry.SourceGenerator` | Always — see `purview-telemetry` |

Duplication that matters: validation repeated per entry point, a domain rule restated in the UI,
configuration bound in two places, two types that both claim to be the truth. Duplication that does
not: two similar-looking methods that answer to different owners.

## 4. Telemetry is not an advanced topic

Recommend instrumentation from the first commit, at every project size. A small application gets
small telemetry — it does not get none. See `purview-telemetry`.

## Checks before you answer

- Did you read the four context dimensions rather than ask for them?
- Can you name, for each project and each abstraction you are proposing, the failure it prevents here?
- Is anything in your proposal there only because a reference architecture has it?
- Did you include telemetry?

## See also

- `purview-telemetry` — instrumentation at every scale
- `purview-persistence` — storage and GUID-key guidance, only when persistence is real
- [Engineering Principles](https://github.com/purview-dev/engineering-playbook/blob/main/docs/wiki/Engineering-Principles.md)
- [Adopting This At Your Scale](https://github.com/purview-dev/engineering-playbook/blob/main/docs/wiki/Adopting-This-At-Your-Scale.md)
115 changes: 115 additions & 0 deletions skills/purview-architecture/references/project-context-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Project context discovery

The procedure for establishing enough context to make an architectural recommendation, and — just as
importantly — for knowing when to stop asking.

## The principle

Every question you ask a user is a question they have usually already answered in the repository.
Reading is free and silent; asking is slow and, when the answer was already in a project file, it
reads as not having looked. So the order is fixed:

1. Read the repository.
2. Read the conversation so far.
3. Ask only about what remains *and* changes your answer.

A dimension whose answer would not change your recommendation is not worth a question. If a project
is a stateless CLI, the database GUID strategy is not an unknown — it is irrelevant, and raising it
is noise.

## Dimension 1 — complexity

Assess from requirements, never from line or project count.

| Signal | Suggests |
| -- | -- |
| One functional area, one consumer, no integrations | Small |
| A handful of features, one or two integrations, a single team | Medium |
| Several bounded areas, multiple services or teams, operational obligations (SLOs, auditability, money) | Large |

Where to look: `README.md`, the wiki, issue or ticket references, the solution file's project list,
`PackageReference` entries naming external systems, the number of distinct hosts.

Traps:

- **A big repository is not a complex one.** A monorepo of ten tiny tools is ten small projects.
- **A small repository can be complex.** Few files, hard invariants.
- **Generated code inflates everything.** Discount it.

## Dimension 2 — project type

Read it; do not ask it.

| Evidence | Type |
| -- | -- |
| `Microsoft.NET.Sdk.Web` | Web application or HTTP API — distinguish by whether views/pages or endpoints dominate |
| `OutputType` of `Exe` plus an argument parser | CLI |
| `IsPackable`, no entry point | Reusable library |
| `IHostedService` / `BackgroundService`, `Worker` SDK | Background worker |
| An AppHost project, `Aspire.Hosting` | Distributed application |
| WPF/WinForms/Avalonia/MAUI SDK or `UseWPF` | Desktop application |

A reusable library carries the strongest constraint on the rest of your advice: it must not configure
global telemetry providers, must not read configuration from the ambient environment, and must not
decide a persistence technology for its consumer.

## Dimension 3 — persistence

| Question | Read it from |
| -- | -- |
| Is persistence required at all? | Any data-access `PackageReference`, a `DbContext`, a connection string, a repository type, an Aspire resource |
| Which technology? | The EF Core provider package (`Microsoft.EntityFrameworkCore.SqlServer`, `Npgsql.EntityFrameworkCore.PostgreSQL`), the client package, or the `Purview.Containers` resource in the AppHost |
| Is more than one implementation required? | Two providers referenced, a provider abstraction already present, or an explicitly stated requirement |
| EF Core or something else? | Presence of `Microsoft.EntityFrameworkCore` versus Dapper, a raw client, or a document store SDK |

"No persistence" is a complete and common answer. When it is the answer, every storage-shaped
recommendation — repositories, migrations, GUID strategies, connection resilience — is out of scope.

## Dimension 4 — telemetry

| Question | Read it from |
| -- | -- |
| Is telemetry configured? | `ILogger` injection, `ActivitySource`/`Meter` construction, `AddOpenTelemetry`, a `ServiceDefaults` project |
| Is Purview Telemetry available? | A `Purview.Telemetry.SourceGenerator` `PackageReference`, or interfaces attributed `[Logger]`, `[ActivitySource]`, `[Meter]` |
| Which signals are relevant? | Logs always; traces once a call crosses a process or a dependency; metrics once someone would alert on a rate or a duration |
| Is there an exporter? | An OTLP endpoint in configuration, a console exporter, an Aspire dashboard, an APM package |

Existing instrumentation is a constraint, not a starting point to replace. Preserve it, extend it,
and never register the same source twice.

## Conditional dimension — identifier generation

Resolve **only** when persistence exists and GUIDs are, or are about to be, primary keys.

- Are GUIDs primary keys, or just surface identifiers?
- Is the value generated in the application or by the database?
- Which provider and version? (This changes the answer — see `purview-persistence`.)
- Does index locality matter at the expected write volume?
- Must the identifier exist before the entity is persisted (outbox, correlation, client-side graph)?

The last question usually decides it: if the application needs the value before `SaveChanges`, the
database cannot be the generator.

## Asking well

When you must ask, ask few and ask concretely. Batch the unknowns into one short set, offer the
likely answers, and state what you will do if the user does not care.

Good:

> Two things I can't tell from the repository: is this service expected to own its own database, or
> read from the existing one? And should failures be returned to the caller or thrown? I'll assume
> its own database and `Purview.Results` for expected failures unless you say otherwise.

Bad:

> What is the project complexity? What is the project type? Is persistence required? Which database?
> Is telemetry configured? Do you use GUID primary keys?

The second asks six questions, five of which the repository answers, and one of which is irrelevant.

## Recording the answer

When a dimension is resolved by conversation rather than by the repository, that is a decision the
next agent will not be able to read. Write it down — in `AGENTS.md`, a wiki page, or an ADR — so the
question is asked once and never again.
Loading
Loading