Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .agents/skills/audit-catalogue-projects/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Related: `project-manifest-reference` (fields and vocabularies), `docs-aggregati
- The aggregated page count is non-zero and the landing page is the page a reader should land on.
- Archived projects that still declare `docs` are intentional (they are excluded from the portal).
- Documentation is not duplicated here: this repository only holds the mirror.
- Pages moved in the repository's history have a redirect — in its `_redirects.yml` or the entry's
`docs.redirects` (ADR 0013). Diagrams are LikeC4/Mermaid pairs, not ASCII art or Mermaid in a
`text` fence. The consumer assessment script reports both against a clone (below); file the
repository-side fixes as `upstream` items, since they belong in the product repository.

### Stability

Expand Down Expand Up @@ -112,6 +116,10 @@ gh api "repos/purview-dev/<repo>/git/trees/main?recursive=1" --jq '[.tree[].path
gh api "repos/purview-dev/<repo>/releases?per_page=10" --jq '.[] | {tag_name, prerelease}'
curl.exe -s "https://api.nuget.org/v3-flatcontainer/<packageid>/index.json"

# Repository-side integration (docs layout, diagrams, redirects, skills) against a fresh clone
git clone --depth 300 https://github.com/purview-dev/<repo>.git <scratch>/<repo>
node skills/purview-site-assessment/scripts/assess.mjs <scratch>/<repo> --catalogue src/src/data/projects/<id>.yml

# Aggregated result
node -e "const m=require('./src/.cache/docs/index.json');for(const p of m.projects)console.log(p.projectId,p.pages.length)"
```
Expand Down
56 changes: 47 additions & 9 deletions .agents/skills/docs-aggregation-rules/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ Additional transformations: GitHub alert blockquotes become Starlight asides; re
documents are rewritten to portal URLs; **relative** images are rewritten to GitHub raw URLs while
**absolute** image URLs pass through untouched — a registry badge such as
`![Purview.BuildSdk](https://img.shields.io/nuget/v/Purview.BuildSdk)` keeps its own URL, so a page can
show a live version without stating a number that rots; duplicate top-level H1s are demoted to keep one
title per document.
show a live version without stating a number that rots; the first top-level H1 is removed from the body
(it becomes the front-matter `title`, which the page header renders) and any later H1 is demoted to `##`,
so each page shows its title once.

## What the site does with it

Expand All @@ -174,13 +175,50 @@ title per document.
from `/llms.txt` and from the project page.
- Staleness: a page is flagged when `lastReviewed` is older than 365 days
(`src/src/lib/docs/staleness.ts`).
- A ```` ```likec4 ```` fence in a mirrored page stays **source**, not a diagram:
the whole `docs` collection is relaxed through astroc4's `validation:
{ collections: ['docs'] }` (`astro.config.ts`). Such a page was written for
another renderer, so its fence is often a partial snippet; astroc4 logs a
warning and leaves it as a highlighted code block instead of failing the build.
Pages the site authors (`src/pages/**`) are not in the collection, so they keep
strict validation.
- A ```` ```likec4 ```` fence in a mirrored page is rendered when it compiles, and
stays **source** when it does not: the whole `docs` collection is relaxed through
astroc4's `validation: { collections: ['docs'] }` (`astro.config.ts`). Such a page
was written for another renderer, so its fence is often a partial snippet; astroc4
logs a warning and leaves it as a highlighted code block instead of failing the
build. Pages the site authors (`src/pages/**`) are not in the collection, so they
keep strict validation.

## Diagram pairs (ADR 0013)

A product page pairs a GitHub rendering with a LikeC4 view:

````markdown
<!-- purview:likec4 file="./diagrams/the-loop.c4" view="theLoop" title="The loop" -->
```mermaid
flowchart LR
resume --> next
```
[LikeC4 model](./diagrams/the-loop.c4)
<!-- /purview:likec4 -->
````

`buildPage` runs `applyDiagramBlocks` (`src/lib/docs/diagrams.ts`) on the raw source before anything
else: the `.c4` file is read from the repository at the channel's ref (resolved relative to the page,
or `/`-rooted at the repository root), and the whole block becomes a ```` ```likec4 ```` fence holding
the model, with `view`, `project`, `title`, `description`, `height`, `collapsible` and `defaultOpen`
passed through. A missing model, an undeclared view or a malformed marker keeps the Mermaid and logs
`docs <id>: <page> line <n>: …`. Markers inside code fences are ignored.

## Repository redirects (`_redirects.yml`, ADR 0013)

`<docs root>/_redirects.yml` (the wiki root for `source: wiki`) holds `previous: current` pairs, either
under `redirects:` or as a bare map; each side is a slug or a Markdown path, reduced with `docSlug`.
It is read per channel and stored as `channels[].redirects` in `src/.cache/docs/index.json`;
`docsPageRedirects(projects, manifest)` writes each channel's pairs from that channel's base, after the
catalogue's `docs.redirects` (which wins on a conflict). An invalid file is a `DocsValidationError`; a
destination that is not a page, or a source that still is, is an advisory line in the sync log.

## Consumer skills

`skills/` at the repository root is what product repositories install
(`npx skills add purview-dev/purview-dev`): `purview-site-integration`, `purview-site-docs` and
`purview-site-assessment` (with `scripts/assess.mjs`). They restate these rules for authors, so a change
to slugs, diagram pairs or redirects here changes them in the same pull request.

## Verifying

Expand Down
5 changes: 4 additions & 1 deletion .agents/skills/project-manifest-reference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Adding a value to any of these lists is a schema change and requires an ADR. `ex

| `source` | Fields | Behaviour |
| --- | --- | --- |
| `github-path` | `path` (required), plus `rootPage`, `readmeAsIndex`, `order`, `exclude`, `channels` | Walks `<path>/**/*.md` at each channel's ref (default branch when no `channels`) |
| `github-path` | `path` (required), plus `rootPage`, `readmeAsIndex`, `order`, `exclude`, `channels`, `redirects` | Walks `<path>/**/*.md` at each channel's ref (default branch when no `channels`) |
| `wiki` | `rootPage`, `readmeAsIndex`, `order`, `exclude` | Shallow-clones `<repo>.wiki.git`; `_Sidebar.md` orders pages, `_Footer.md` is skipped. Single channel only |
| `readme` | none | Publishes `README.md` alone as `/docs/<id>/`. Single channel only |

Expand All @@ -80,6 +80,9 @@ Adding a value to any of these lists is a schema change and requires an ADR. `ex
- `readmeAsIndex` uses the repository `README.md` as the landing page and cannot be combined with
`rootPage`; it aliases `readme` to `index`.
- `order` is an explicit list of page slugs, applied before the `_Sidebar.md` order.
- `redirects` maps moved slugs (`previous: current`) to keep old URLs alive on every channel that
publishes the tree. Prefer the repository's own `_redirects.yml` in its docs root (ADR 0013), which
lands with the move and is read per channel; the catalogue's pairs win when both name a slug.
- If `docs` is set but the aggregation yields no pages, `just data-sync` fails loudly.

## `docs.channels` — live and prerelease documentation (ADR 0009)
Expand Down
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ package is `src/`.
npm for a package with `registry: npm`). Apply the machine-applicable ones with `just fix-projects`
(dry run; pass `--write` to edit).
- Keep documentation where it lives (the product repository). This site aggregates it at build time.
Product repositories own their diagram pairs (`purview:likec4` blocks) and their page moves
(`_redirects.yml` in the docs root) — ADR 0013.
- `skills/` at the root is for **product repositories** (`npx skills add purview-dev/purview-dev`);
`.agents/` is for work on this site. When the docs rules in `src/src/lib/docs/` change, update
`skills/purview-site-docs` and `skills/purview-site-assessment/scripts/assess.mjs` with them.
- Keep the package version hydration intact (ADR 0008, ADR 0012): the build-time version renders
first, the browser only ever raises it, and the result is stored in `localStorage` for six hours.
Do not move any other data to the client.
Expand Down Expand Up @@ -51,5 +56,6 @@ package is `src/`.
`.agents/skills/add-catalogue-project/SKILL.md` (the procedure).
- Auditing/fixing the catalogue → `.agents/agents/catalogue-auditor.agent.md` and
`.agents/skills/audit-catalogue-projects/SKILL.md`.
- Assessing a product repository's integration → `skills/purview-site-assessment/SKILL.md`.
- Any other change → `.agents/` holds the manifest reference, docs-aggregation rules, use-case
authoring rules, repo-metadata guidance, and the validation loop.
23 changes: 18 additions & 5 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ on:
required: false
type: string
default: ""
# Called by upstream-refresh.yml when a catalogue project's repository or
# packages changed since the deployed build (ADR 0014). That workflow also
# owns the `purview-site-rebuild` repository dispatch, so a release elsewhere
# waits for its packages to publish before this runs.
workflow_call:
inputs:
pages-base:
required: false
type: string
default: ""
schedule:
# Weekly refresh of release information from GitHub and NuGet.
# Weekly refresh of release information from GitHub and NuGet (download
# counts and other figures the nightly upstream check does not compare).
- cron: "17 4 * * 1"
repository_dispatch:
# Fired by a release workflow in another purview-dev repository. The
# expected payload contract is documented in the README.
types: [purview-site-rebuild]

permissions:
contents: read
Expand Down Expand Up @@ -145,6 +152,12 @@ jobs:
mkdir -p src/dist/_discovery
cp .discovery-capture/previous.json src/dist/_discovery/previous.json

- name: Record the upstream state this build was made from
# Published as /_discovery/upstream.json. The nightly upstream check
# compares the catalogue repositories and packages with it and skips the
# rebuild when nothing moved (ADR 0014).
run: bun run upstream:state --write dist/_discovery/upstream.json

- name: Configure Pages
# actions/configure-pages v6.0.0
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d
Expand Down
85 changes: 85 additions & 0 deletions .github/workflows/upstream-refresh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Refresh from upstream projects

# Rebuilds and redeploys the site when a catalogue project changed upstream —
# a commit on its default branch, a new tag, or a new package version — and
# not otherwise (ADR 0014). The catalogue projects are only read: nothing here
# needs a workflow, secret or setting in their repositories.
#
# - Nightly, the upstream state is compared with the state the deployed site
# recorded at build time (/_discovery/upstream.json).
# - A `purview-site-rebuild` repository dispatch (sent by a release workflow
# elsewhere, see the README) starts the same check after a 15-minute settle
# period, so the packages that release published are indexed before the build
# reads them. A newer dispatch cancels a run that is still settling, so a burst
# of releases produces one build.

on:
schedule:
- cron: "23 2 * * *"
repository_dispatch:
types: [purview-site-rebuild]
workflow_dispatch:
inputs:
force:
description: Rebuild even when nothing changed upstream.
required: false
type: boolean
default: false

permissions:
contents: read

jobs:
settle:
runs-on: ubuntu-latest
# Only one run may wait at a time; a newer trigger cancels the waiting one
# (and with it that run's check and build), so the newest event wins.
concurrency:
group: upstream-refresh-settle
cancel-in-progress: true
steps:
- name: Wait for upstream publishing to settle
if: github.event_name == 'repository_dispatch'
env:
SOURCE: ${{ github.event.client_payload.repository }}
VERSION: ${{ github.event.client_payload.version }}
run: |
echo "Rebuild requested by ${SOURCE:-an upstream repository} ${VERSION}; waiting 15 minutes for package publishing."
sleep 900

check:
needs: settle
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.compare.outputs.changed }}
steps:
- name: Checkout
# actions/checkout v7.0.1
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

- name: Install Bun
# oven-sh/setup-bun v2.2.0
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.4

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Compare upstream with the deployed build
id: compare
env:
SITE_URL: ${{ vars.SITE_URL }}
run: |
ORIGIN="${SITE_URL:-https://purview.dev}"
bun run upstream:state --compare "$ORIGIN/_discovery/upstream.json"

deploy:
needs: check
if: needs.check.outputs.changed == 'true' || inputs.force == true
permissions:
contents: read
pages: write
id-token: write
uses: ./.github/workflows/deploy.yml
secrets: inherit
19 changes: 17 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ prints advisory observations (which never fail the build) when a project's decla
old→new slug in `docs.redirects`, which the build turns into redirects on **every** channel that
publishes the tree — the default channel at `/docs/<id>/` and the prerelease one at
`/docs/<id>/prerelease/`, each pair written only once its destination exists; a redirect never
shadows a real page.
shadows a real page. A repository may instead record its own moves in `_redirects.yml` at its
docs root (ADR 0013); the sync reads it per channel and the catalogue's `docs.redirects` wins on
a conflict. An unreadable `_redirects.yml` fails the sync — fix it upstream, never ignore it.
- Diagrams in product docs are **pairs** (ADR 0013): a ```` ```mermaid ```` fence for GitHub inside
`<!-- purview:likec4 file="…" view="…" -->` … `<!-- /purview:likec4 -->`. The sync replaces the
block with a ```` ```likec4 ```` fence built from the repository's `.c4` file; a missing model or
view keeps the Mermaid and is logged, never a failure.
- Agent Skills a project publishes are declared in `skills:` (ADR 0010) at
`skills/<name>/SKILL.md`; the sync mirrors the whole directory to
`/skills/<id>/<name>/` and each project page renders the same install block
Expand Down Expand Up @@ -158,16 +164,25 @@ Do not assume this file contains everything; consult `.agents/` while planning.
- `.agents/skills/site-validation-loop/SKILL.md` — the checks, and how to reproduce failures.
- `.agents/prompts/add-project.prompt.md`, `.agents/prompts/audit-projects.prompt.md`.

`skills/` (repository root) is different: it holds the **consumer** skills that product repositories
install with `npx skills add purview-dev/purview-dev` — `purview-site-integration`,
`purview-site-docs` and `purview-site-assessment` (ADR 0013). They restate the docs-aggregation rules
for authors; change them in the same pull request as `src/src/lib/docs/`. When onboarding or auditing a
project, run `node skills/purview-site-assessment/scripts/assess.mjs <checkout> --catalogue
src/src/data/projects/<id>.yml` against a clone of its repository.

## Key entry points

| Concern | Location |
| --- | --- |
| Catalogue manifest | `src/src/data/projects/<id>.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`) |
| Docs aggregation | `src/src/lib/docs/aggregate.ts` (+ `frontmatter`, `links`, `sidebar`, `staleness`, `diagrams`, `redirect-file`) |
| Consumer skills for product repositories | `skills/purview-site-{integration,docs,assessment}/` |
| Release data | `src/src/lib/releases/*`, `src/scripts/fetch-releases.ts` |
| Mirrored project assets | `src/scripts/sync-assets.ts` (`assets:` in the manifest) |
| Pages | `src/src/pages/{index,about,use-cases,docs,projects,releases}/` |
| Live examples | `src/src/pages/examples/<project-id>.mdx` (linked from the project page automatically) |
| Validation | `justfile`, `src/scripts/{check-links,check-generated,check-assets,check-projects}.ts` |
| Upstream-triggered rebuilds | `.github/workflows/upstream-refresh.yml`, `src/scripts/upstream-state.ts`, `src/src/lib/upstream.ts` (ADR 0014) |
| Architecture decisions | `docs/decisions/*.md` |
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ purview-dev/ # workspace root
├── justfile # task runner (runs in the src/ package)
├── AGENTS.md # repository context and agent rules (ADR 0003)
├── .agents/ # agents, skills, and prompts for catalogue/docs work
├── skills/ # skills product repositories install (npx skills add purview-dev/purview-dev)
├── docs/decisions/ # architectural decision records
└── src/ # the Astro site package (@purview-dev/website)
├── assets/branding/ # approved branding sources (never modified)
Expand Down Expand Up @@ -397,6 +398,21 @@ skills:
`just check-projects` fails when a declared skill has no mirror, and
`just check-generated` fails when one is missing from the build.

### Skills for product repositories

This repository also publishes skills, from `skills/` at its root, for agents
working in the product repositories (ADR 0013):

```shell
npx skills add purview-dev/purview-dev
```

| Skill | Use it to |
| --- | --- |
| `purview-site-integration` | make a repository readable by the site, and prepare its catalogue entry |
| `purview-site-docs` | lay out documentation hierarchically, pair LikeC4 and Mermaid diagrams, record moves in `_redirects.yml` |
| `purview-site-assessment` | run `scripts/assess.mjs` over a checkout and get a prioritised report with fixes |

## Release data

Release data is fetched at build time — never from the visitor's browser.
Expand Down Expand Up @@ -552,15 +568,26 @@ granting that repository any secrets. In that repository's release workflow,
call:

```shell
gh api repos/purview-dev/purview-dev.github.io/dispatches \
gh api repos/purview-dev/purview-dev/dispatches \
-f event_type=purview-site-rebuild \
-f "client_payload[repository]=purview-dev/<repo>" \
-f "client_payload[version]=<v1.2.3>"
```

The deploy workflow listens for `repository_dispatch` with type
`purview-site-rebuild`. The payload is informational and is not required for
the rebuild to succeed; the build always fetches fresh data itself.
`upstream-refresh.yml` listens for `repository_dispatch` with type
`purview-site-rebuild`. It waits 15 minutes so the release's packages finish
publishing (a newer dispatch cancels a run that is still waiting, so a burst of
releases builds once), then rebuilds only if something changed upstream. The
payload is informational; the build always fetches fresh data itself.

### Nightly upstream refresh

No product repository needs the dispatch. Every night `upstream-refresh.yml`
compares each catalogue project's default-branch head, tags and package versions
with the state the deployed site recorded at build time
(`/_discovery/upstream.json`), and calls `deploy.yml` only when something moved
(ADR 0014). Run it by hand from the Actions tab (`force` rebuilds regardless), or
locally with `just upstream-check`.

The release workflow is intentionally scoped to root `package.json` changes so
content refreshes do not get blocked by an unchanged version or an existing
Expand Down
Loading
Loading