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
5 changes: 4 additions & 1 deletion .config/purview-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@
},
"Release": {
"Mode": "None"
},
"PackageJson": {
"Enabled": true
}
}
}
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ This file is the primary instruction set for human and AI agents working in this
- `docs/wiki/Release-Flow.md` — versioning, branch models, and release strategy.
- `docs/wiki/Release-Models.md` — the three release trigger models, as raw Mermaid source.
- `docs/wiki/Local-Development.md` — `just` recipes, `release-explain`, simulation, and the local rehearsal.
- `docs/wiki/Pack-Validation.md` — the `.nupkg`/`.snupkg` content rules.
- `docs/wiki/Package-Json-Validation.md` — the root `package.json` profiles, the `PKG0nn` rules, and `package-validate`.
- `docs/wiki/Net-11.md` — supported runtimes (and the .NET 8/9 removal) and the .NET 11 feature evaluation.
- `docs/wiki/Migration-*.md` — per-consumer migration guides.
- `README.md` — user-facing overview and minimal consumer setup.
Expand All @@ -42,6 +44,7 @@ The developer entry points are the `just` recipes; the .NET SDK and `just` are r
- `just test` / `just test-unit` — run tests under `src/tests`. CI and `just test` both run **everything**: `Build:TestFilter` in `purview-build.json` is `/*/*/*/*/`. `just test-unit` narrows to the unit suites for a faster local loop.
- `just test-eligibility` — the eligibility and config-resolution suites only.
- `just release-matrix` / `just config-matrix` — run every scenario through the real CLI. **Run both before any change to release or configuration behaviour**; they need no secrets and no network.
- `just package-matrix` — run every `package.json` validation scenario through the real CLI. Run it before any change to the `PKG0nn` rules; it needs no secrets and no network.
- `just release-explain` / `just release-simulate <ref> <version>` — explain the release decision, mutating nothing.
- `just lint-yaml` — `actionlint` over the workflow files (skipped with a note when not installed).
- `just lint-check` / `just lint-fix` — CSharpier (via local tool manifest `.config/dotnet-tools.json`).
Expand All @@ -64,7 +67,7 @@ CI builds with `--warnaserror`.

- **Versioning**: the single source of truth is the `version` field in `package.json`. `VersionModule` resolves it once into an ordered set of release units; every downstream module reads that result and **never recomputes a version**. `Version:Strictness` defaults to `NuGet`, which accepts four-part versions — two consuming repositories ship them, so do not tighten this default.
- **Eligibility ownership**: the tool *evaluates* release eligibility and reports it; the workflow *decides* and sets `Release__Mode`. The tool must never silently decline to release when asked to. `ReportEligibilityModule` is category `Build` and every failure path in it is a warning.
- **Rule IDs are permanent**: never renumber or repurpose a `REL0nn`.
- **Rule IDs are permanent**: never renumber or repurpose a `REL0nn` or a `PKG0nn`.
- **This repository builds itself and never references its own released package.** The `Justfile` runs `.tools/local/Purview.Build`, published from the working tree; `ci.yml` and `release.yml` pack from source and install with `--source artifacts` (not `--add-source`, which leaves nuget.org able to satisfy an already-shipped version); `Purview.Build` is absent from `.config/dotnet-tools.json`. `SelfHostingTests` enforces all three. The reusable workflows and the composite action are the opposite case — they install the published tool for consuming repositories and must keep doing so.
- **GitHub Packages is additive and opt-in.** Consuming from `https://nuget.pkg.github.com/<owner>/index.json` is automatic for every repository using the shared action/workflows; publishing to it is controlled only by the caller repository's `PURVIEW_PUBLISH_GITHUB_PACKAGES` variable, which the release entry points map onto `NuGet:GitHubPackages:Enabled`. Never make publication automatic, never make it a replacement for NuGet.org, and never put a PAT in the shared path for publishing: publication uses `GITHUB_TOKEN` with `packages: write`. Consumption falls back to `GITHUB_TOKEN` and may use the optional, read-only organisation secret `PURVIEW_PACKAGES_READ_TOKEN` (`read:packages` only) for repositories that should read every package without per-package Actions-access grants. NuGet.org publication and `Release:Mode` semantics must not change.
- **One source-setup implementation.** `actions/nuget-sources/` is the only place that registers the organisation feed. `purview-build.yml`, `purview-release.yml`, and both composite actions call it; `ci.yml` and `release.yml` call it from `./` to dogfood it. It writes the runner's user-level NuGet configuration (never a committed `nuget.config`) and stores only a macro — `%GITHUB_TOKEN%`, or `%PURVIEW_NUGET_TOKEN%` when the optional organisation read token is used.
Expand Down Expand Up @@ -123,6 +126,7 @@ npx skills add purview-dev/build
| `skills/purview-build-configuration/` | where `purview-build.json` is found, precedence, every setting, the secrets |
| `skills/purview-build-pipeline/` | what each module does, its dependencies, and why it was skipped |
| `skills/purview-build-pack-validation/` | the package rules, the target-framework token, and `RequireExplicitContent` |
| `skills/purview-build-package-json/` | the root `package.json` profiles, the `PKG0nn` rules, and `package-validate` |
| `skills/purview-build-release-eligibility/` | the `REL0nn` rules, the built-in policies, and the three release models |

Every skill carries the `purview-build` prefix. Skills install into one flat directory per agent,
Expand Down
7 changes: 7 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ publish-aot:
-p:Version={{ current_version }} -p:PackageVersion={{ current_version }}
"{{ aot_dir }}/Purview.Build" --version
"{{ aot_dir }}/Purview.Build" release-explain --format=json | jq -e '.verdict'
"{{ aot_dir }}/Purview.Build" package-validate --format=json | jq -e '.succeeded'

# Explain the release decision for the working tree, without running any module or mutating anything
[group('Release')]
Expand All @@ -147,6 +148,12 @@ config-matrix:
just build-local-tool
sh src/tests/scenarios/run-config-matrix.sh

# Run every package.json validation scenario through the real CLI; non-zero on any mismatch
[group('Build and Test')]
package-matrix:
just build-local-tool
sh src/tests/scenarios/run-package-matrix.sh

# Regenerate the release-explain JSON golden file, then show what changed
[group('Release')]
release-explain-golden:
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ Secrets must not be committed. They are supplied through `NUGET_APIKEY` (or `NuG

Release eligibility is rule-based and selected per repository with `Release:Eligibility:Policy`: `ReleaseOnMain` (the default, reproducing the pipeline's long-standing behaviour), `TrunkReservesMinor` (per-line release branches), or `FourPartServicing`. Run `purview-build release-explain` to see the decision, the rule trail and the publication that would result, without running anything. See [release models](docs/wiki/Release-Models.md).

The root `package.json` can be checked against the content a best-in-class package of the repository's kind is expected to declare: run `purview-build package-validate` (exit `0`/`1`, `--format=json` for a workflow), and set `PackageJson:Enabled=true` to add it to the pipeline. A `.NET` repository that packs, one that does not, a published npm package and a web application are judged differently — descriptions, versions, licence, repository, author, engines, scripts and the `private` flag — and every rule is reported, in ID order. See [package.json validation](docs/wiki/Package-Json-Validation.md).

See the [Documentation](#documentation) section below for the architecture, configuration reference, and release strategy.

## GitHub Packages
Expand Down Expand Up @@ -403,12 +405,12 @@ Other repositories can opt in later with only steps 1–2; nothing in Purview.Bu
## Pipeline

```text
CleanArtifacts → { Version, Restore → Build → Test, Restore → Lint } → Pack → Validate → Publish ─┬→ NuGet.org
└→ ReportEligibility └→ GitHub Packages (opt-in)
→ GitHub release
CleanArtifacts → { Version → ValidatePackageJson, Restore → Build → Test, Restore → Lint } → Pack → Validate → Publish ─┬→ NuGet.org
└→ ReportEligibility └→ GitHub Packages (opt-in)
→ GitHub release
```

`Lint` and `ReportEligibility` are gates and reports, not inputs: nothing depends on either.
`Lint`, `ReportEligibility` and `ValidatePackageJson` are gates and reports, not inputs: nothing depends on any of them.

`CleanArtifacts` deletes and recreates `Build:ArtifactsFolder` before anything else runs, so pack, validation, publishing, and release uploads only ever see the packages from the current run (set `Build:CleanArtifacts=false` to keep existing artifacts). `Version` resolves the release units from `Version:Source` (default: the `version` field of the root `package.json`); `Version:Strictness` defaults to `NuGet`, which accepts four-part versions. `ReportEligibility` evaluates the release rules and reports the verdict without acting on it. Lint restores local tools and runs CSharpier (`Build:CSharpierPath` runs a vendored CSharpier assembly instead, for an unreleased build). Tests are discovered under `Build:TestRoot`/`Build:TestPatterns` and run with a TUnit tree-node filter (or an xUnit filter). Pack validation inspects each `.nupkg`/`.snupkg` against required/forbidden content rules (glob patterns) and can enforce source link, deterministic builds, and compiler flags on the packaged assemblies. Analyzer-only packages can embed portable PDBs under `analyzers/dotnet/` without requiring a `.snupkg`. Publication and GitHub release steps are controlled by `Release:Mode` (`None`, `LocalNuGet`, `NuGet`, `GitHubRelease`) — a preset over the independent `Release:Publish` and `Release:GitHubRelease` switches — and by the `Build__Run*` switches. `Release:DryRun` runs everything and skips both publish steps. `LocalNuGet` is only honoured when the tool runs locally; it is ignored in CI (for example via a reusable workflow).

Expand Down
7 changes: 5 additions & 2 deletions docs/wiki/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ CleanArtifactsModule → RestoreModule → BuildModule → RunTestsModule ──
CleanArtifactsModule → RestoreModule → LintModule │ └→ PublishGitHubPackagesModule ─┤
CleanArtifactsModule → VersionModule ─────────────────────────────────┤ └→ CreateGitHubReleaseModule
VersionModule → ReportEligibilityModule │
VersionModule → ValidatePackageJsonModule │
│
(LintModule and ReportEligibilityModule
are not inputs to PackModule)
(LintModule, ReportEligibilityModule and
ValidatePackageJsonModule are not inputs
to PackModule)
```

Explicit `[DependsOn]` edges:
Expand All @@ -66,6 +68,7 @@ Explicit `[DependsOn]` edges:
- `LintModule` depends on `RestoreModule` (Web lint needs the dependencies installed by `bun install`; dotnet lint is unaffected beyond running after restore). **Nothing depends on `LintModule`** — it is a gate, not an input.
- `RunTestsModule` depends on `BuildModule`.
- `ReportEligibilityModule` depends on `VersionModule`. Nothing depends on it; it only reports.
- `ValidatePackageJsonModule` depends on `VersionModule` and is skipped unless `PackageJson:Enabled` is true. Nothing depends on it either: like `LintModule` it is a gate, so enabling it can never reorder pack or publish. The same check is reachable as the standalone `package-validate` command, which builds no pipeline at all.
- `PackModule` depends on `RunTestsModule` and `VersionModule`.
- `ValidatePackModule` depends on `PackModule`.
- `PublishNuGetModule` depends on `PackModule`, `ValidatePackModule`, and `RunTestsModule`.
Expand Down
14 changes: 14 additions & 0 deletions docs/wiki/Configuration-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ command line
| `-v`, `--version` | Print the tool version and exit without running the pipeline. |
| `-h`, `--help`, `-?` | Print usage, options, the probe order, and the resolved configuration path (or "none found"), then exit. |
| `release-explain` | Print the release decision and exit without running any module. `--format=json` emits the stable contract the release action consumes. See [Local Development](Local-Development.md). |
| `package-validate` | Validate the root `package.json` against the rules for the repository's kind and exit without running any module: `0` when it passes, `1` when it does not. `--format=json` emits the report. See [package.json Validation](Package-Json-Validation.md). |

Failures are reported the way a CLI build tool reports them: the tool prints the failing module and that module's
output, then exits with code 1. Set `PURVIEW_BUILD_STACKTRACE=1` to add stack traces when diagnosing the tool itself.
Expand Down Expand Up @@ -180,6 +181,19 @@ Content entry paths and package-id keys are matched as globs (case-insensitive),
>
> `RequireExplicitContent` is the opposite case and was previously documented incorrectly as `false`. It was **absent** from `appsettings.json`, so the C# initialiser's `true` survived and the effective default has always been `true`. It is now stated explicitly in `appsettings.json` so the shipped value is visible rather than inferred. Behaviour is unchanged. Note what `true` means with an empty `RequiredContent`: every produced package must declare its content, so a repository that packs anything without a matching `RequiredContent` rule gets an error. Set `"RequireExplicitContent": false` to opt out, as this repository's own `purview-build.json` does.

## `PackageJson`

Opt-in validation of the repository root `package.json`. See
[package.json Validation](Package-Json-Validation.md) for the rules and the profiles.

| Key | Default | Purpose |
| --- | --- | --- |
| `Enabled` | `false` | Adds `ValidatePackageJsonModule` to the pipeline. The standalone `package-validate` command always works, whether or not this is set |
| `Profile` | `Auto` | Derives the profile from `Build:ProjectType`, `Build:RunPack` and the file's own `private` flag. `DotNetNuGet`, `DotNetSolution`, `WebPackage` and `WebApp` declare it instead, which is the only way to catch a `private` flag that contradicts the repository |
| `DisabledRules` | `[]` | `PKG0nn` rule IDs to skip, e.g. `["PKG008"]`. Unknown IDs are ignored |
| `AdditionalRequiredFields` | `[]` | Extra top-level fields the repository requires, e.g. `["funding"]`; a missing or empty field fails `PKG014` |
| `TreatWarningsAsErrors` | `false` | Makes a warning-level finding fail the run, once the file is clean |

## `NuGet`

| Key | Default | Purpose |
Expand Down
1 change: 1 addition & 0 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The same implementation is available three ways:
- [Configuration Reference](Configuration-Reference.md)
- [Pipeline Modules](Pipeline-Modules.md)
- [Pack Validation](Pack-Validation.md)
- [package.json Validation](Package-Json-Validation.md)
- [Release Flow](Release-Flow.md)
- [Release Models](Release-Models.md)
- [Local Development](Local-Development.md)
Expand Down
2 changes: 2 additions & 0 deletions docs/wiki/Local-Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ the freshly built `.tools/local/Purview.Build`.
| `just release-simulate ref version *args` | Explain the decision for a simulated ref and version |
| `just release-matrix` | Run every eligibility scenario through the real CLI; non-zero on any mismatch |
| `just config-matrix` | Run every config-resolution scenario through the real CLI |
| `just package-matrix` | Run every `package.json` validation scenario through the real CLI |
| `just release-explain-golden` | Regenerate the `release-explain` JSON golden file and show the diff |

`just release-matrix` and `just config-matrix` are the two commands to run before opening a pull
Expand All @@ -79,6 +80,7 @@ fixture files drive the TUnit suites:

- `src/tests/fixtures/eligibility-scenarios.json`
- `src/tests/fixtures/config-resolution-scenarios.json`
- `src/tests/fixtures/package-json-scenarios.json`

Adding a rule or a probe location means appending cases to those files, not writing test code.

Expand Down
Loading
Loading