Skip to content
Open
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
69 changes: 69 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Bug
description: Something compiles but does less than it should, throws when it shouldn't, or doesn't refuse when it should.
labels: [bug]
body:
- type: markdown
attributes:
value: |
The bugs that matter most here don't crash. A value object builds but was never generated, a
failed `Result` goes unnoticed, an analyzer stops reporting, a column reads back a value the
type would have refused. If that's what you found, please say so below. It decides how soon
the fix ships.
- type: dropdown
id: package
attributes:
label: Package
multiple: true
options:
- CodoMetis.TypeKit
- CodoMetis.TypeKit.Analyzers
- CodoMetis.TypeKit.Generators
- CodoMetis.TypeKit.EntityFrameworkCore
- CodoMetis.TypeKit.AspNetCore
- CodoMetis.TypeKit.Serilog
- CodoMetis.TypeKit.Testing
validations:
required: true
- type: input
id: version
attributes:
label: Version
placeholder: "2.2.0"
validations:
required: true
- type: dropdown
id: failure
attributes:
label: How does it fail?
options:
- Silently. It compiles or answers, and does less than it appears to
- Loudly. It throws, fails the build or reports a diagnostic where it should work
- I'm not sure
validations:
required: true
- type: textarea
id: repro
attributes:
label: What you ran
description: The smallest code that shows it. For EF Core, include the query and the SQL it translated to; for JSON or OpenAPI, the payload or the schema.
render: csharp
validations:
required: true
- type: textarea
id: actual
attributes:
label: What you got
validations:
required: true
- type: textarea
id: expected
attributes:
label: What you expected
description: If a docs page or a README says what should happen, link it.
validations:
required: true
- type: input
id: environment
attributes:
label: Environment
description: .NET SDK version, OS, whether you publish with Native AOT, and the database provider if EF Core is involved.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Security report
url: https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/security/advisories/new
about: Report a vulnerability privately, as SECURITY.md describes. Please don't open a public issue for it.
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/enhancement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Enhancement
description: A new member, generated feature, analyzer rule, satellite or option.
labels: [enhancement]
body:
- type: textarea
id: problem
attributes:
label: What you're trying to do
description: The task, not the API. What do you write today, and why doesn't it work?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: What you'd like
description: A sketch of the code you'd write with it.
render: csharp
- type: dropdown
id: breaking
attributes:
label: Does it change something that compiles or works today?
description: A change to an existing signature, a generated member, a wire form or a diagnostic's severity needs a major version, even if the new behaviour is better.
options:
- No, it only adds
- Yes, existing code would compile or behave differently
- I'm not sure
validations:
required: true
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/epic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Epic
description: A release or a theme. Its sub-issues are the work. (Maintainers.)
title: "Epic: "
labels: [epic]
body:
- type: textarea
id: goal
attributes:
label: Goal
description: What is true when this is done, in one or two sentences. For a release, link its section of design/roadmap.md.
validations:
required: true
- type: textarea
id: scope
attributes:
label: In scope and out of scope
validations:
required: true
- type: textarea
id: done
attributes:
label: Done when
description: Every sub-issue is closed, plus anything that isn't an issue (a tag, a published package, a doc).
validations:
required: true
- type: markdown
attributes:
value: Add the work as sub-issues once this exists. For a release, set the milestone on the epic and on every sub-issue.
22 changes: 22 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Closes #

<!--
Give this PR the same labels and milestone as the issue it closes: one type (bug, enhancement,
documentation, tests, chore), the package/* and area/* it touches, severity/* on a bug, and silent,
breaking, found-by/prober or stacked where they apply. The legend is in CONTRIBUTING.md, "Issues and
labels". CI fails a PR with no type label, a bug with no severity, a missing closing reference, a
branch whose <type>/ disagrees with its type label, a PR into release/<version> without that
milestone, or a breaking PR into anything but a major.
-->

## Problem

<!-- What was wrong or missing, and what a consumer would have seen. -->

## Change

<!-- What changed, file by file where it helps. -->

## Tests

<!-- What proves it. For a fix: the test, that it fails with the fix reverted, and the variants it ran across. -->
51 changes: 51 additions & 0 deletions .github/workflows/pr-hygiene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Every pull request into main or a release branch carries one type label, a severity if it is a bug,
# the milestone of the release it goes into, a branch named <type>/<slug> that agrees with its type, and a
# closing reference to the issue it resolves; a breaking one goes into a major (CONTRIBUTING.md, "Issues and
# labels"; design/adr/0109-work-is-tracked-as-labelled-issues-and-ci-checks-each-pull-request.md). The rules
# live in tools/PrHygiene, a tool project like the others (design/adr/0092), which reads the event payload
# alone: no token, nothing of the pull request's code run, and runnable locally on a saved payload.
#
# Triggered on label, milestone and description edits as well as pushes, so adding what is missing turns
# the check green without a new commit. `pull_request`, not `pull_request_target`: a pull request from a
# fork runs with a read-only token and never sees a secret, and builds the tool from its own checkout,
# which can only weaken the check on that pull request's own run, where a reviewer reads the diff.
#
# Actions are pinned to commit SHAs, as in dotnet.yml.

name: PR hygiene

on:
pull_request:
branches: [ "main", "release/**" ]
types: [ opened, edited, reopened, synchronize, labeled, unlabeled, milestoned, demilestoned ]

permissions:
contents: read

concurrency:
group: pr-hygiene-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true

jobs:
check:
name: Labels and issue link
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
global-json-file: global.json

- name: Build the check
run: dotnet build tools/PrHygiene -c Release

- name: Check labels, milestone, branch and issue link
run: dotnet tools/PrHygiene/bin/Release/net10.0/pr-hygiene.dll "$GITHUB_EVENT_PATH"
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dotnet test --project test/CodoMetis.TypeKit.Conventions.Tests --filter-method "
dotnet build spikes/FabricSpike/FabricSpike.slnx # the fabric spike, standalone
dotnet tools/ConsumerSmokeTest/bin/Release/net10.0/consumer-smoke-test.dll # the packages, installed into throwaway consumers, one published with Native AOT (needs the platform linker); build it with -c Release first
dotnet tools/BuildVerdict/bin/Release/net10.0/build-verdict.dll test [--project P] [--filter-class C] [--filter-method M] [-- flags] # an agent's build and test: a verdict, not the transcript
dotnet tools/PrHygiene/bin/Release/net10.0/pr-hygiene.dll event.json # a PR's labels, milestone, branch and issue link, as CI's PR hygiene check reads them from the payload
```

The probing tool, prober (`tools/Prober`, started as its built dll like docs-query). These need no
Expand Down Expand Up @@ -221,6 +222,28 @@ the whole page. A ranked miss proves nothing.
analyzer that stops firing, or a converter that materialises `default` are this repo's
characteristic failures. Prefer a diagnostic or an exception to a plausible result.

## Issues
Work is tracked as GitHub issues, not as lists in docs or memory
([decision 109](design/adr/0109-work-is-tracked-as-labelled-issues-and-ci-checks-each-pull-request.md)).
Open the issue before starting the work: a bug, an enhancement, a follow-up, a finding from a review or a
probe.
- **Labels, on issues and PRs alike** (ValueRanges' scheme; the legend is CONTRIBUTING's "Issues and
labels", and `gh label list` the source): exactly one type (`bug`, `enhancement`, `documentation`,
`tests`, `chore`), exactly one `severity/*` on a bug, the `package/*` and `area/*` it touches, and
`silent` (a bug that does less than it appears to), `breaking`, `found-by/prober` or `stacked` where they
apply. Never invent a label; if none fits, say so.
- **Epics:** a release is an `epic` issue with a milestone of its version (`2.3.0`), its work attached as
native sub-issues with the same milestone (`gh issue create --parent N --milestone M`). A theme that spans
releases is an `epic` without a milestone; a finding belongs to the release it ships in. The plan stays in
`design/roadmap.md`; there is no roadmap issue.
- **PRs** carry their issue's labels and milestone, open with `Closes #n`, and follow
`.github/pull_request_template.md` (Problem, Change, Tests). `pr-hygiene.yml` runs `tools/PrHygiene` on
every PR into `main` or a release branch and fails a missing type, severity or closing reference, a branch
whose `<type>/` disagrees with its type label, a PR into `release/<version>` without that milestone, and
`breaking` into anything but a major. A topic merges into its release branch, where `Closes` closes
nothing: close its issue by hand on merge, with a comment linking the PR.
- When the work changes what an issue says (its scope, its state, a measured fact), edit the issue.

## Workflow
1. Read `design/plan.md`, the roadmap, the decision records and the design note for the area you
touch, and the relevant spike README, before starting. A decision is changed by a new record.
Expand Down
59 changes: 56 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,56 @@ string, an assembly-name prefix or a type-name list will be asked to replace it.
**Prefer a loud failure to a silent one.** Where the package cannot do something correctly, it
should fail the build or throw with an actionable message.

## Issues and labels

Use the **Bug** or **Enhancement** form. The bug form asks whether the failure is silent or loud, because a
silent one, code that compiles and does less than it appears to, is the failure this repository exists to
prevent.

Each release is an `epic` issue with a milestone of its version (`2.3.0`), and its work is attached as
sub-issues with the same milestone; a theme that spans releases is an `epic` without one. The plan stays in
[design/roadmap.md](design/roadmap.md), which an epic links rather than copies
([decision 109](design/adr/0109-work-is-tracked-as-labelled-issues-and-ci-checks-each-pull-request.md)).
A maintainer sets the labels when triaging, and a pull request gets the same labels and milestone as the
issue it closes:

| Label | Means |
|---|---|
| `bug`, `enhancement`, `documentation`, `tests`, `chore` | The type. Exactly one |
| `severity/critical` | Data loss, or silently wrong results, in common use |
| `severity/high` | Wrong results, a crash or a failed build in a realistic scenario |
| `severity/medium` | An inconsistency or a failure in a narrow scenario |
| `severity/low` | A cosmetic, message or documentation-level difference |
| `silent` | A bug that compiles or answers and does less than it appears to |
| `breaking` | Changes a signature, a generated member, a wire form or a diagnostic's severity: needs a major |
| `found-by/prober` | Found by the prober; the fix's commit carries `Found-By: PR` |
| `stacked` | Based on another open pull request: merge in order |
| `epic` | A release or a theme; its sub-issues are the work |
| `package/core`, `package/analyzers`, `package/generators`, `package/efcore`, `package/aspnetcore`, `package/serilog`, `package/testing` | The package it affects |
| `area/option-result` | `Option`, `Result` and their extensions |
| `area/value-objects` | The value-object contracts and what the aspects generate for them |
| `area/json` | System.Text.Json wire forms, values and dictionary keys |
| `area/aot` | Native AOT and trimming |
| `area/parsing` | `Parse`, `TryParse`, formatting and the type converter |
| `area/release` | Versioning, changelogs, packaging and release wiring |
| `area/tooling` | The repository's tools: the prober, the verdict, the guard, docs-query, the smoke test |

A bug carries exactly one severity. CI's "PR hygiene" check reads each pull request into `main` or a release
branch and fails it with no type label, a bug with no severity, `silent` on anything but a bug, no closing
reference, a branch whose `<type>/` disagrees with its type label (`feat` is `enhancement`, `fix` is `bug`,
`docs` is `documentation`, `test` is `tests`, the rest `chore`), a pull request into `release/<version>`
without that milestone, or `breaking` on a release that is not a major. It turns green when you add what is
missing, without a new commit. To run it on a saved payload:

```bash
dotnet build tools/PrHygiene -c Release
dotnet tools/PrHygiene/bin/Release/net10.0/pr-hygiene.dll path/to/event.json
```

`Closes #12` closes an issue only when the pull request merges into `main`, and topics merge into a release
branch, so close the issue by hand when your topic merges, with a comment linking the pull request. The
release's pull request into `main` closes its epic.

## Pull requests

Work for the next version goes into its release branch, `release/<version>` (`release/2.2.0`), not
Expand All @@ -96,7 +146,9 @@ Branch from it as `<type>/<slug>`, where the type is your main commit's (`feat/
`build/…`), and open the PR against it. PRs are merged with a merge commit, never squashed or rebased:
the design records cite commits by hash.

- One concern per PR, with the reasoning in the description rather than only in the diff.
- One concern per PR, closing one issue, with its labels and milestone ([above](#issues-and-labels)).
The description follows the template: `Closes #n`, then Problem, Change and Tests, with the reasoning
there rather than only in the diff.
- Full suite green.
- User-visible changes get a changelog entry.
- Match the surrounding style. Comments explain *why*, especially where behaviour is load-bearing
Expand All @@ -121,15 +173,16 @@ The packages share one version and are released together, only through
2. Move the rules in `src/CodoMetis.TypeKit.Analyzers/AnalyzerReleases.Unshipped.md` to
`AnalyzerReleases.Shipped.md`, under `## Release <version>`. Their ids and severities are public
contract from then on. `ChangelogTests` fails until steps 1 and 2 agree.
3. Commit on the release branch, open its PR into `main`, and merge it with a merge commit. Tag that
3. Commit on the release branch, open its PR into `main` (`chore` and `area/release`, the version's
milestone, `Closes #<epic>`), and merge it with a merge commit. Tag that
merge commit and push the tag: `git tag v1.0.0 && git push origin v1.0.0`. The workflow checks the tag
against `Version` and the dated heading, runs the suite and the smoke test, and waits for approval
in the `nuget` environment before pushing. Delete the release branch once the tag is pushed.
4. Afterwards: raise `Version` in `Directory.Build.props`, set `PackageValidationBaselineVersion` to
the version just released, so ApiCompat guards the public surface from then on, and open a new
`## <version> — Unreleased` section. `ChangelogTests` fails while the baseline trails the last
version the changelog dates. Once that is on `main`, cut the next `release/<version>` from it and
push it.
push it, and open the version's milestone and its `epic` issue.

`workflow_dispatch` with `dry_run` (the default) rehearses everything but the push.

Expand Down
2 changes: 2 additions & 0 deletions CodoMetis.TypeKit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<Project Path="test/CodoMetis.TypeKit.Facts/CodoMetis.TypeKit.Facts.csproj" />
<Project Path="test/CodoMetis.TypeKit.Generators.Probes/CodoMetis.TypeKit.Generators.Probes.csproj" />
<Project Path="test/CodoMetis.TypeKit.Generators.Tests/CodoMetis.TypeKit.Generators.Tests.csproj" />
<Project Path="test/CodoMetis.TypeKit.PrHygiene.Tests/CodoMetis.TypeKit.PrHygiene.Tests.csproj" />
<Project Path="test/CodoMetis.TypeKit.Prober.Tests/CodoMetis.TypeKit.Prober.Tests.csproj" />
<Project Path="test/CodoMetis.TypeKit.Samples/CodoMetis.TypeKit.Samples.csproj" />
<Project Path="test/CodoMetis.TypeKit.Serilog.Tests/CodoMetis.TypeKit.Serilog.Tests.csproj" />
Expand All @@ -31,6 +32,7 @@
<Project Path="tools/BuildVerdict/BuildVerdict.csproj" />
<Project Path="tools/ConsumerSmokeTest/ConsumerSmokeTest.csproj" />
<Project Path="tools/DocsQuery/DocsQuery.csproj" />
<Project Path="tools/PrHygiene/PrHygiene.csproj" />
<Project Path="tools/Prober/Prober.csproj" />
</Folder>
<Folder Name="/benchmarks/">
Expand Down
Loading
Loading