Local work uses the just recipes in the Justfile (which delegate to plain dotnet/bun commands) or the shared pipeline tool directly.
Purview.Build is an ordinary .NET local tool in a consuming repository, pinned in
.config/dotnet-tools.json and installed by dotnet tool restore. This repository is the
exception: it never resolves Purview.Build from a feed, because a released version standing in for
the local one would run the pipeline without exercising the change being made — and on the release
path, the previous version would publish the new one.
Every just pipeline-* recipe therefore builds the tool from the working tree first:
just build-local-tool # dotnet publish src/src/Build/Build.csproj -c Release -o .tools/localThe output goes to .tools/local/ (gitignored) rather than bin/Release/, because a pipeline run
rebuilds the solution underneath itself and would otherwise overwrite the binary it is executing.
.config/dotnet-tools.json pins only CSharpier and dotnet-inspect.
just pipeline-dogfood is the separate, slower check that the packaged tool is installable: it
packs from source into artifacts/dogfood, installs from that folder alone (--source, not
--add-source), and runs the result. CI and the release workflow do the same.
| Recipe | Purpose |
|---|---|
just build |
Build src/Build.slnx (Debug) |
just build-local-tool |
Publish the tool from source (Release) to .tools/local, which every pipeline-* recipe then runs |
just test / just test-unit |
just test runs every suite, the same set CI runs; just test-unit narrows to /*/*/*/*[Category=Unit] |
just lint-check / just lint-fix |
CSharpier check / format |
just pack |
dotnet pack with the current package.json version |
just pipeline-pr |
Run the shared tool (restore, build, lint, tests) |
just pipeline-build |
Run the shared tool without tests |
just pipeline-release |
Run the shared tool with Release:Mode=NuGet |
just pipeline-tests |
Run the shared tool with tests enabled |
just pipeline-local-release |
Lint-fix, then run the shared tool with Release:Mode=LocalNuGet |
just pipeline-dogfood |
Pack the tool from source, install it to a temp tool path, and run it against this repository |
just test-eligibility |
The eligibility and config-resolution suites only |
just lint-yaml |
actionlint over the workflow files (skipped with a note when not installed) |
just clean-all / just scrub |
Clean build outputs |
just pipeline-* runs dotnet tool restore (for CSharpier) and just build-local-tool, then runs
the freshly built .tools/local/Purview.Build.
| Recipe | Purpose |
|---|---|
just release-explain *args |
Explain the release decision for the working tree |
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 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
request that touches release behaviour. Both need no secrets and no network: every case supplies
its own ref and tag list through Release:Context:*, which suppresses all process and network
lookups. Both build the tool first (a restore is needed on a genuinely fresh clone) and then run the
real binary, so the shipped CLI and the in-process evaluator are held to one specification — the same
fixture files drive the TUnit suites:
src/tests/fixtures/eligibility-scenarios.jsonsrc/tests/fixtures/config-resolution-scenarios.json
Adding a rule or a probe location means appending cases to those files, not writing test code.
Each run creates throwaway repositories under .scenario-runs/ (gitignored) and leaves the working
tree untouched.
Prints the release decision and exits 0 without running any module and without mutating anything. The decision's own exit code is reported inside the output, for a caller to act on.
just release-explain
just release-explain --config .config/purview-build.json
dotnet purview-build release-explain --format=jsonIt reports the resolved configuration path, the probe trail, any shadowed files, whether user
configuration was active, the resolved release units, the selected policy and its fully resolved rule
list, every rule in evaluation order with its verdict and message, the short-circuit point, the
Release__Mode / Release:Publish / Release:GitHubRelease / feed URL that would result, and the
ref it evaluated against with where that ref came from.
--format=json is the contract the reusable release workflow consumes. It is pinned by a golden file
(src/tests/fixtures/release-explain.golden.json), because consuming repositories pin the workflow
by ref: a change in shape or in a rule message is a change to a published contract.
just release-simulate refs/heads/main 1.1.0
just release-simulate refs/heads/release/2.0 2.0.2 --Release:Eligibility:Policy=TrunkReservesMinor
just release-simulate refs/heads/release/2.1 2.1.0-prerelease.1 --Release:Eligibility:Policy=TrunkReservesMinorThe output is clearly marked SIMULATED, and the real repository's branch and tags do not leak in —
a simulated verdict must never be mistakable for a real one.
A local run cannot perform a real publish or create a tag:
release-explainnever mutates anything.- If any
Release:Context:*key is set and the run would really publish, the pipeline fails before any module runs, naming the conflict. Simulated context must never drive a real publish. Release:DryRun=trueruns the full pipeline but skips publishing and the GitHub release, logging what each would have done.Release:Mode=LocalNuGetis honoured only when running locally and is ignored in CI.
Release:Mode=LocalNuGet is the natural rehearsal path. Install the tool to a separate tool path
first — running the Release-configuration build against the binary that is executing it will fail on
a locked file:
dotnet pack src/Build.slnx -c Release -o ./artifacts/dogfood \
-p:Version=$(bun -p "require('./package.json').version") \
-p:PackageVersion=$(bun -p "require('./package.json').version")
dotnet tool install Purview.Build --tool-path ./.tools/dogfood \
--add-source ./artifacts/dogfood --version $(bun -p "require('./package.json').version")
LOCAL_NUGET_FEED_PATH=/tmp/purview-local-feed ./.tools/dogfood/purview-build --Release:Mode=LocalNuGetjust pipeline-dogfood does the pack-and-install part for you.
The rehearsal exercises Clean → Version → ReportEligibility → Restore → Build → Test → Lint → Pack →
ValidatePack → local publish, and provably does not create a tag, create a GitHub release, or
touch a remote feed: PublishNuGetModule, PublishGitHubPackagesModule (GitHub Packages is opt-in and
off by default) and CreateGitHubReleaseModule all report Skipped, even when NUGET_APIKEY and
GITHUB_TOKEN are present in the environment.
For a dry run against the real publication path instead:
./.tools/dogfood/purview-build --Release:Mode=NuGet --Release:DryRun=trueBoth publish steps then log Would have … and perform no action. To rehearse the dual publication as
well, add --NuGet:GitHubPackages:Enabled=true; the extra module logs its intended push and still
performs none.
Release:Mode=LocalNuGet pushes packages to a local feed and is only honoured when the tool runs locally — it is ignored in CI.
The feed path is checked before the pipeline starts, along with the other startup requirements, so a missing or non-absolute path fails immediately rather than after restore, build, test, and pack.
LOCAL_NUGET_FEED_PATH=C:/local/nuget-feed/ dotnet purview-build --Release:Mode=LocalNuGetThe local feed path must be an absolute path. just runs recipes through the shell, which strips backslashes from unquoted arguments, so a backslash-based Windows path is mangled before the tool sees it and is rejected. Use the LOCAL_NUGET_FEED_PATH environment variable, or forward slashes:
just pipeline-local-release --PublishLocalNuGet:LocalFeedPath=C:/local/nuget-feed/By default the module overwrites existing packages, clears the NuGet global-packages and HTTP caches for the published packages, and shuts down the dotnet build server afterwards (all configurable under PublishLocalNuGet).
The tool locates the repository root by walking up from the current working directory to the nearest package.json. Run purview-build from within the repository. MODULAR_PIPELINES_DIRECTORY can override the directory containing the tool's shipped appsettings.json — it has nothing to do with locating purview-build.json, which has its own probe order.
just runs recipes through a shell that strips backslashes from unquoted arguments, so a
backslash-based Windows path is mangled before the tool sees it. Use forward slashes, or the
LOCAL_NUGET_FEED_PATH environment variable:
just pipeline-local-release --PublishLocalNuGet:LocalFeedPath=p:/_sync-projects/.local-nuget/The local feed path must be absolute. A drive-relative path such as p:foo — the classic signature
of stripped backslashes — is rejected with a remediation message rather than silently resolving
against the current directory.