Astro static site for the Diversio Engineering hub at engineering.diversio.com.
This README is for maintainers of the website code, not just visitors of the site.
Before making content or structure changes, read:
docs/content-governance.mdfor page scope, content ownership, blog ordering, and stack asset provenancedocs/editing-recipes.mdfor “change X → edit Y” guidancedocs/editorial-workflow.mdfor deciding whether something belongs on a page, in shared data, or in a blog post
cd website
npm install
npm run dev # local dev at http://localhost:4321
npm run build # production build -> dist/
npm run preview # preview the production build locallyNode requirement: Astro 6 in this site currently needs Node >=22.12.0.
The website reads tool docs and metadata from an agent-skills-marketplace checkout.
Current local-resolution order is:
AGENT_SKILLS_REPO_DIR- the current git repo root when it contains
plugins/andpi-packages/ ../agent-skills-marketplace../vendor/agent-skills-marketplace./vendor/agent-skills-marketplace
If local builds cannot find the tool source repo, set:
export AGENT_SKILLS_REPO_DIR=/absolute/path/to/agent-skills-marketplaceThis applies to both:
- the Astro build/extraction path
website/scripts/generate-og-images.py
The website currently works as a small set of connected page families.
The homepage explains the whole engineering site and routes readers toward the right surface.
Primary routes:
/
These pages explain how Diversio Engineering works, what standards shape the work, and how stack and workflow decisions fit together.
Primary routes:
/how-we-work/systems
These pages explain the open tools surface, the package registry, and the deeper docs for plugins, skills, and Pi packages.
Primary routes:
/agentic-tools/registry/docs/*/skills/*/pi/*
These pages carry the publication layer and the broader ways people can follow, engage with, and contribute to the work.
Primary routes:
/blog/blog/*/authors/*/community
A good maintenance rule: when a change feels like it belongs to two page families, stop and reread docs/content-governance.md and docs/editorial-workflow.md before editing both.
Blog posts live as markdown files under src/content/blog/. The site supports
two kinds of posts that share one schema and one list page.
Written for this site. The canonical URL stays on engineering.diversio.com.
---
title: My Post Title
summary: One-sentence summary
publishDate: 2026-05-04
author:
name: Author Name
url: https://github.com/author # optional
tags: [engineering, tooling]
sourceType: original
featured: false # optional; set true to pin on hero surfaces
---
Post body in markdown.Originally published elsewhere. The canonical URL and source URL must point to the original so search engines attribute correctly and readers can find the full article.
---
title: Original Article Title
slug: original-slug-from-source
summary: One-sentence description
publishDate: 2025-08-07 # original publication date
author:
name: Original Author
url: https://their-site.com
tags: [engineering, django]
sourceType: repost
sourceSiteName: their-site.com
sourceUrl: https://their-site.com/original-post/
canonicalUrl: https://their-site.com/original-post/
---
*This post was originally published on [their-site.com](https://their-site.com/original-post/).*
Brief context or excerpt here.
[Read the full post on their-site.com](https://their-site.com/original-post/)| Field | Required for reposts | Why |
|---|---|---|
sourceType: repost |
Yes | Tells the page to show "Curated repost" label and source box |
sourceUrl |
Yes | Links readers to the original |
canonicalUrl |
Yes | Search engines see this as the authoritative URL |
sourceSiteName |
Recommended | Shown on the blog index as "Source: example.com" |
slug |
Recommended | Match the original slug so URLs are predictable |
publishDate |
Recommended | Use the original publish date so the timeline is honest |
# Add a new repost markdown file
$EDITOR website/src/content/blog/repost-some-post.md
# Regenerate social preview cards (blog posts get their own OG images)
python3 website/scripts/generate-og-images.py
# Rebuild and check
cd website && npm run build
find dist/blog -name '*.html' | sort
# Check a specific repost page
curl -s http://localhost:4321/blog/some-slug/ | rg '<title>'frontmatter.sourceType = "repost"
-> blog index shows "Curated repost" label instead of "Original post"
-> detail page shows source note box with sourceUrl and canonicalUrl
-> detail page sets og:url to the original canonical URL for SEO
frontmatter.sourceType = "original"
-> blog index shows "Original post" label
-> detail page does not show source note box
-> canonical URL defaults to the engineering site URL
marketplace.json
-> cards, counts, registry summaries
engineering-principles.ts
-> how-we-work principles
engineering-highlights.ts
-> systems examples and lane ordering
engineering-stack.ts
-> stack layers, logos, supporting tools
site-entry-points.ts
-> shared route summaries and CTA wording
plugins/*/skills/*/SKILL.md
pi-packages/*/skills/*/SKILL.md
pi-packages/*/README.md
-> real human-authored source docs
src/data/site-docs.ts
-> build-time extraction layer
/skills/* and /pi/*
-> readable website pages built from the real repo docs
Why this shape works
Because the repo already has a source of truth. We do not want a second website-only documentation schema that drifts.
| Website surface | Source of truth | Why |
|---|---|---|
| Shared site name, primary hostname, nav routes | site.config.mjs |
one place for public brand/domain strings |
| Homepage counts, registry cards, package summaries | src/data/marketplace.json |
stable catalog metadata |
/how-we-work principles |
src/data/engineering-principles.ts |
principles change more slowly and should stay separate from examples |
/how-we-work practice rows |
src/data/engineering-practices.ts |
concrete engineering habits now live outside page markup for easier maintenance |
/how-we-work featured example selection |
src/data/how-we-work.ts |
page-specific featured highlight slugs stay out of template code |
/systems and shared example rows |
src/data/engineering-highlights.ts |
manual summaries are easier to review than build-time aggregation |
| Stack showcase | src/data/engineering-stack.ts |
stack layers, logos, and supporting tools stay in one place |
| Reused route/entry-point summaries | src/data/site-entry-points.ts |
shared directional copy stays consistent across top-level pages |
| Tool-section featured plugin curation | src/data/agentic-tools.ts |
homepage and tools-section featured plugin choices stay out of page templates |
| Shared broad-site counts | src/utils/site-metrics.ts |
homepage/community-style counts stay consistent instead of drifting by page |
| Individual plugin skill pages | plugins/*/skills/*/SKILL.md |
the skill itself is the canonical behavior |
| Pi-local skill pages | pi-packages/*/skills/*/SKILL.md |
same reason: skill behavior lives in markdown |
| Pi extension pages | pi-packages/*/README.md |
commands/tools/env vars already live there |
| Blog posts and repost metadata | src/content/blog/* + src/content.config.ts |
explicit editorial schema with reviewable markdown |
| Contributor grid | git history via src/data/contributors.ts |
community data should age with the repo |
website/
├── astro.config.mjs
├── package.json
├── site.config.mjs
├── tsconfig.json
├── README.md
├── docs/
│ ├── content-governance.md
│ ├── editing-recipes.md
│ └── editorial-workflow.md
├── public/
│ ├── _headers
│ ├── _redirects
│ ├── og/
│ ├── diversio-logo.svg
│ ├── favicon.svg
│ └── og-default.png
├── scripts/
│ └── generate-og-images.py
└── src/
├── styles/
│ └── global.css
├── layouts/
│ ├── BaseLayout.astro
│ ├── PageLayout.astro
│ └── DocsLayout.astro
├── components/
│ ├── Header.astro
│ ├── Footer.astro
│ ├── Hero.astro
│ ├── Terminal.astro
│ ├── CodeBlock.astro
│ ├── RuntimeCodeTabs.astro
│ ├── Card.astro
│ ├── CardGrid.astro
│ ├── DataTable.astro
│ ├── FeatureList.astro
│ ├── SectionHeader.astro
│ ├── Tag.astro
│ └── Button.astro
├── content/
│ └── blog/
├── content.config.ts
├── data/
│ ├── marketplace.json
│ ├── site-docs.ts
│ ├── engineering-principles.ts
│ ├── engineering-practices.ts
│ ├── how-we-work.ts
│ ├── engineering-highlights.ts
│ ├── engineering-stack.ts
│ ├── site-entry-points.ts
│ ├── agentic-tools.ts
│ └── contributors.ts
├── utils/
│ ├── blog-posts.ts
│ ├── engineering-content.ts
│ ├── marketplace-content.ts
│ ├── pick-featured-blog-post.ts
│ ├── sort-blog-posts.ts
│ └── site-metrics.ts
└── pages/
├── index.astro
├── how-we-work.astro
├── systems.astro
├── agentic-tools.astro
├── registry.astro
├── community.astro
├── security.astro
├── terms.astro
├── 404.astro
├── docs/
│ ├── index.astro
│ ├── [...slug].astro
│ └── monty-code-review.astro
├── skills/
│ ├── index.astro
│ └── [skill].astro
├── pi/
│ ├── index.astro
│ └── [package].astro
└── blog/
├── index.astro
└── [slug].astro
Keep the detailed rules in the dedicated docs instead of copying them everywhere:
docs/content-governance.md- page scope
- content ownership
- stack asset provenance
- blog ordering rules
docs/editing-recipes.md- common "change X → edit Y" tasks
docs/editorial-workflow.md- judgment calls about page vs post vs shared-data changes
Rule of thumb:
- use this README for orientation
- use
content-governance.mdfor structure and ownership decisions - use
editing-recipes.mdwhen you already know what you want to change
This is the bridge between repo docs and website pages.
It exists because:
- plugin pages answer “what bundle is this?”
- skill pages need to answer “what does this one skill do?”
- Pi package pages need to answer “what is the extension surface?”
It intentionally does simple extraction, not clever parsing.
That simplicity is intentional.
If something breaks, a future maintainer should be able to read the file and say:
“Okay, we read headings, bullets, code fences, and a few README table sections. I understand why this page rendered the way it did.”
This component exists because install/invoke examples are only useful if the copy button matches the runtime the user is looking at.
Old approach:
# Claude Code
...
# Codex
...
Problem: the user copies both blocks together.
New approach:
[Claude Code] [Codex] (inside the dark code header)
<only one code sample visible>
Why the toggle lives inside the black code header:
- it visually replaces the old
bashlabel - it keeps the runtime choice attached to the code itself
- the copy button can always copy the active tab only
This file exists because contributor lists should come from git history when possible.
It keeps the logic simple:
- ask git for author history
- ignore obvious bots
- merge known human aliases
- infer GitHub profiles from noreply addresses when possible
- fall back to a small static list if git metadata is unavailable
This file is the single source of truth for site identity.
It exists because the rebrand touched several things at once:
- site name
- primary hostname
- top-level nav labels
- top-level route names
Without a shared config file, those strings drift into layouts, headers, footers, metadata, and docs.
Mental model:
page copy -> explains the content on one page
site.config.mjs -> explains what the site is called and where sections live
If a future change renames a section or moves a top-level route, start there.
This file defines the editorial contract for blog content.
It exists because original posts and curated reposts have different rules.
original post
-> can be owned entirely by this site
repost
-> must keep source attribution
-> must keep canonical URL information
That is why reposts are validated more strictly than original posts.
This file exists to preserve human entrypoints after the site rebrand.
It handles simple aliases such as:
/marketplace -> /agentic-tools
/tools -> /agentic-tools
/agent-skills-marketplace -> /agentic-tools
Why keep the rules exact instead of using broad wildcards?
- the tools landing page moved
- the deep docs routes mostly did not
- exact aliases are easier to reason about and harder to misconfigure
This workflow exists so deployment stays repo-owned and repeatable.
Mental model:
GitHub Actions
-> install dependencies
-> build website/dist
-> upload the finished static output to Cloudflare Pages
Why not rely on an opaque remote build only?
- build logs stay in GitHub where the change happened
- the same build command runs locally and in CI
- deployment is easier to audit when the upload step is explicit
Plugin skill pages come from:
plugins/<plugin>/skills/<skill>/SKILL.md
plugins/<plugin>/commands/*.md
The page uses:
- SKILL frontmatter for name / description / allowed tools
- markdown sections for skimmable summaries
- command wrappers to infer the related slash commands
references/andscripts/directories for resource listings
Pi package pages come from:
pi-packages/<package>/README.md
pi-packages/<package>/skills/*/SKILL.md
pi-packages/<package>/extensions/*
The page uses the README for:
- commands
- LLM tools
- environment variables
- UI shortcuts
- install / local test snippets
- first-principles sections like “What it does”, “Why this package exists”, etc.
That means:
if you want a better
/pi/<package>page, usually the right first fix is to improve the package README.
Edit the real skill docs first:
# examples
plugins/frontend/skills/frontend/SKILL.md
plugins/monty-code-review/skills/monty-code-review/SKILL.mdThen rebuild and check the generated site pages:
cd website
npm run build
npm run devOpen:
http://localhost:4321/skillshttp://localhost:4321/skills/<skill-name>- the parent plugin page at
http://localhost:4321/docs/<plugin-name>
Edit the real package docs first:
# examples
pi-packages/ci-status/README.md
pi-packages/dev-workflow/README.md
pi-packages/image-router/README.mdThen rebuild and inspect:
http://localhost:4321/pihttp://localhost:4321/pi/<package-name>- the package summary page at
http://localhost:4321/docs/<package-name>
Think in this order:
- Did I update the source docs?
SKILL.md- Pi package
README.md
- Did I update catalog metadata?
src/data/marketplace.json
- Does the extractor understand the doc shape I used?
src/data/site-docs.ts
That order matters.
Do not start by patching the final page template if the real docs are missing.
The blog now supports two content types:
sourceType: original -> written for this site
sourceType: repost -> mirrored/curated content with source attribution
A minimal original post looks like this:
---
title: Example post
summary: Short summary for cards and metadata.
publishDate: 2026-05-03
author:
name: Diversio Engineering
sourceType: original
---
Hello world.A repost must carry more metadata:
---
title: Example repost
summary: Why this external piece matters here.
publishDate: 2026-05-03
author:
name: Original Author
sourceType: repost
sourceSiteName: Example Site
sourceUrl: https://example.com/post
canonicalUrl: https://example.com/post
---That extra metadata is not optional ceremony. It exists so future readers and social crawlers can tell where the piece came from.
cd website
npm run buildcd website
npm run devcd ..
find plugins -path '*/skills/*/SKILL.md' | sort
find pi-packages -path '*/skills/*/SKILL.md' | sort
find pi-packages -name README.md | sortcd website
rg -n '/skills/|/pi/' distFor page scope and ownership rules, prefer docs/content-governance.md.
Quick classification only:
- Top-level hub/editorial pages:
/,/how-we-work,/systems,/agentic-tools,/blog,/community - Bundle-level pages:
/docs/<plugin-or-package>,/registry - Deep docs pages:
/skills/<skill>,/pi/<package>,/blog/<slug>
Every page now renders the metadata needed for share previews on Open Graph and X/Twitter consumers.
That includes:
- canonical URL
- page title
- page description
- absolute social image URL
- image alt text
- Open Graph tags used by Slack, Facebook, LinkedIn, and similar clients
- X/Twitter card tags
The logic lives in:
site.config.mjssrc/layouts/BaseLayout.astro
First principles:
- page content owns the page title and description
- the layout turns that into share metadata for every route
- the fallback social image is shared, but the page title/description stay route-specific
If a future page needs a custom share image or article-style metadata, extend
BaseLayout.astro props instead of hardcoding tags in a single page.
The site now supports route-specific static OG images for the main public page types:
public/og/home.png
public/og/agentic-tools.png
public/og/docs-*.png
public/og/skill-*.png
public/og/pi-*.png
public/og/blog-*.png
...
Those images are generated by:
python3 website/scripts/generate-og-images.pyWhy generate them ahead of time?
- shared links should look specific to the page being shared
- crawlers like Slack and X fetch static assets more reliably than dynamic ones
- design tweaks can be made in one script and regenerated consistently
A few examples:
/ -> /og/home.png
/agentic-tools -> /og/agentic-tools.png
/docs/dev-workflow -> /og/docs-dev-workflow.png
/skills/dev-workflow -> /og/skill-dev-workflow.png
/pi/dev-workflow -> /og/pi-dev-workflow.png
/blog/my-post -> /og/blog-my-post.png
The page code does not hardcode file existence checks by hand. Instead, routes
use src/utils/og-image.ts to ask for the image that matches their route type
and fall back cleanly when a generated asset is missing.
Typical workflow:
# 1. Change metadata or copy that affects a social card
$EDITOR website/src/data/marketplace.json
$EDITOR website/src/content/blog/*.md
# 2. Regenerate the PNG assets
python3 website/scripts/generate-og-images.py
# 3. Rebuild the site and spot-check a few routes
cd website && npm run build
rg -n 'og:image|twitter:image' dist/docs/dev-workflow/index.html
rg -n 'og:image|twitter:image' dist/skills/dev-workflow/index.html
rg -n 'og:image|twitter:image' dist/pi/dev-workflow/index.htmlThe header does not show a fake search control anymore.
Reason:
- a visible search affordance implies working search
- a dead button is worse than no button
If real static search gets added later, it should come back as a real feature, not as a placeholder.
Reason:
- the runtime is part of the code sample, not a separate page control
- the copy button should copy the active runtime only
- the user should not have to visually stitch together two distant controls
Reason:
- the community page is meant to show humans behind the repo
- git history still drives the data, but the display layer intentionally filters bot identities
At the moment there are no intentionally accepted build warnings in the website itself. If Astro starts warning again, treat that as a fresh issue to inspect, not as normal background noise.
The site is fully static and deploys cleanly to Cloudflare Pages.
Current deploy shape:
Validate Websiteremains the read-only build gate.github/workflows/deploy-website-cloudflare-pages.ymlbuilds the site in GitHub Actions and uploadswebsite/distto Cloudflare Pages- PR previews run for same-repo PRs
- production deploys run automatically on pushes to
main
Current redirect split:
public/_redirectshandles same-project path aliases such as/marketplace->/agentic-tools- hostname migration from
agents.diversio.comtoengineering.diversio.comstill needs host-level redirect configuration during rollout
Required GitHub secrets:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID
Optional GitHub repo variable:
CLOUDFLARE_PAGES_PROJECT(defaults todiversio-engineeringin the workflow)
Recommended settings:
| Setting | Value |
|---|---|
| Pages project name | diversio-engineering |
| Deploy path | website/dist |
| Production branch | main |
| Node.js version | 24 |
Before you trust CI, run the same core steps locally:
cd website
npm install --package-lock=false
npm run buildThen sanity-check the output:
rg -n '/agentic-tools|/blog|/skills|/pi' distpull request from this repo
-> preview deployment on Cloudflare Pages
push to main
-> production deployment on Cloudflare Pages
manual workflow_dispatch run
-> preview from any selected ref
-> production only when dispatched from main
This split exists for a simple reason: reviewers need a safe preview URL, while production should only change after merge. Manual dispatch still exists so the team can re-run a preview or production deploy without manufacturing a new code change, but the workflow guards against deploying a feature-branch build to the production Pages branch.
The canonical site URL is:
https://engineering.diversio.com
The site uses real Diversio branding assets:
public/diversio-logo.svgpublic/favicon.svgpublic/og-default.png
Header, footer, hero, favicon, and OG metadata all use this branding layer.
If you add or remove a major route family, data source, or doc surface, update:
- this
website/README.md src/pages/docs/index.astro- any affected registry or homepage navigation
That keeps the website code understandable for the next person, not just functional today.