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
188 changes: 188 additions & 0 deletions .agents/skills/purview-buildsdk-settings-docs-sync/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
name: purview-buildsdk-settings-docs-sync
description: Change or document a Purview.BuildSdk setting without leaving part of the surface behind. Use when flipping a .NET 11 compiler feature default (Runtime Async, memory-safety rules), adding or renaming a property, or making sure every skill and doc explains how to set required settings and versions, the target framework(s), and the compiler-visible (build_property.*) settings. Repository-local guidance for purview-dev/build-sdk.
license: MIT
metadata:
scope: repository-local
package: Purview.BuildSdk
repository: purview-dev/build-sdk
---

# Keeping settings, docs and skills in step

> **Repository-local.** This skill lives in `.agents/skills/`, which is gitignored, not packed and not
> published. It is deliberately **not** linked from `AGENTS.md`: `RepositorySkillsSelfTests` fails any
> AGENTS.md link that points into `.agents/skills/`, because a fresh clone does not have this folder.
> The published skills are authored under `skills/` and are a different thing entirely.

## The rule this exists for

A property is written down in several places, and only some of them are checked by a test. The failure
mode this repository keeps hitting is a change that lands in one place and stops there — the property
declaration, its validation, `README.md`, the wiki page and the shipped skills drift apart, and the
only evidence is a consumer following a doc that still describes last release's default.

So: **treat a settings change as a sweep, not an edit.** Work the surfaces below, then prove it with
the validation commands at the end.

## When to use this

- Flipping a .NET 11 compiler feature default (`MemorySafetyRules`, `PurviewRuntimeAsync`).
- Adding, renaming, re-defaulting or removing a user-facing property.
- Making required settings, versions, target frameworks or compiler-visible settings clearer in the
docs and the shipped skills.
- Auditing whether a property is documented everywhere before you change it.

For auditing a *consumer* repository, use `purview-buildsdk-repository-review`. For what a project
detection rule does, use `purview-buildsdk-project-detection`.

## The surfaces — one property, many homes

Find the row and make sure every file agrees.

| Surface | File | What changes |
| -- | -- | -- |
| Runtime default (all projects) | `src/src/BuildSdk/Sdk/Props/Defaults.props` | The default when unset, plus the explanatory comment. Sets the value in `Sdk.props`, which is also what makes it compiler-visible. |
| Per-TFM default | `src/src/BuildSdk/Sdk/Targets/Modern.targets` | The `net11.0`+ feature selection. Must agree with `Defaults.props`. |
| Compiler-visible description | `src/src/BuildSdk/Sdk/Sdk.props` | The `CompilerVisibleProperty` `<Description>`, which is what an analyzer author reads. |
| Pinning test | `src/tests/BuildSdk.IntegrationTests/Dotnet11FeaturesTests.cs` | The test that fails when the default changes silently. |
| Changelog | `CHANGELOG.md` | A `[Unreleased]` entry: `### Changed` for behaviour, `### Documentation` for docs-only. |
| README | `README.md` | The feature table row, the property table(s), the quick start, and the `.NET 11 features` section + tiers paragraph. |
| Wiki | `docs/wiki/Configuration-Reference.md`, `Getting-Started.md`, `Version-Detection.md`, `Project-Type-Detection.md` | The property tables, the required-settings and target-framework sections, and the compiler-visible section. |
| Shipped skills | `skills/purview-buildsdk-*/SKILL.md` and `references/*.md` | The property tables and the sections below. |
| AGENTS.md | `AGENTS.md` | The "authored set, and what each one answers" table, when a skill's scope grows. |

Two traps worth naming:

- **The default lives in two places.** `Defaults.props` defaults the property for every project (which
is what exposes it to the compiler); `Modern.targets` re-defaults it per-TFM. Change one and the
other silently disagrees.
- **`Sdk.props` vs `Sdk.targets` decides *where* a value may be set.** A property read during
`Sdk.props` evaluation must be set before the import; everything else can be set in the project. Say
which it is, in the docs, next to the property.

A **feature** bigger than a single property gets its own guide as well. SourceLink is the worked
example, because the decision is per repository host: add a wiki page `docs/wiki/<Feature>.md` and
register it in `_Sidebar.md` **and** `Home.md`, add a `## <Feature>` section to `README.md`, and — when
it is configuration — a `references/<feature>.md` under
`skills/purview-buildsdk-configuration/` that the `SKILL.md` links. Fix any wrong package or property
name you find while documenting: the SourceLink pages used to name `Microsoft.SourceLink.AzureDevOps.Git`,
which does not exist.

## Worked example — flip a .NET 11 compiler feature default

1. `Defaults.props` — change the default and the comment. Say **why** (an experimental compiler feature
is opt-in), not just what.
2. `Modern.targets` — change the same default in the per-TFM block and its header comment.
3. `Sdk.props` — the `CompilerVisibleProperty` `<Description>` ("0 off (default), …").
4. `Dotnet11FeaturesTests.cs` — make the default test assert the switch is **absent**, rename it to say
so, and add an explicit opt-in test. De-couple any other test that asserted the switch was on by
default (for example, the project-features-survive test).
5. `README.md` — the `.NET 11 opt-ins` row, the `.NET 11 features` table row and the tiers paragraph,
and the property table row.
6. `docs/wiki/Configuration-Reference.md` — the General row **and** the `.NET 11 features` table.
7. Skills — the `MemorySafetyRules`/`PurviewRuntimeAsync` rows in
`skills/purview-buildsdk-configuration/SKILL.md` and `references/properties.md`.
8. `CHANGELOG.md` — a `[Unreleased]` `### Changed` entry that names the old default, the new one, how to
opt in, and what is unchanged.

Keep `AllowUnsafeBlocks` (and anything else) at its compiler default: a feature flip must not quietly
change a second setting.

## Required settings and versions — where it must be said

Four values, stated as a table wherever a reader lands first:

| Setting | Home |
| -- | -- |
| `NamespacePrefix` (required, pre-import) | `Directory.Build.props`, above the `<Import>` |
| SDK version | `global.json` → `msbuild-sdks.Purview.BuildSdk` (`PurviewBuildSdkVersionForGlobalJson` decides what the bootstrap writes) |
| Product version | repo-root `package.json` → `version` (`UsePackageJsonVersion`, `RootPackageJson`) |
| Package versions | `Directory.Packages.props` (CPM; a `PackageVersion` is a minimum) |

Keep it identical in all four homes: `README.md` quick start, `docs/wiki/Configuration-Reference.md`,
`docs/wiki/Getting-Started.md`, and the `purview-buildsdk-adoption` and
`purview-buildsdk-configuration` skills.

## Target framework(s) — where it must be said

Every skill and doc that talks about configuring a project must explain how to set the target
framework(s), consistently:

- the default set is `net10.0;net11.0` (`Current`);
- an explicit `TargetFramework`/`TargetFrameworks` must be declared **unconditionally** (the SDK reads
it from the project XML, so a `Condition` is not seen — `PurviewConditionedProjectDeclaration`);
- a curated `PurviewTargetFrameworkSet` (`Current`, `Latest`, `Supported`, `Broad`, `All`) or a
redefined `PurviewTargetFrameworks<Set>` (pre-import, in `Directory.Build.props`);
- a Roslyn component keeps `netstandard2.0` and ignores the sets (`PurviewTargetFrameworkSetIgnored`).

The canonical home is `skills/purview-buildsdk-configuration/SKILL.md` and its
`references/properties.md`; the other skills point at it rather than restating the whole thing.

## Compiler-visible settings — where it must be said

The `CompilerVisibleProperty` → `build_property.*` mechanism, plus a one-line
`AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.X", …)` example, belongs in:

- `skills/purview-buildsdk-configuration/SKILL.md` and `references/properties.md`,
- `skills/purview-buildsdk-project-detection/SKILL.md` (the analyzers section),
- `README.md`, `docs/wiki/Configuration-Reference.md` and `docs/wiki/Project-Type-Detection.md`.

Say both halves: **consume** the exported property instead of re-deriving SDK behaviour, and **add a
new one** by listing it as a `CompilerVisibleProperty` item (with a `<Description>`) in `Sdk.props` —
nothing else exposes an MSBuild property to the compiler.

## Skills sweep

Each published skill that touches project configuration gets the change; a skill that is only about
skills tooling does not.

| Skill | What to update |
| -- | -- |
| `purview-buildsdk-configuration` | required settings, target framework(s), compiler-visible properties, the property table, `references/properties.md` |
| `purview-buildsdk-adoption` | the required-settings table and `global.json` example, the target-framework step |
| `purview-buildsdk-project-layout` | the target-framework section |
| `purview-buildsdk-project-detection` | the target-framework subsection, the `build_property.*` note |
| `purview-buildsdk-repository-review` | the read list (`global.json`, `package.json`), the target-framework finding |
| `purview-buildsdk-testing` | the test-project target-framework paragraph |
| `purview-buildsdk-skill-tests`, `-skill-installation`, `-skills-justfile` | **not** project configuration — leave them alone |

Also update each skill's frontmatter `description` so the change makes the skill **fire** on the right
question ("setting the target framework(s)", "pinning the SDK version in global.json").

## Validate

```bash
just build
just skills-validate # PRSKL0005, the pack-time check
just version-sync-check # the version pins must still match package.json
just lint-check # CSharpier over the repo

# Targeted and fast (MTP: --treenode-filter, never --filter):
dotnet test --project src/BuildSdk.slnx --configuration Debug \
--treenode-filter "/*/*/Dotnet11FeaturesTests/*" --ignore-exit-code 8
dotnet test --project src/BuildSdk.slnx --configuration Debug \
--treenode-filter "/*/*/(TargetFrameworkSetTests)|(CompilerVisiblePropertyTests)|(CoreDefaultsTests)/*" --ignore-exit-code 8
dotnet test --project src/BuildSdk.slnx --configuration Debug \
--treenode-filter "/*/*/(RepositorySkillTests)|(RepositorySkillsSelfTests)|(PurviewSkillValidationTests)/*" --ignore-exit-code 8
```

State the commands you ran before declaring the change done.

## Guardrails

- **Do not publish repository-local advice.** Anything under `skills/` ships to every consumer through
`npx skills add purview-dev/build-sdk`; guidance about working *in this repository* stays here, in
`.agents/skills/`, or in `AGENTS.md`.
- **Do not add a link into `.agents/skills/` from `AGENTS.md`** — it is gitignored, so the link is
broken in a fresh clone and `RepositorySkillsSelfTests` fails the build.
- **Do not give a local skill an authored skill's name.**
`AuthoredSkillsAreNotInstalledIntoTheAgentDirectories` fails when a folder here collides with one
under `skills/`.
- **Preserve a default unless the task explicitly changes product behaviour.** A docs task is not
licence to re-default anything.
- **Do not restate a default in a consumer project file** — recommend deleting the repetition instead.
- **Never force-add `.agents/skills/`.** Everything the CLI writes is reproducible from
`skills-lock.json`; `InstalledSkillsAreNotTracked` fails if any of it is committed.
- Keep changes small, use Conventional Commits, and do not mix an unrelated refactor into the sweep.

14 changes: 10 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -670,16 +670,22 @@ sketch

# Agent skills installed by the skills CLI.
#
# The authored copies live at skills/<name>/ and are the only tracked ones. Everything below is
# written by `npx skills add` from that folder and is reproducible from the committed
# skills-lock.json, so none of it belongs in the repository:
# The published skills live at skills/<name>/ and are the only ones the CLI installs. Everything the
# CLI writes below is reproducible from the committed skills-lock.json with
# `npx skills experimental_install`, so none of it belongs in the repository:
#
# .agents/skills/ the CLI's "universal" location, shared by most agents
# .claude/skills/ per-agent links into the universal location
# agent/skills/ ditto
#
# Note that .agents/agents/ and .agents/prompts/ are authored content and stay tracked, so the
# skills folder is ignored specifically rather than the whole of .agents/.
/.agents/skills/
#
# One exception. `.agents/skills/` is also the one location an agent reads natively here, so a
# repository-local skill - guidance about working in *this* repository, which must never ship to a
# consumer - is authored in place and tracked. It is un-ignored by name below, so everything the CLI
# writes stays out of git.
/.agents/skills/*
!/.agents/skills/purview-buildsdk-settings-docs-sync/
/.claude/skills/
/agent/skills/
23 changes: 13 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Use `.agents/` as the default location for **generic agentic information** share

- `.agents/agents/` — canonical, cross-agent agent workflow specs (authored, tracked)
- `.agents/prompts/` — canonical, cross-agent prompt specs (authored, tracked)
- `.agents/skills/` — **not authored.** Skills installed from *other* sources, written by the skills CLI and gitignored; see below.
- `.agents/skills/` — skills installed from *other* sources by the skills CLI (gitignored), plus any **repository-local** skill authored in place and tracked; see below.

Use `.github/` customization files only as VS Code/Copilot wrappers or registration points when required by tooling.

Expand All @@ -21,6 +21,7 @@ This distinction is the one most easily got wrong, and getting it wrong produces
| `skills/<name>/` | the skills this repository **authors and publishes** | by hand | yes |
| `skills-lock.json` | the record of skills installed **from other sources** | the skills CLI | yes |
| `.agents/skills/<name>/` | skills installed from other sources (the CLI's "universal" location) | the CLI | no |
| `.agents/skills/<name>/` | a **repository-local** skill — guidance for working in *this* repository, never published | by hand | yes |
| `.claude/skills/`, `agent/skills/` | per-agent links into the universal location | the CLI | no |

### `skills/` is published, not installed
Expand All @@ -35,23 +36,25 @@ They are not installed into this repository's own agent directories. An agent wo

Naming follows from the same fact. Skills install into one flat directory per agent, shared with every skill a developer has installed from anywhere, so **every skill here carries the `purview-buildsdk-` prefix**. `sdk-*`, `project-*`, `test-*` and `build-*` are far too common to use unprefixed, and the loser of a collision is whichever installed second. `RepositorySkillTests.EverySkill_IsNamedForThisProduct` enforces that.

Guidance about working *in this repository* belongs in this file, not in a skill — a skill here ships to someone else.
Guidance about working *in this repository* belongs in this file, or in a **repository-local** skill under `.agents/skills/` (which is not published) — a skill under `skills/` ships to someone else.

### `.agents/skills/` is for skills from elsewhere
### `.agents/skills/` — installed skills, plus the one local skill

To add one:
To add an installed skill:

```bash
just skills-add purview-dev/engineering-playbook # append '#<ref>' to pin it
```

Everything the CLI writes is gitignored, because it is all reproducible from the committed `skills-lock.json` with `npx skills experimental_install`. Only `.agents/skills/` is ignored, not the whole of `.agents/`, because `.agents/agents/` and `.agents/prompts/` are authored content.
Everything the CLI writes is gitignored, because it is all reproducible from the committed `skills-lock.json` with `npx skills experimental_install`. Only `.agents/skills/*` is ignored by name, not the whole of `.agents/`, because `.agents/agents/` and `.agents/prompts/` are authored content.

One entry under `.agents/skills/` is **tracked**: `purview-buildsdk-settings-docs-sync`, a repository-local skill. It is guidance about working *in this repository*, so it must not ship to a consumer and cannot live under `skills/`, and `.agents/skills/` is the one location an agent reads natively here. `.gitignore` un-ignores it by name; `RepositorySkillsSelfTests.InstalledSkillsAreNotTracked` still fails if any skill the CLI *installed* — anything recorded in `skills-lock.json` — is tracked.

**Skills have exactly one installer, and it is the skills CLI.** The SDK writes nothing into an agent directory at all — the `.agents` mirror that used to copy a package's `agents/` and `prompts/` has been removed outright. A package still ships `.agents/skills/<name>/`, but as *metadata*: it is what `ResolvePurviewSkillSources` reads to map a package to its skills and commit, not content to copy.

The single-writer rule is why. The CLI's universal install location is that same folder, so a second writer would mean the mirror silently overwriting a pinned install on the next build while `PRSKL0011` compared a lockfile ref against content the mirror had already replaced. See "The agent-folder mirror was removed" below for what went with it.

Link to a skill by its **authored** path (`./skills/<name>/SKILL.md`). A link into `.agents/skills/` points at a gitignored file and is broken in a fresh clone; `RepositorySkillsSelfTests` fails the build if one reappears.
Link to a published skill by its **authored** path (`./skills/<name>/SKILL.md`). A link into `.agents/skills/` points at a CLI-installed, gitignored file and is broken in a fresh clone; `RepositorySkillsSelfTests` fails the build if one reappears. The repository-local skill needs no link here — the agent runtime finds it.

## Purpose

Expand All @@ -69,10 +72,10 @@ The authored set, and what each one answers:

| Skill | Answers |
| -- | -- |
| [`purview-buildsdk-adoption`](./skills/purview-buildsdk-adoption/SKILL.md) | wiring the SDK into a repository, and why a property had no effect |
| [`purview-buildsdk-configuration`](./skills/purview-buildsdk-configuration/SKILL.md) | which property controls this, and where must it be set |
| [`purview-buildsdk-project-layout`](./skills/purview-buildsdk-project-layout/SKILL.md) | where does a project go, and what is it called |
| [`purview-buildsdk-project-detection`](./skills/purview-buildsdk-project-detection/SKILL.md) | why did the SDK do that automatically |
| [`purview-buildsdk-adoption`](./skills/purview-buildsdk-adoption/SKILL.md) | wiring the SDK into a repository, required settings and versions, and why a property had no effect |
| [`purview-buildsdk-configuration`](./skills/purview-buildsdk-configuration/SKILL.md) | which property controls this, where must it be set, how to set the target framework(s), and which SourceLink provider matches the repository host |
| [`purview-buildsdk-project-layout`](./skills/purview-buildsdk-project-layout/SKILL.md) | where does a project go, what is it called, and which target framework(s) it uses |
| [`purview-buildsdk-project-detection`](./skills/purview-buildsdk-project-detection/SKILL.md) | why did the SDK do that automatically, target framework included |
| [`purview-buildsdk-testing`](./skills/purview-buildsdk-testing/SKILL.md) | writing TUnit tests, and running them under MTP |
| [`purview-buildsdk-skill-tests`](./skills/purview-buildsdk-skill-tests/SKILL.md) | authoring a skill, and validating it with `Purview.Skills.Testing` |
| [`purview-buildsdk-skill-installation`](./skills/purview-buildsdk-skill-installation/SKILL.md) | installing the skills a package ships, and the `PRSKL*` diagnostics |
Expand Down
Loading
Loading