diff --git a/.agents/agents/catalogue-auditor.agent.md b/.agents/agents/catalogue-auditor.agent.md index 00d6821..8db4b47 100644 --- a/.agents/agents/catalogue-auditor.agent.md +++ b/.agents/agents/catalogue-auditor.agent.md @@ -1,6 +1,6 @@ --- name: Catalogue Auditor -description: "Specialist for auditing src/src/data/projects.yml against reality: schema validity, documentation configuration, lifecycle stability, metadata, tags, packages, relationships, use-case coverage, and ordering." +description: "Specialist for auditing every catalogue record under src/src/data/ against reality: schema validity, documentation configuration, lifecycle stability, metadata, tags, packages, relationships, use-case coverage, and ordering." tools: [ "search/codebase", @@ -18,7 +18,7 @@ You are a specialist for auditing (and repairing) the Purview-Dev project catalo ## Primary objective -Prove that every project defined in `src/src/data/projects.yml` is correct and report the evidence, +Prove that every project defined in the catalogue (`src/src/data/projects/`) is correct and report the evidence, then fix the drift that is unambiguous and safe to fix. ## Background knowledge diff --git a/.agents/agents/catalogue-onboarder.agent.md b/.agents/agents/catalogue-onboarder.agent.md index e0ee261..34425eb 100644 --- a/.agents/agents/catalogue-onboarder.agent.md +++ b/.agents/agents/catalogue-onboarder.agent.md @@ -1,6 +1,6 @@ --- name: Catalogue Onboarder -description: "Specialist for adding a purview-dev (or collaboration) repository to the website catalogue: recon the repository, author the projects.yml record (docs, tags, packages, use cases), integrate it across the site, and prove it with the validation pipeline." +description: "Specialist for adding a purview-dev (or collaboration) repository to the website catalogue: recon the repository, author the catalogue record (docs, tags, packages, use cases), integrate it across the site, and prove it with the validation pipeline." tools: [ "search/codebase", @@ -19,7 +19,7 @@ You are a specialist for onboarding repositories into the Purview-Dev website ca ## Primary objective Take a repository (`purview-dev/` or a collaboration repository) and deliver a complete, -validated integration: a correct `src/src/data/projects.yml` record, a working documentation +validated integration: a correct catalogue record under `src/src/data/projects/`, a working documentation configuration, concrete use cases, tags/metadata, and a green `just validate`. ## Background knowledge @@ -41,7 +41,9 @@ The most important rules are: - **Tags are GitHub topics**, not manifest fields. - **`name` must slugify to `id`** for any project with `docs` (the per-project LLM bundle path). - **Every non-archived project needs at least one use case with real code and evidence.** -- **`status` must match reality** (`stable` / `preview` / `archived`). +- **`status` must match reality** (`stable` / `preview` / `archived`), and **`experimental: true`** marks + an exploratory project — it is orthogonal to `status`, invalid on an archived project, and displayed + through `displayStatus()` (ADR 0004). - **Never hand-edit generated content** or weaken a check. ## Workflow diff --git a/.agents/prompts/add-project.prompt.md b/.agents/prompts/add-project.prompt.md index d04cd72..8828bfd 100644 --- a/.agents/prompts/add-project.prompt.md +++ b/.agents/prompts/add-project.prompt.md @@ -11,6 +11,7 @@ Inputs (fill in before running): - Owned by purview-dev? `yes` (catalogue project) / `no` (collaboration) - Category (optional — let the recon decide): `` - Status (optional — let the release channel decide): `stable | preview | archived` +- Experimental? `yes` / `no` — `yes` sets the `experimental` flag (ADR 0004); it does not replace `status` - Notes: anything known about docs location, package ids, or the story to tell ## Instructions @@ -30,7 +31,7 @@ Use the `catalogue-onboarder` agent if it is available. 1. Recon evidence: repository metadata, docs tree/landing page, package ids and channels, release channel. Show the commands and their output. -2. The `projects.yml` record (or `externalProjects` record) added at an unused `order`, with `docs`, +2. The catalogue record (or `externalProjects` record) added at an unused `order`, with `docs`, `packages`, `targetFrameworks`, `install` (only when not plain NuGet), relationships and acknowledgments where they genuinely apply. 3. At least one concrete use case with `code` + `language`, an `evidence` fact, and a `docsPage` that diff --git a/.agents/prompts/audit-projects.prompt.md b/.agents/prompts/audit-projects.prompt.md index b90e947..3967def 100644 --- a/.agents/prompts/audit-projects.prompt.md +++ b/.agents/prompts/audit-projects.prompt.md @@ -1,6 +1,6 @@ --- mode: agent -description: Audit every project in projects.yml for correctness (documentation, stability, metadata) and repair the clear drift. +description: Audit every project in the catalogue for correctness (documentation, stability, metadata) and repair the clear drift. --- # Audit the project catalogue diff --git a/.agents/skills/add-catalogue-project/SKILL.md b/.agents/skills/add-catalogue-project/SKILL.md index ee878de..2227250 100644 --- a/.agents/skills/add-catalogue-project/SKILL.md +++ b/.agents/skills/add-catalogue-project/SKILL.md @@ -1,6 +1,6 @@ --- name: add-catalogue-project -description: Use when adding a repository to the Purview-Dev website catalogue — recon the repo, author the projects.yml record (docs, packages, tags, use cases), integrate it across the catalogue/docs/releases surfaces, and prove it with the validation pipeline. +description: Use when adding a repository to the Purview-Dev website catalogue — recon the repo, author the catalogue record (docs, packages, tags, use cases), integrate it across the catalogue/docs/releases surfaces, and prove it with the validation pipeline. category: purview-dev-website roles: - catalogue @@ -61,8 +61,12 @@ description/topics. `source-generation`, `aspire`, `build-tooling`, `developer-tooling`. - `status`: - `archived` when the repository is archived (GitHub `archived: true`). - - `preview` when there is no stable NuGet release, or the tooling is explicitly experimental. + - `preview` when there is no stable NuGet release. - `stable` only when a stable (non-prerelease) release exists and is recommended for use. +- `experimental: true` (a flag, not a status value) when the tooling is explicitly an experiment: + no compatibility promise, no production support guarantee. Keep `status` as the honest release + channel alongside it (`preview` for prerelease-only tooling). See ADR 0004; the project page and + every aggregated documentation page then carry an Experimental warning. - `id`: lowercase `[a-z0-9-]` slug, normally the repository name. - `name`: display name. **It must slugify to `id` when the project has `docs`** (the LLM bundle is emitted at `/_llms-txt/.txt` while the UI links `/_llms-txt/.txt`). @@ -70,7 +74,7 @@ description/topics. ## Step 3 — author the record -Add the entry to `src/src/data/projects.yml` (see `project-manifest-reference` for every field): +Add the entry as `src/src/data/projects/.yml` (see `project-manifest-reference` for every field): ```yaml - id: diff --git a/.agents/skills/audit-catalogue-projects/SKILL.md b/.agents/skills/audit-catalogue-projects/SKILL.md index b122c71..bb3c20d 100644 --- a/.agents/skills/audit-catalogue-projects/SKILL.md +++ b/.agents/skills/audit-catalogue-projects/SKILL.md @@ -1,6 +1,6 @@ --- name: audit-catalogue-projects -description: Use when auditing or repairing src/src/data/projects.yml — the invariant checklist for documentation, stability, metadata, tags, packages, relationships, use cases and ordering, plus the findings report format. +description: Use when auditing or repairing the catalogue records under src/src/data/ — the invariant checklist for documentation, stability, metadata, tags, packages, relationships, use cases and ordering, plus the findings report format. category: purview-dev-website roles: - catalogue @@ -58,6 +58,9 @@ Related: `project-manifest-reference` (fields and vocabularies), `docs-aggregati - `archived` repository ⇒ `status: archived`. - No stable NuGet release ⇒ not `stable` (`preview`). +- `experimental: true` is orthogonal to `status`: report `experimental` with `status: stable` as + `drift` (the flag says "no support promise" while the channel says "recommended for use"), and + `experimental` on an archived project as a `blocker` (the schema rejects it outright). - `stable` ⇒ a stable, listed NuGet release exists and the repository is not archived. - Deprecated NuGet packages are reported, not silently accepted. - A project with `packages: []` is intentional: build tooling consumed as a tool/SDK still declares @@ -132,5 +135,5 @@ evidence and the guards agree. the gap instead. - Do not change the schema vocabulary; adding a `status`/`category` value needs an ADR. - Do not hand-edit the docs mirror or caches to make the audit pass. -- If an audit fix changes `projects.yml`, re-run `just check-projects`, `bun run test`, and +- If an audit fix changes a catalogue record, re-run `just check-projects`, `bun run test`, and `just validate` before reporting completion. diff --git a/.agents/skills/docs-aggregation-rules/SKILL.md b/.agents/skills/docs-aggregation-rules/SKILL.md index 3dd0b5f..882209b 100644 --- a/.agents/skills/docs-aggregation-rules/SKILL.md +++ b/.agents/skills/docs-aggregation-rules/SKILL.md @@ -66,7 +66,8 @@ The wikis of several product repositories are mirrored into `docs/wiki`, so the ## Injected front matter (per page) `title` (first `# H1`, else the slug), `description` (first prose paragraph, ≤160 chars), -`owners: [purview-dev]`, `status` (the project's status), `lastReviewed`, `sourceProject`, +`owners: [purview-dev]`, `status` (the project's status), `experimental: true` (written only when +the catalogue flags the project, ADR 0004), `lastReviewed`, `sourceProject`, `sourceRepo`, `projectName`, `sourcePath`, `editUrl`, `tags` (the repository's GitHub **topics**, absent when there are none), and `sidebar.label`/`sidebar.order`. diff --git a/.agents/skills/github-repo-metadata/SKILL.md b/.agents/skills/github-repo-metadata/SKILL.md index 259991b..16df5a6 100644 --- a/.agents/skills/github-repo-metadata/SKILL.md +++ b/.agents/skills/github-repo-metadata/SKILL.md @@ -67,7 +67,7 @@ Discussions on an organisation repository requires organisation permission; repo four chips are meaningful (cards render `tags.slice(0, 4)`). - Prefer ecosystem-standard names (`dotnet`, `csharp`, `nuget`, `opentelemetry`) so cross-repo searches behave predictably. -- Topics are the **only** way tags reach the site. There is no `tags` field in `projects.yml` — do +- Topics are the **only** way tags reach the site. There is no `tags` field in a catalogue record — do not add one. ## Stability signals to check against `status` @@ -77,6 +77,7 @@ Discussions on an organisation repository requires organisation permission; repo | `archived: true` | `status` must be `archived` (and `supersededBy` when a successor exists) | | No stable release, only `-prerelease.N` tags | `preview` at most | | Latest NuGet version is stable and listed | `stable` is allowed | +| The README/repository presents the project as an experiment | Set `experimental: true` (it does not replace `status`, which stays the release channel) | | NuGet `deprecated: true` | Report as `drift`/`upstream`; a deprecated package should not front a `stable` project without explanation | | `default_branch` is not `main` | Docs aggregation produces nothing — a blocker for any project with `docs` | diff --git a/.agents/skills/project-manifest-reference/SKILL.md b/.agents/skills/project-manifest-reference/SKILL.md index 0bfb0c1..21762d2 100644 --- a/.agents/skills/project-manifest-reference/SKILL.md +++ b/.agents/skills/project-manifest-reference/SKILL.md @@ -1,6 +1,6 @@ --- name: project-manifest-reference -description: Field-by-field reference for src/src/data/projects.yml (schema vocabulary, defaults, loader-enforced rules, and the invariants the site build depends on). Use whenever authoring or reviewing a catalogue record. +description: Field-by-field reference for the catalogue records under src/src/data/projects/ (schema vocabulary, defaults, loader-enforced rules, and the invariants the site build depends on). Use whenever authoring or reviewing a catalogue record. category: purview-dev-website roles: - catalogue @@ -13,7 +13,8 @@ tags: # project-manifest-reference Skill -The catalogue is `src/src/data/projects.yml`, typed by `src/src/lib/manifest/schema.ts` and validated +The catalogue is one file per project under `src/src/data/projects/` (plus +`src/src/data/external-projects.yml` for collaborations), typed by `src/src/lib/manifest/schema.ts` and validated by `src/src/lib/manifest/load.ts`. `$schema: ../lib/manifest/schema.ts` is the first line of the file. ## Vocabularies (do not invent values) @@ -22,10 +23,12 @@ by `src/src/lib/manifest/load.ts`. `$schema: ../lib/manifest/schema.ts` is the f | --- | --- | | `category` | `application-framework`, `validation`, `observability`, `source-generation`, `aspire`, `build-tooling`, `developer-tooling` | | `status` | `stable`, `preview`, `archived` | +| `experimental` | boolean flag, default `false` — orthogonal to `status` (ADR 0004); invalid with `status: archived` | | `install` | `nuget` (default), `msbuild-sdk`, `dotnet-tool` | | `useCases[].audience` | `developer`, `team-lead`, `architect`, `contributor` (labels: Developer, Team lead, Architect, Contributor) | -Adding a value to any of these lists is a schema change and requires an ADR. +Adding a value to any of these lists is a schema change and requires an ADR. `experimental` is a +**flag, not a status value**: it never replaces `status`, which stays the project's release channel. ## `projects[]` fields @@ -41,6 +44,7 @@ Adding a value to any of these lists is a schema change and requires an ADR. | `repository` | yes | `owner/repository`; **`owner` must be `purview-dev`** for `projects[]` | | `category` | yes | See vocabulary | | `status` | yes | See vocabulary | +| `experimental` | no | Default `false`; marks an exploratory project (unstable API, no support promise). Orthogonal to `status`; invalid with `status: archived`. Displayed as its own status via `displayStatus()` | | `featured` | no | Default `false`; shown prominently on the home page | | `order` | no | Default `1000`; projects are sorted ascending; keep values unique | | `docs` | no | See below | @@ -76,12 +80,13 @@ Adding a value to any of these lists is a schema change and requires an ADR. 2. `projects[].repository.split('/')[0]` must equal `purview-dev`. 3. `related`, `supersedes`, and `supersededBy` must all reference existing project ids. 4. A NuGet package id may be declared by **exactly one** project. -5. The returned list is sorted by `order` ascending. +5. `experimental: true` is rejected when `status` is `archived` (schema refinement). +6. The returned list is sorted by `order` ascending. ## Defaults applied by the loader `featured: false`, `order: 1000`, `packages: []`, `related: []`, `useCases: []`, -`acknowledgments: []`, `discussions: false`, `install: 'nuget'`. Derived values (`repoOwner`, +`acknowledgments: []`, `discussions: false`, `install: 'nuget'`, `experimental: false`. Derived values (`repoOwner`, `repoName`, `sourceUrl`, `issuesUrl`, `discussionsUrl`, `changelogUrl`, `releasesUrl`) are computed, never authored. diff --git a/.agents/skills/site-validation-loop/SKILL.md b/.agents/skills/site-validation-loop/SKILL.md index 822b852..50763bf 100644 --- a/.agents/skills/site-validation-loop/SKILL.md +++ b/.agents/skills/site-validation-loop/SKILL.md @@ -35,7 +35,7 @@ tags: ## The catalogue guard (`just check-projects`) -`src/scripts/check-projects.ts` validates `projects.yml` against the generated data (offline, +`src/scripts/check-projects.ts` validates the catalogue records (`src/src/data/`) against the generated data (offline, deterministic). It fails the build on: - ids/orders duplicated, `order` not a unique integer, `name` not slugifying to `id` for documented diff --git a/.agents/skills/use-case-authoring/SKILL.md b/.agents/skills/use-case-authoring/SKILL.md index d634c97..84279ef 100644 --- a/.agents/skills/use-case-authoring/SKILL.md +++ b/.agents/skills/use-case-authoring/SKILL.md @@ -1,6 +1,6 @@ --- name: use-case-authoring -description: Use when writing or reviewing useCases in projects.yml — the ADR 0002 rules for audience-tagged, concrete, evidence-backed examples with short code snippets and resolvable documentation deep links. +description: Use when writing or reviewing useCases in the catalogue records — the ADR 0002 rules for audience-tagged, concrete, evidence-backed examples with short code snippets and resolvable documentation deep links. category: purview-dev-website roles: - catalogue @@ -14,7 +14,7 @@ tags: # use-case-authoring Skill Use cases are the "what does it actually do for me" evidence on the home page, on each project page, -and on `/use-cases/`. They are authored in `src/src/data/projects.yml` and validated by the manifest +and on `/use-cases/`. They are authored in the catalogue records (`src/src/data/projects/.yml`) and validated by the manifest schema. The governing decision is `docs/decisions/0002-concrete-use-cases.md`. ## Shape diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index decce35..fdf390c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,7 +16,7 @@ package is `src/`. - Treat `just validate` as the definition of done. It chains format-check, lint, typecheck, unit tests, asset checks, build, link crawl, generated-output checks, built-output tests, and the catalogue guard (`just check-projects`). -- Edit the catalogue in `src/src/data/projects.yml` only, and keep every invariant listed in +- Edit the catalogue under `src/src/data/` only (one file per project, plus `external-projects.yml`), and keep every invariant listed in `AGENTS.md` (unique ids/orders/packages, `name` slugifying to `id` for documented projects, at least one use case per non-archived project, `code` paired with `language`, resolvable `docsPage`). - Keep documentation where it lives (the product repository). This site aggregates it at build time. @@ -33,12 +33,15 @@ package is `src/`. `src/src/lib/manifest/schema.ts`. - Never claim a project is `stable` without a stable NuGet release, or leave a project `stable` while its repository is archived. +- Never derive a project's displayed status outside `displayStatus()` (`src/src/lib/status.ts`): + `experimental: true` is an intent flag orthogonal to `status` (ADR 0004) and is invalid with + `status: archived`. ## Task routing - Adding a project → `.agents/agents/catalogue-onboarder.agent.md` and `.agents/skills/add-catalogue-project/SKILL.md`. -- Auditing/fixing `projects.yml` → `.agents/agents/catalogue-auditor.agent.md` and +- Auditing/fixing the catalogue → `.agents/agents/catalogue-auditor.agent.md` and `.agents/skills/audit-catalogue-projects/SKILL.md`. - Any other change → `.agents/` holds the manifest reference, docs-aggregation rules, use-case authoring rules, repo-metadata guidance, and the validation loop. diff --git a/AGENTS.md b/AGENTS.md index a4eca3a..8d0f5c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,8 @@ Two agent tasks recur here, and both have a dedicated agent plus skills under `. 1. **Add a project** — given a `purview-dev/` (or a collaboration repository), build its documentation, use cases, tags and metadata so it is fully integrated into the site. -2. **Audit the catalogue** — prove that every project defined in `src/src/data/projects.yml` is +2. **Audit the catalogue** — prove that every project defined in `src/src/data/projects/` (one file per project, plus + `external-projects.yml` for collaborations) is correct: documentation configuration, lifecycle stability, metadata and relationships. ## Technology and tooling context @@ -50,7 +51,7 @@ merge". `just check-projects` is part of that chain and fails the build on catal ## Ecosystem model — what "a project" means here -- `projects:` in `src/src/data/projects.yml` are Purview-owned catalogue entries. Their `repository` +- `projects:` entries — one file per project under `src/src/data/projects/` — are Purview-owned catalogue entries. Their `repository` **must** be `purview-dev/` (enforced by the loader). - `externalProjects:` are collaborations the organisation contributes to but does not own (for example `likec4/likec4`). They link out to their own site/repository: no documentation @@ -64,7 +65,7 @@ merge". `just check-projects` is part of that chain and fails the build on catal 1. **Never weaken a check to make a change pass.** Fix the data or the implementation. (ADR 0001.) 2. **Never hand-edit generated content** (`src/src/content/docs/**`, `src/.cache/**`, `src/dist/**`). -3. **Any edit to `projects.yml` must keep the catalogue invariants.** These are enforced by +3. **Any edit to a project record under `src/src/data/` must keep the catalogue invariants.** These are enforced by `loadProjects()` (schema, ownership, relationships, package uniqueness) and by `just check-projects`: - `id` is a lowercase `[a-z0-9-]` slug; `repository` is `owner/repository` and must be @@ -80,6 +81,10 @@ merge". `just check-projects` is part of that chain and fails the build on catal - `related`, `supersedes`, `supersededBy` must reference known ids. - `status` must match reality: an archived repository is `archived`; prerelease-only tooling is `preview`; a project with a stable release is `stable`. + - `experimental: true` marks a project whose API and packaging may change without notice + (ADR 0004). It is orthogonal to `status` — which stays the release channel — it is invalid on an + `archived` project, and it never hides a project: it adds a warning badge and a notice to the + catalogue and to every aggregated documentation page. 4. **Run the validation loop before finishing** — at minimum `just check-projects`, `bun run typecheck`, `bun run test`; run the full `just validate` for anything that touches the build, data aggregation, or rendering. @@ -111,7 +116,7 @@ Do not assume this file contains everything; consult `.agents/` while planning. | Concern | Location | | --- | --- | -| Catalogue manifest | `src/src/data/projects.yml` | +| Catalogue manifest | `src/src/data/projects/.yml` (one per project) + `src/src/data/external-projects.yml` | | Manifest schema / loader | `src/src/lib/manifest/{schema,load}.ts` | | Docs aggregation | `src/src/lib/docs/aggregate.ts` (+ `frontmatter`, `links`, `sidebar`, `staleness`) | | Release data | `src/src/lib/releases/*`, `src/scripts/fetch-releases.ts` | diff --git a/README.md b/README.md index 5622530..9bc9e6f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,8 @@ purview-dev/ # workspace root ├── src/ │ ├── content.config.ts # Starlight docs collection schema │ ├── content/docs/ # GENERATED documentation mirror (gitignored) - │ ├── data/projects.yml # Typed, validated project catalogue manifest + │ ├── data/projects/ # Typed, validated catalogue: one file per project + │ ├── data/external-projects.yml # Collaborations the organisation does not own │ ├── lib/ # manifest, releases, docs, urls, site constants │ ├── components/ # shared chrome, Starlight overrides, islands │ ├── layouts/ # SiteLayout.astro (marketing pages) @@ -165,7 +166,7 @@ just dev ``` The dev server starts after a data sync. Edit `src/src/pages/` for marketing -pages and `src/src/data/projects.yml` for catalogue content. Documentation pages +pages and `src/src/data/projects/.yml` for catalogue content. Documentation pages are generated under `src/src/content/docs/` and should not be edited by hand — change the source repository instead and re-run `just data-sync`. @@ -190,16 +191,18 @@ generated copies. ## Project catalogue -`src/src/data/projects.yml` is the single source of truth for the catalogue. It +`src/src/data/projects/.yml` (one file per project, plus +`src/src/data/external-projects.yml` for collaborations) is the single source of truth for the catalogue. It is validated at build time by a typed Zod schema (`src/src/lib/manifest/schema.ts`). Validation failures report the source file, the offending property, the expected shape, and a remediation hint. ### Adding a new Purview-Dev project -1. Add a record to `src/src/data/projects.yml` with a unique `id`, `name`, +1. Add a record at `src/src/data/projects/.yml` with a unique `id`, `name`, `shortDescription`, `description`, `repository` (a purview-dev repo), - `category`, `status`, and optional `order`. + `category`, `status`, and optional `order`. Set `experimental: true` for an + exploratory project — it is a flag orthogonal to `status` (ADR 0004). 2. Declare its NuGet packages under `packages` — every package id must belong to exactly one project. 3. Declare documentation under `docs`: @@ -261,8 +264,8 @@ Documentation is owned by each product repository and presented through this portal. The aggregation (`src/lib/docs/aggregate.ts`) fetches each project's docs (in-repo `docs/`, GitHub wikis via a shallow clone, or the README), then: -- injects front matter (title, description, owners, status, last review date, - source repository, edit URL), +- injects front matter (title, description, owners, status, the experimental flag, + last review date, source repository, edit URL), - selects the page served at `/docs//` from `docs.rootPage`, otherwise the conventional `index.md`/`Home.md`/`readme.md`, otherwise the repository README when `readmeAsIndex: true`, @@ -337,7 +340,7 @@ auditing the ones already published (ADR 0003): | Task | Agent | Skills | | ---- | ----- | ------ | | Add and integrate a repository | `.agents/agents/catalogue-onboarder.agent.md` | `add-catalogue-project`, `project-manifest-reference`, `docs-aggregation-rules`, `use-case-authoring`, `github-repo-metadata` | -| Audit and repair `projects.yml` | `.agents/agents/catalogue-auditor.agent.md` | `audit-catalogue-projects`, plus the reference skills | +| Audit and repair the catalogue | `.agents/agents/catalogue-auditor.agent.md` | `audit-catalogue-projects`, plus the reference skills | `AGENTS.md` holds the repository context and the mandatory rules, `.github/copilot-instructions.md` the always-on subset, and `.agents/prompts/` provides `add-project.prompt.md` and @@ -347,8 +350,10 @@ The deterministic half of the guarantee is `just check-projects` (`src/scripts/check-projects.ts`), which runs inside `just validate` and fails on catalogue drift: schema failures, documentation that does not resolve, missing or malformed use cases, unpublished or duplicated packages, a `status` that contradicts the repository's archived flag or the NuGet release -channel, missing repository topics (the site's tags), and a documented project whose default branch -is not `main`. Quality observations that cannot be decided mechanically are printed as warnings. +channel, an `experimental` flag on an archived project, missing repository topics (the site's tags), +and a documented project whose default branch is not `main`. Quality observations that cannot be +decided mechanically are printed as warnings — including `experimental` paired with a stable release +channel. ## Site versioning and releases diff --git a/docs/decisions/0004-experimental-projects.md b/docs/decisions/0004-experimental-projects.md new file mode 100644 index 0000000..565e838 --- /dev/null +++ b/docs/decisions/0004-experimental-projects.md @@ -0,0 +1,93 @@ +# ADR 0004 — Mark exploratory projects with an orthogonal `experimental` flag + +- Status: accepted +- Date: 2026-09-30 + +## Context + +The catalogue's `status` field answers one question: which release channel does a project publish on, +and is it still maintained? `stable` means a stable release exists and the project is recommended; +`preview` means prereleases only; `archived` means the repository is archived and the project is no +longer developed. `src/scripts/check-projects.ts` ties that field to observable reality — the GitHub +`archived` flag and the NuGet version list — so it cannot be used as free-text marketing. + +There is a second, independent thing a reader needs to know: **intent**. A project can be entirely +exploratory — "the API, defaults and packaging will change between prereleases, and there is no +production support guarantee" — while being honestly described by `preview`: it is actively +developed, publishes prereleases, and has documentation. The first project in this situation +(`containers`, whose README opens with an explicit *Experimental* caveat) would be *described* +accurately by `preview` but not *warned about* at all. + +Adding a fourth `status` value (`experimental`) was considered and rejected. It would force one +field to answer two orthogonal questions, so the guard would have to decide what +"`experimental` with a stable NuGet version" and "`experimental` on an archived repository" mean, +and every vocabulary-enumerating surface would inherit a four-value lifecycle. The release channel +and the maturity promise genuinely are different axes, so they are modelled as different fields. + +## Decisions + +### 1. `experimental` is an optional boolean on `projects[]` + +Every catalogue record (`src/src/data/projects/.yml`, see ADR 0005) gains an optional +`experimental: true` (default `false`, applied by the loader). It is only valid on `projects[]` — +collaborations in `externalProjects.yml` are not ours to label. `status` keeps its existing +three-value vocabulary and its existing meaning; no value is added or redefined. + +### 2. The two axes collapse into one *display* status in exactly one place + +`src/src/lib/status.ts` owns `displayStatus()`, which resolves `{ status, experimental }` into +`stable | preview | experimental | archived` for presentation. The precedence is deliberate: + +- `archived` wins over everything (an archived project is never presented as an experiment); +- otherwise the `experimental` flag wins over the release channel, because the warning is the point. + +Every surface that renders a badge, filters, or labels a sidebar topic calls that helper rather than +re-deriving the rule, so the catalogue card, project page, home-page version snapshot, docs page, +docs sidebar topic and catalogue filter cannot disagree. + +### 3. `experimental: true` with `status: archived` is a schema error + +The combination is contradictory: an archived repository is not an ongoing experiment. It is +rejected by a refinement in the manifest schema (`src/src/lib/manifest/schema.ts`), so it fails at +load time for every consumer — the build, the guard, and the unit tests — with a remediation +message rather than at render time. + +### 4. `experimental` with a stable release is legal but reported + +The flag is an intent marker, not a release-channel marker, so the guard does not invent a rule such +as "experimental must not have a stable version": a project may legitimately ship `1.x` while still +being flagged as an experiment. `just check-projects` emits a **warning** (not a failure) so the +combination is a conscious decision rather than an oversight. + +### 5. Experimental projects are surfaced, not hidden + +Only `archived` removes a project from the catalogue, the documentation portal, the sidebar topics +and the per-project LLM bundles. An experimental project with documentation is aggregated, listed, +indexed by `/use-cases/`, included in `/releases/`, and gets its `/_llms-txt/.txt` bundle like +any other project. Discoverability with an explicit warning is the point of the flag. + +### 6. The flag is presented on the project page and on every documentation page + +A distinct badge (`.pv-status-experimental`) is not sufficient on its own, so the warning is also +stated in prose: the project page renders an "Experimental" notice above the content, and +`src/src/components/starlight/PageTitle.astro` renders one on every aggregated documentation page, +driven by the injected front matter. The aggregated docs mirror stays generated — the notice is +injected by the site, never hand-authored into `src/src/content/docs/**`. + +### 7. Promotion is a manifest edit + +Removing `experimental: true` (and correcting `status`) is the promotion mechanism; nothing else has +to change for a project to graduate. An experimental project is expected to end in one of the two +defined states — promoted, or `archived`. + +## Consequences + +- `status` stays a closed three-value vocabulary tied to the release channel, and the guard's + existing rules (`archived` ⇔ repository archived, `stable` ⇒ stable NuGet release) are untouched. +- Two axes create combinations that a single field could not: the schema forbids the contradictory + one and warns about the ambiguous one, rather than leaving them undefined. +- The documentation front matter carries the raw pair (`status` plus `experimental`) and not a + pre-collapsed value, so the manifest remains the only place the vocabulary is defined; the + display collapse happens in the site components through `displayStatus()`. +- Adding an experimental project still requires one concrete use case (ADR 0002 applies to every + non-archived project). diff --git a/docs/decisions/0005-one-file-per-project.md b/docs/decisions/0005-one-file-per-project.md new file mode 100644 index 0000000..2489f97 --- /dev/null +++ b/docs/decisions/0005-one-file-per-project.md @@ -0,0 +1,68 @@ +# ADR 0005 — One file per project for the catalogue manifest + +- Status: accepted +- Date: 2026-09-30 + +## Context + +ADR 0001 established a single YAML manifest, `src/src/data/projects.yml`, as the source of truth for +the catalogue. By the time this was written it held twelve projects and one collaboration in 1,037 +lines / 47 KB, and it grows by roughly 80–140 lines per project — one project per pull request. Every +addition appends to the same region of the same file, so two concurrent catalogue pull requests +conflict almost by construction, and `git log`/`blame` attribute a change to "the manifest" rather +than to a project. + +The decision recorded here is not that the single manifest was wrong. It is that the *granularity of +the file* no longer matches the granularity of the change. + +## Decisions + +### 1. `src/src/data/projects/.yml`, one file per project + +The file name is the project id. Each file holds the project record itself — no `projects:` wrapper — +so the file *is* the project. `$schema: ../../lib/manifest/schema.ts` opens the file, as before. + +### 2. Collaborations stay in one file: `src/src/data/external-projects.yml` + +`externalProjects:` remains a list in a single file. It is small, it changes rarely, and the entries +are not ours to grow. + +### 3. The loader assembles the files; the validation does not move + +`readRawManifest()` reads the directory (sorted by file name, so the result is deterministic whatever +order the filesystem reports) plus the collaborations file, and returns the same raw shape as +before (`{ projects, externalProjects }`). `parseManifest()` then validates that shape exactly as it +did, so every existing invariant — purview-dev ownership, unique ids, known relationship targets, +package uniqueness — and the `just check-projects` guard are untouched. Reading and validating stay +separate concerns, and `parseManifest()` remains the single validation entry point (still unit +testable with inline objects). + +### 4. A schema failure names the project, not the array position + +Splitting the file would otherwise degrade schema errors to `projects[3].name`, which the author has +to map back to a file. `parseManifest()` resolves the index to the record's `id`, so messages stay +`projects[].name`. + +### 5. The `$schema` hint is treated as a hint + +A project record may declare `$schema`, and the loader strips it before resolution: it is an editor +affordance, never part of the domain type. `manifestSchema.projects` is defaulted to `[]` so the +collaborations file is valid on its own. The hint is advisory — there is no editor schema wiring in +this repository, and the enforcing validation is `loadProjects()` plus `just check-projects`. + +### 6. The move was made by construction, not by re-serialisation + +The split was a text transform that removed only the list indentation, and it was proved by +comparing the fully resolved catalogue before and after the change: byte-identical. Re-serialising +the YAML would have reformatted every hand-wrapped block scalar and code sample. + +## Consequences + +- Adding a project is one new file: unconflicted in review, and correctly attributed by + `git log`/`blame`. +- ADRs 0001–0004 reference `src/src/data/projects.yml` for the manifest; read those paths as this + layout. +- Cross-file invariants (duplicate `id`, duplicate `order`, a package id declared twice) can no + longer be seen by reading one file, so the guard matters more rather than less. + `just check-projects` is the authority for them and is unchanged. +- The migration was a fixed cost (loader plus documentation references); it does not recur. diff --git a/src/astro.config.ts b/src/astro.config.ts index 89c418c..75f2e1e 100644 --- a/src/astro.config.ts +++ b/src/astro.config.ts @@ -13,6 +13,7 @@ import { readDocsManifest } from './src/lib/docs/aggregate'; import { buildProjectItems } from './src/lib/docs/sidebar'; import { loadProjects } from './src/lib/manifest/load'; import { SITE, BRAND } from './src/lib/site'; +import { displayStatus, type DisplayStatus } from './src/lib/status'; import { absoluteUrl } from './src/lib/urls'; const projects = loadProjects(); @@ -32,13 +33,22 @@ const docsSets = docsProjects.map((project) => ({ description: project.shortDescription, paths: [`docs/${project.id}/**`], })); -const previewBadge = { text: 'Preview', variant: 'caution' } as const; +/** + * Sidebar badges for the two "not for production" states. The value comes from + * the shared display status (ADR 0004), so the docs sidebar, the project page + * and the catalogue filter cannot disagree about how a project is flagged. + */ +const topicBadges: Partial> = + { + preview: { text: 'Preview', variant: 'caution' }, + experimental: { text: 'Experimental', variant: 'danger' }, + }; const sidebarTopics = [ { label: 'Documentation home', link: '/docs/' }, ...docsProjects.map((project) => ({ label: project.name, link: `/docs/${project.id}/`, - badge: project.status === 'preview' ? previewBadge : undefined, + badge: topicBadges[displayStatus(project)], items: buildProjectItems(project, docsManifest), })), ]; diff --git a/src/fixtures/github/releases/containers.json b/src/fixtures/github/releases/containers.json new file mode 100644 index 0000000..d10c256 --- /dev/null +++ b/src/fixtures/github/releases/containers.json @@ -0,0 +1,131 @@ +[ + { + "url": "https://api.github.com/repos/purview-dev/containers/releases/401620214", + "assets_url": "https://api.github.com/repos/purview-dev/containers/releases/401620214/assets", + "upload_url": "https://uploads.github.com/repos/purview-dev/containers/releases/401620214/assets{?name,label}", + "html_url": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.3", + "id": 401620214, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "node_id": "RE_kwDOU0fn4M4X8Dz2", + "tag_name": "v1.0.0-prerelease.3", + "target_commitish": "main", + "name": "v1.0.0-prerelease.3", + "draft": false, + "immutable": false, + "prerelease": true, + "created_at": "2026-10-02T07:36:52Z", + "updated_at": "2026-10-02T07:38:44Z", + "published_at": "2026-10-02T07:38:44Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/purview-dev/containers/tarball/v1.0.0-prerelease.3", + "zipball_url": "https://api.github.com/repos/purview-dev/containers/zipball/v1.0.0-prerelease.3", + "body": "## What's Changed\n* chore: add MIT license file by @kieronlanning in https://github.com/purview-dev/containers/pull/4\n* docs: updated docs by @kieronlanning in https://github.com/purview-dev/containers/pull/5\n* docs: updated docs by @kieronlanning in https://github.com/purview-dev/containers/pull/6\n\n\n**Full Changelog**: https://github.com/purview-dev/containers/compare/v1.0.0-prerelease.2...v1.0.0-prerelease.3", + "mentions_count": 1 + }, + { + "url": "https://api.github.com/repos/purview-dev/containers/releases/401397482", + "assets_url": "https://api.github.com/repos/purview-dev/containers/releases/401397482/assets", + "upload_url": "https://uploads.github.com/repos/purview-dev/containers/releases/401397482/assets{?name,label}", + "html_url": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.2", + "id": 401397482, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "node_id": "RE_kwDOU0fn4M4X7Nbq", + "tag_name": "v1.0.0-prerelease.2", + "target_commitish": "main", + "name": "v1.0.0-prerelease.2", + "draft": false, + "immutable": false, + "prerelease": true, + "created_at": "2026-10-01T22:15:33Z", + "updated_at": "2026-10-01T22:17:22Z", + "published_at": "2026-10-01T22:17:22Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/purview-dev/containers/tarball/v1.0.0-prerelease.2", + "zipball_url": "https://api.github.com/repos/purview-dev/containers/zipball/v1.0.0-prerelease.2", + "body": "## What's Changed\n* Docs update by @kieronlanning in https://github.com/purview-dev/containers/pull/2\n* Portable WSLC backend for net10.0, Testcontainers-parity connection strings, repo renamed to containers by @kieronlanning in https://github.com/purview-dev/containers/pull/3\n\n\n**Full Changelog**: https://github.com/purview-dev/containers/compare/v1.0.0-prerelease.1...v1.0.0-prerelease.2", + "mentions_count": 1 + }, + { + "url": "https://api.github.com/repos/purview-dev/containers/releases/400244584", + "assets_url": "https://api.github.com/repos/purview-dev/containers/releases/400244584/assets", + "upload_url": "https://uploads.github.com/repos/purview-dev/containers/releases/400244584/assets{?name,label}", + "html_url": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.1", + "id": 400244584, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "node_id": "RE_kwDOU0fn4M4X2z9o", + "tag_name": "v1.0.0-prerelease.1", + "target_commitish": "main", + "name": "v1.0.0-prerelease.1", + "draft": false, + "immutable": false, + "prerelease": true, + "created_at": "2026-09-30T16:38:46Z", + "updated_at": "2026-09-30T16:41:23Z", + "published_at": "2026-09-30T16:41:23Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/purview-dev/containers/tarball/v1.0.0-prerelease.1", + "zipball_url": "https://api.github.com/repos/purview-dev/containers/zipball/v1.0.0-prerelease.1", + "body": "## What's Changed\n* Develop by @kieronlanning in https://github.com/purview-dev/wsl-testcontainers/pull/1\n\n## New Contributors\n* @kieronlanning made their first contribution in https://github.com/purview-dev/wsl-testcontainers/pull/1\n\n**Full Changelog**: https://github.com/purview-dev/wsl-testcontainers/commits/v1.0.0-prerelease.1", + "mentions_count": 1 + } +] diff --git a/src/fixtures/github/repos/containers.json b/src/fixtures/github/repos/containers.json new file mode 100644 index 0000000..9a73c5b --- /dev/null +++ b/src/fixtures/github/repos/containers.json @@ -0,0 +1,198 @@ +{ + "id": 1397221344, + "node_id": "R_kgDOU0fn4A", + "name": "containers", + "full_name": "purview-dev/containers", + "private": false, + "owner": { + "login": "purview-dev", + "id": 96894084, + "node_id": "O_kgDOBcZ8hA", + "avatar_url": "https://avatars.githubusercontent.com/u/96894084?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/purview-dev", + "html_url": "https://github.com/purview-dev", + "followers_url": "https://api.github.com/users/purview-dev/followers", + "following_url": "https://api.github.com/users/purview-dev/following{/other_user}", + "gists_url": "https://api.github.com/users/purview-dev/gists{/gist_id}", + "starred_url": "https://api.github.com/users/purview-dev/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/purview-dev/subscriptions", + "organizations_url": "https://api.github.com/users/purview-dev/orgs", + "repos_url": "https://api.github.com/users/purview-dev/repos", + "events_url": "https://api.github.com/users/purview-dev/events{/privacy}", + "received_events_url": "https://api.github.com/users/purview-dev/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/purview-dev/containers", + "description": "Testcontainers-style library for .NET (net10.0): run throwaway Linux containers in integration tests on Microsoft WSL Containers (WSLC) with no Docker install, or on Docker. Add one package, write your test code once: zero configuration between Windows dev (WSLC) and Linux CI (Docker). Seven service modules.", + "fork": false, + "url": "https://api.github.com/repos/purview-dev/containers", + "forks_url": "https://api.github.com/repos/purview-dev/containers/forks", + "keys_url": "https://api.github.com/repos/purview-dev/containers/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/purview-dev/containers/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/purview-dev/containers/teams", + "hooks_url": "https://api.github.com/repos/purview-dev/containers/hooks", + "issue_events_url": "https://api.github.com/repos/purview-dev/containers/issues/events{/number}", + "events_url": "https://api.github.com/repos/purview-dev/containers/events", + "assignees_url": "https://api.github.com/repos/purview-dev/containers/assignees{/user}", + "branches_url": "https://api.github.com/repos/purview-dev/containers/branches{/branch}", + "tags_url": "https://api.github.com/repos/purview-dev/containers/tags", + "blobs_url": "https://api.github.com/repos/purview-dev/containers/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/purview-dev/containers/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/purview-dev/containers/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/purview-dev/containers/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/purview-dev/containers/statuses/{sha}", + "languages_url": "https://api.github.com/repos/purview-dev/containers/languages", + "stargazers_url": "https://api.github.com/repos/purview-dev/containers/stargazers", + "contributors_url": "https://api.github.com/repos/purview-dev/containers/contributors", + "subscribers_url": "https://api.github.com/repos/purview-dev/containers/subscribers", + "subscription_url": "https://api.github.com/repos/purview-dev/containers/subscription", + "commits_url": "https://api.github.com/repos/purview-dev/containers/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/purview-dev/containers/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/purview-dev/containers/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/purview-dev/containers/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/purview-dev/containers/contents/{+path}", + "compare_url": "https://api.github.com/repos/purview-dev/containers/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/purview-dev/containers/merges", + "archive_url": "https://api.github.com/repos/purview-dev/containers/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/purview-dev/containers/downloads", + "issues_url": "https://api.github.com/repos/purview-dev/containers/issues{/number}", + "pulls_url": "https://api.github.com/repos/purview-dev/containers/pulls{/number}", + "milestones_url": "https://api.github.com/repos/purview-dev/containers/milestones{/number}", + "notifications_url": "https://api.github.com/repos/purview-dev/containers/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/purview-dev/containers/labels{/name}", + "releases_url": "https://api.github.com/repos/purview-dev/containers/releases{/id}", + "deployments_url": "https://api.github.com/repos/purview-dev/containers/deployments", + "created_at": "2026-09-30T06:52:40Z", + "updated_at": "2026-10-02T07:36:57Z", + "pushed_at": "2026-10-02T07:38:43Z", + "git_url": "git://github.com/purview-dev/containers.git", + "ssh_url": "git@github.com:purview-dev/containers.git", + "clone_url": "https://github.com/purview-dev/containers.git", + "svn_url": "https://github.com/purview-dev/containers", + "homepage": "https://purview.dev/projects/containers/", + "size": 820, + "stargazers_count": 1, + "watchers_count": 1, + "language": "C#", + "has_issues": true, + "has_projects": false, + "has_downloads": false, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "has_pull_requests": true, + "pull_request_creation_policy": "all", + "topics": [ + "azurite", + "containers", + "csharp", + "docker", + "dotnet", + "integration-testing", + "mysql", + "nats", + "nuget", + "postgresql", + "rabbitmq", + "redis", + "sql-server", + "testcontainers", + "testing", + "windows", + "wsl", + "wsl-containers", + "wslc" + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "main", + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "temp_clone_token": "", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": true, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE", + "custom_properties": {}, + "organization": { + "login": "purview-dev", + "id": 96894084, + "node_id": "O_kgDOBcZ8hA", + "avatar_url": "https://avatars.githubusercontent.com/u/96894084?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/purview-dev", + "html_url": "https://github.com/purview-dev", + "followers_url": "https://api.github.com/users/purview-dev/followers", + "following_url": "https://api.github.com/users/purview-dev/following{/other_user}", + "gists_url": "https://api.github.com/users/purview-dev/gists{/gist_id}", + "starred_url": "https://api.github.com/users/purview-dev/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/purview-dev/subscriptions", + "organizations_url": "https://api.github.com/users/purview-dev/orgs", + "repos_url": "https://api.github.com/users/purview-dev/repos", + "events_url": "https://api.github.com/users/purview-dev/events{/privacy}", + "received_events_url": "https://api.github.com/users/purview-dev/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "disabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_ai_detection": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + }, + "secret_scanning_delegated_alert_dismissal": { + "status": "disabled" + }, + "secret_scanning_delegated_bypass": { + "status": "disabled" + } + }, + "network_count": 0, + "subscribers_count": 0 +} diff --git a/src/fixtures/nuget/purview.containers.azurite.json b/src/fixtures/nuget/purview.containers.azurite.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.azurite.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.core.json b/src/fixtures/nuget/purview.containers.core.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.core.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.docker.json b/src/fixtures/nuget/purview.containers.docker.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.docker.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.json b/src/fixtures/nuget/purview.containers.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.mssql.json b/src/fixtures/nuget/purview.containers.mssql.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.mssql.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.mysql.json b/src/fixtures/nuget/purview.containers.mysql.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.mysql.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.nats.json b/src/fixtures/nuget/purview.containers.nats.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.nats.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.postgresql.json b/src/fixtures/nuget/purview.containers.postgresql.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.postgresql.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.rabbitmq.json b/src/fixtures/nuget/purview.containers.rabbitmq.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.rabbitmq.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.redis.json b/src/fixtures/nuget/purview.containers.redis.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.redis.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/purview.containers.wsl.json b/src/fixtures/nuget/purview.containers.wsl.json new file mode 100644 index 0000000..b8a278f --- /dev/null +++ b/src/fixtures/nuget/purview.containers.wsl.json @@ -0,0 +1,6 @@ +{ + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Azurite.json b/src/fixtures/nuget/search/Purview.Containers.Azurite.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Azurite.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Core.json b/src/fixtures/nuget/search/Purview.Containers.Core.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Core.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Docker.json b/src/fixtures/nuget/search/Purview.Containers.Docker.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Docker.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.MsSql.json b/src/fixtures/nuget/search/Purview.Containers.MsSql.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.MsSql.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.MySql.json b/src/fixtures/nuget/search/Purview.Containers.MySql.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.MySql.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Nats.json b/src/fixtures/nuget/search/Purview.Containers.Nats.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Nats.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.PostgreSql.json b/src/fixtures/nuget/search/Purview.Containers.PostgreSql.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.PostgreSql.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.RabbitMq.json b/src/fixtures/nuget/search/Purview.Containers.RabbitMq.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.RabbitMq.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Redis.json b/src/fixtures/nuget/search/Purview.Containers.Redis.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Redis.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.Wsl.json b/src/fixtures/nuget/search/Purview.Containers.Wsl.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.Wsl.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/nuget/search/Purview.Containers.json b/src/fixtures/nuget/search/Purview.Containers.json new file mode 100644 index 0000000..b6b988b --- /dev/null +++ b/src/fixtures/nuget/search/Purview.Containers.json @@ -0,0 +1,8 @@ +{ + "@context": { + "@vocab": "http://schema.nuget.org/schema#", + "@base": "https://api.nuget.org/v3/registration5-semver1/" + }, + "totalHits": 0, + "data": [] +} diff --git a/src/fixtures/releases/index.json b/src/fixtures/releases/index.json index adf130d..163e222 100644 --- a/src/fixtures/releases/index.json +++ b/src/fixtures/releases/index.json @@ -312,6 +312,39 @@ "defaultBranch": "main", "updatedAt": "2026-09-30T14:05:36Z", "hasDiscussions": false + }, + "purview-dev/containers": { + "name": "containers", + "fullName": "purview-dev/containers", + "description": "Testcontainers-style library for .NET (net10.0): run throwaway Linux containers in integration tests on Microsoft WSL Containers (WSLC) with no Docker install, or on Docker. Add one package, write your test code once: zero configuration between Windows dev (WSLC) and Linux CI (Docker). Seven service modules.", + "archived": false, + "homepage": "https://purview.dev/projects/containers/", + "topics": [ + "azurite", + "containers", + "csharp", + "docker", + "dotnet", + "integration-testing", + "mysql", + "nats", + "nuget", + "postgresql", + "rabbitmq", + "redis", + "sql-server", + "testcontainers", + "testing", + "windows", + "wsl", + "wsl-containers", + "wslc" + ], + "stargazersCount": 1, + "openIssuesCount": 0, + "defaultBranch": "main", + "updatedAt": "2026-10-02T07:36:57Z", + "hasDiscussions": false } }, "releases": { @@ -1442,6 +1475,35 @@ "publishedAt": "2026-09-30T12:35:07Z", "htmlUrl": "https://github.com/purview-dev/results/releases/tag/v1.0.0-prerelease.1" } + ], + "purview-dev/containers": [ + { + "tagName": "v1.0.0-prerelease.3", + "name": "v1.0.0-prerelease.3", + "body": "## What's Changed\n* chore: add MIT license file by @kieronlanning in https://github.com/purview-dev/containers/pull/4\n* docs: updated docs by @kieronlanning in https://github.com/purview-dev/containers/pull/5\n* docs: updated docs by @kieronlanning in https://github.com/purview-dev/containers/pull/6\n\n\n**Full Changelog**: https://github.com/purview-dev/containers/compare/v1.0.0-prerelease.2...v1.0.0-prerelease.3", + "prerelease": true, + "draft": false, + "publishedAt": "2026-10-02T07:38:44Z", + "htmlUrl": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.3" + }, + { + "tagName": "v1.0.0-prerelease.2", + "name": "v1.0.0-prerelease.2", + "body": "## What's Changed\n* Docs update by @kieronlanning in https://github.com/purview-dev/containers/pull/2\n* Portable WSLC backend for net10.0, Testcontainers-parity connection strings, repo renamed to containers by @kieronlanning in https://github.com/purview-dev/containers/pull/3\n\n\n**Full Changelog**: https://github.com/purview-dev/containers/compare/v1.0.0-prerelease.1...v1.0.0-prerelease.2", + "prerelease": true, + "draft": false, + "publishedAt": "2026-10-01T22:17:22Z", + "htmlUrl": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.2" + }, + { + "tagName": "v1.0.0-prerelease.1", + "name": "v1.0.0-prerelease.1", + "body": "## What's Changed\n* Develop by @kieronlanning in https://github.com/purview-dev/wsl-testcontainers/pull/1\n\n## New Contributors\n* @kieronlanning made their first contribution in https://github.com/purview-dev/wsl-testcontainers/pull/1\n\n**Full Changelog**: https://github.com/purview-dev/wsl-testcontainers/commits/v1.0.0-prerelease.1", + "prerelease": true, + "draft": false, + "publishedAt": "2026-09-30T16:41:23Z", + "htmlUrl": "https://github.com/purview-dev/containers/releases/tag/v1.0.0-prerelease.1" + } ] }, "packages": { @@ -2030,6 +2092,83 @@ "1.0.0-prerelease.1", "1.0.0-prerelease.2" ] + }, + "Purview.Containers": { + "id": "Purview.Containers", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Core": { + "id": "Purview.Containers.Core", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Wsl": { + "id": "Purview.Containers.Wsl", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Docker": { + "id": "Purview.Containers.Docker", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.PostgreSql": { + "id": "Purview.Containers.PostgreSql", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Redis": { + "id": "Purview.Containers.Redis", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.MsSql": { + "id": "Purview.Containers.MsSql", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.MySql": { + "id": "Purview.Containers.MySql", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.RabbitMq": { + "id": "Purview.Containers.RabbitMq", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Azurite": { + "id": "Purview.Containers.Azurite", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] + }, + "Purview.Containers.Nats": { + "id": "Purview.Containers.Nats", + "versions": [ + "1.0.0-prerelease.2", + "1.0.0-prerelease.3" + ] } }, "packageSearch": { @@ -2121,6 +2260,17 @@ "Purview.Results.SourceGenerator": null, "Purview.Results.AspNetCore": null, "Purview.Results.ZodSharp": null, - "Purview.Results.ZodSharp.AspNetCore": null + "Purview.Results.ZodSharp.AspNetCore": null, + "Purview.Containers": null, + "Purview.Containers.Core": null, + "Purview.Containers.Wsl": null, + "Purview.Containers.Docker": null, + "Purview.Containers.PostgreSql": null, + "Purview.Containers.Redis": null, + "Purview.Containers.MsSql": null, + "Purview.Containers.MySql": null, + "Purview.Containers.RabbitMq": null, + "Purview.Containers.Azurite": null, + "Purview.Containers.Nats": null } } diff --git a/src/scripts/check-projects.ts b/src/scripts/check-projects.ts index c4bd6f3..a8c73fd 100644 --- a/src/scripts/check-projects.ts +++ b/src/scripts/check-projects.ts @@ -16,7 +16,8 @@ import { OWNER } from '../src/lib/site'; /** * Deterministic catalogue guard (ADR 0003). * - * Proves the invariants of `src/src/data/projects.yml` against the generated + * Proves the invariants of the catalogue (`src/src/data/projects/*.yml` plus + * `src/src/data/external-projects.yml`) against the generated * docs mirror/cache and the release cache/fixtures. Offline only: it never * performs network I/O, so it can run inside `just validate` and CI. * @@ -149,6 +150,28 @@ function checkDocs( return pages; } +/** + * The `experimental` flag is an intent marker, so it stays orthogonal to the + * release channel (ADR 0004). The one combination that is legal but worth a + * second look is a stable release: the flag says "exploratory" while the + * channel says "recommended for use". The contradictory pairing + * (`experimental` with `status: archived`) is rejected by the manifest schema + * before this runs, so it needs no rule here. + */ +function checkExperimental(project: ResolvedProject): void { + if (!project.experimental || project.status !== 'stable') { + return; + } + warn({ + project: project.id, + field: 'experimental', + observed: 'true with status: stable', + expected: 'an experimental project normally publishes prereleases only', + remediation: + 'confirm the intent; if the project is still an experiment, `status: preview` is the honest release channel.', + }); +} + /** Package declarations must resolve to published NuGet versions. */ function checkPackages(project: ResolvedProject, data: ReleaseCacheData): void { if (project.packages.length > 1) { @@ -183,7 +206,7 @@ function checkPackages(project: ResolvedProject, data: ReleaseCacheData): void { observed: describeVersions(0), expected: 'at least one published version on NuGet', remediation: - 'publish the package before declaring it, or correct the package id in projects.yml.', + 'publish the package before declaring it, or correct the package id in the project file.', }); } } @@ -225,7 +248,7 @@ function checkRepoMetadata(project: ResolvedProject, data: ReleaseCacheData): vo observed: project.status, expected: 'archived', remediation: - 'the GitHub repository is archived; set `status: archived` (and `supersededBy`).', + 'the GitHub repository is archived; set `status: archived`, remove `experimental: true` if present, and add `supersededBy` when a successor exists.', }); } if (!repo.archived && project.status === 'archived') { @@ -233,7 +256,7 @@ function checkRepoMetadata(project: ResolvedProject, data: ReleaseCacheData): vo project: project.id, field: 'status', observed: 'archived', - expected: 'stable or preview', + expected: 'stable, preview or experimental', remediation: 'the GitHub repository is active; correct `status`.', }); } @@ -293,7 +316,7 @@ export function checkProjects(): { errors: Finding[]; warnings: Finding[] } { project: '(manifest)', field: 'schema', observed: 'invalid manifest', - expected: 'a schema-valid projects.yml', + expected: 'a schema-valid project record', remediation: error.message, }); return { errors, warnings }; @@ -346,6 +369,7 @@ export function checkProjects(): { errors: Finding[]; warnings: Finding[] } { for (const project of projects) { const docsPages = checkDocs(project, docsManifest); checkUseCases(project, docsPages); + checkExperimental(project); if (data) { checkPackages(project, data); checkRepoMetadata(project, data); diff --git a/src/src/components/ProjectCard.astro b/src/src/components/ProjectCard.astro index f89a226..d3d4968 100644 --- a/src/src/components/ProjectCard.astro +++ b/src/src/components/ProjectCard.astro @@ -4,6 +4,7 @@ import { buildBadge, nugetDownloadsBadge, nugetVersionBadge, actionsUrl } from ' import { projectRepoEnrichment } from '~/lib/releases/repo'; import { getReleaseIndex } from '~/lib/releases/runtime'; import type { ResolvedProject } from '~/lib/manifest/load'; +import { displayStatus } from '~/lib/status'; import { nugetPackageUrl, withBase } from '~/lib/urls'; interface Props { @@ -13,10 +14,11 @@ interface Props { } const { project, showBadges = true } = Astro.props; +const status = displayStatus(project); const { tags } = projectRepoEnrichment(project, getReleaseIndex().data); const primaryPackage = project.packages.find((pkg) => pkg.primary) ?? project.packages[0]; const badges = - project.status === 'archived' ? + status === 'archived' ? [] : [ ...(primaryPackage ? @@ -45,7 +47,7 @@ const badges =

{project.name}

- +

{project.shortDescription}

diff --git a/src/src/components/StatusBadge.astro b/src/src/components/StatusBadge.astro index de7735e..a116fd2 100644 --- a/src/src/components/StatusBadge.astro +++ b/src/src/components/StatusBadge.astro @@ -1,12 +1,14 @@ --- +import { DISPLAY_STATUS_LABELS, type DisplayStatus } from '~/lib/status'; + interface Props { - status: 'stable' | 'preview' | 'archived'; + status: DisplayStatus; /** Extra classes, e.g. `shrink-0` when the badge shares a row with a title. */ class?: string; } const { status, class: className } = Astro.props; -const label = { stable: 'Stable', preview: 'Preview', archived: 'Archived' }[status]; +const label = DISPLAY_STATUS_LABELS[status]; --- {label} \ No newline at end of file diff --git a/src/src/components/starlight/PageTitle.astro b/src/src/components/starlight/PageTitle.astro index 16b74f4..40cbfd0 100644 --- a/src/src/components/starlight/PageTitle.astro +++ b/src/src/components/starlight/PageTitle.astro @@ -2,6 +2,8 @@ import StatusBadge from '~/components/StatusBadge.astro'; import { isStale, STALE_AFTER_DAYS } from '~/lib/docs/staleness'; import { LLMS_LINK_ATTRS } from '~/lib/llms'; +import { displayStatus } from '~/lib/status'; + import { withBase } from '~/lib/urls'; const { entry } = Astro.locals.starlightRoute; @@ -9,6 +11,9 @@ const data = entry?.data; const lastReviewed = typeof data?.lastReviewed === 'string' ? data.lastReviewed : undefined; const stale = lastReviewed ? isStale(lastReviewed) : false; const tags = Array.isArray(data?.tags) ? data.tags : []; +const experimental = data?.experimental === true; +const status = data?.status ? displayStatus({ status: data.status, experimental }) : undefined; + // Project overview pages surface the per-project `llms.txt` bundle emitted by // the `customSets` option in `astro.config.ts`. Starlight resolves an index // page to its directory id (`docs/`), while the content collection — @@ -28,9 +33,9 @@ const llmsHref = data && (
{ - data.status && ( + status && ( - + ) } @@ -96,6 +101,15 @@ const llmsHref = ) } +{ + experimental && ( +

+ Experimental. This project is an experiment: the public API, defaults and + packaging can change between prereleases, and there is no production support guarantee. +

+ ) +} +