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
2 changes: 2 additions & 0 deletions .erpaval/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ development sessions. Solutions are reusable; specs are per-feature.

- [SCIP replaces LSP for code-graph oracle edges](solutions/architecture-patterns/scip-replaces-lsp.md) — one-shot indexers beat stateful LSP clients for compiler-grade graph edges.
- [Repomix --compress is output-side only](solutions/architecture-patterns/repomix-is-output-side.md) — don't substitute it for a tree-sitter chunker; use it for repo snapshots.
- [Starlight in a pnpm monorepo — minimal scaffold + GH Pages](solutions/architecture-patterns/starlight-in-pnpm-monorepo.md) — 9 files + 1 workflow give you a buildable docs site; gotchas captured.
- [Hand-roll a minimal protobuf reader for fixed schemas](solutions/conventions/scip-protobuf-hand-rolled-reader.md) — ~130 LOC beats pulling in buf+codegen when the schema is small and stable.
- [Seed docs-authoring subagents with a single ground-truth YAML](solutions/conventions/docs-site-ground-truth-yaml.md) — parallel writers agree when you tell them where truth lives.

## Specs

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: Starlight in a pnpm monorepo — minimal scaffold + GH Pages
tags: [astro, starlight, docs, pnpm, github-pages, monorepo]
first_applied: 2026-04-27
repos: [open-code-hub]
---

## The pattern

Add a Starlight docs site to a pnpm workspace as `packages/docs/`
without running the interactive `pnpm create astro` scaffolder. Nine
files give you a buildable site; one GitHub Actions workflow deploys
it to Pages. Total setup is ~5 minutes of file authoring plus one
`pnpm install` that adds astro + @astrojs/starlight to the root
lockfile.

## Required files

```
packages/docs/
├── package.json # private, name @<scope>/docs, engines.node ">=22.12.0"
├── astro.config.mjs # defineConfig + starlight integration with site+base
├── tsconfig.json # extends "astro/tsconfigs/strict"
├── public/
│ ├── favicon.svg
│ └── .nojekyll # empty — tells GH Pages "don't run Jekyll"
└── src/
├── content.config.ts # defineCollection for 'docs' with docsLoader+docsSchema
├── assets/logo.svg
├── styles/custom.css # optional; referenced via customCss in astro.config
└── content/docs/
├── index.mdx # landing page — template: splash + hero
└── <sections>/*.md # rest of content
```

## What you need in `astro.config.mjs`

```js
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";

export default defineConfig({
site: "https://<owner>.github.io",
base: "/<repo-slug>", // leading slash, NO trailing slash
integrations: [
starlight({
title: "...",
social: [{ icon: "github", label: "GitHub", href: "https://..." }],
editLink: { baseUrl: "https://github.com/<owner>/<repo>/edit/main/packages/docs/" },
sidebar: [{ label: "...", autogenerate: { directory: "..." } }],
}),
],
});
```

Critical: `site` has NO trailing slash, `base` starts with `/` and
has NO trailing slash. Pagefind, sitemaps, and canonical URLs all
derive from these.

## What you need in `src/content.config.ts`

Starlight 0.32+ uses `src/content.config.ts` — NOT the older
`src/content/config.ts` path that lots of tutorials still show.

```ts
import { defineCollection } from "astro:content";
import { docsLoader } from "@astrojs/starlight/loaders";
import { docsSchema } from "@astrojs/starlight/schema";

export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};
```

Without `docsLoader()` + `docsSchema()`, pages fail to build.

## GitHub Pages workflow

Two-job: build (upload pages artifact) then deploy (actions/deploy-pages).
Match the rest of your CI — if everything else uses `jdx/mise-action`,
don't switch to `withastro/action` just for this job.

```yaml
name: Pages
on:
push:
branches: [main]
paths:
- 'packages/docs/**'
- '.github/workflows/pages.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false # NOT true — canceling a mid-deploy leaves Pages in a weird state
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm -F <scope>/docs build
- uses: actions/upload-pages-artifact@v4
with: { path: packages/docs/dist }
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
```

`--ignore-scripts` on install is safe here (docs don't need
tree-sitter native builds or other postinstall hooks).

## Gotchas

- **Node 22.12.0 minimum** for Astro 6. If your root `package.json`
says `"node": ">=22.0.0"`, tighten `packages/docs/package.json`
locally with its own `engines` block — don't force the whole repo
up just for docs.
- **Biome does not parse `.astro` or `.mdx`.** Add to `biome.json`
ignore list. Biome 2.2+ no longer wants a trailing `/**` on
folder ignores — write `!packages/docs/src/content/docs`, not
`!packages/docs/src/content/docs/**`.
- **Mark the docs package `private: true`.** This excludes it from
the production license allowlist audit, which is useful because
astro deps sometimes pull `caniuse-lite` (CC-BY-4.0) transitively.
- **`.nojekyll` is required** in `public/`. Without it, GitHub Pages
strips `_astro/` and `_pagefind/` directories (underscore-prefixed
paths are Jekyll-hidden by default).
- **Internal links need the base prefix.** Write
`[text](/<repo-slug>/section/page/)` — plain `/section/page/`
will 404 on Pages. Starlight's sidebar and `<LinkCard>` handle
base automatically; only hand-written markdown links need care.
- **Enable Pages in repo settings once** (Settings → Pages → Build and
deployment → Source: GitHub Actions) before the first push. The
workflow silently succeeds-then-404s if Pages isn't enabled.

## When this pattern is wrong

- You need multi-version docs or i18n out of the box. Starlight
supports both but the scaffold above is single-version English.
- You need an Algolia DocSearch index. Starlight ships Pagefind by
default — good enough for most sites, no Algolia account needed.
- You need server-side rendering. Starlight is a static site; add
`@astrojs/node` or similar only if you actually need SSR.
71 changes: 71 additions & 0 deletions .erpaval/solutions/conventions/docs-site-ground-truth-yaml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Seed docs-authoring subagents with a single ground-truth YAML
tags: [erpaval, docs, subagents, explore, grounding]
first_applied: 2026-04-27
repos: [open-code-hub]
---

## The pattern

When parallelizing doc-authoring across multiple subagents, write
ONE ground-truth YAML from the Explore phase and make every agent
read it first. The YAML is authoritative; the repo's own README /
CONTRIBUTING may be stale. Tell the agents that explicitly.

## Why it works

Subagents start with empty context. Absent a pointer, they will
search the codebase and often surface stale prose. In this repo,
three separate numbers exist for "MCP tool count" (README=27,
server.ts=28, smoke-mcp.sh=19), and three separate license
allowlists (mise.toml, ci.yml, acceptance.sh). Every agent that
uses the README as ground truth will write "27 tools". Every agent
that reads explore.yaml (which captured the server.ts live count)
will write "28 tools" consistently.

## Recipe

1. **Explore phase** writes `explore.yaml` with a `top_gotchas`
section at the top that calls out source-of-truth conflicts
before any other content.
2. **Each Act subagent prompt** opens with:
> Ground truth: `.erpaval/sessions/<id>/explore.yaml`.
> If the repo's README disagrees, explore.yaml wins.
3. **Non-negotiable facts section** in the subagent prompt repeats
the 4-6 things that MUST be right (canonical CLI name, counts,
versions, banned strings). Agents skim long prompts; repeated
facts survive skimming.
4. **Banned-strings list** goes in every Act prompt. The repo's
`scripts/check-banned-strings.sh` will reject the whole PR if
any agent leaks a banned literal. Cheap insurance.

## Example frontmatter for a writing subagent

```text
## Non-negotiable facts

- CLI binary: `codehub` (NOT opencodehub).
- MCP tool count: 28 (README says 27 — stale. server.ts is authoritative).
- License allowlist: Apache-2.0;MIT;BSD-2-Clause;...
- Node 22, pnpm 10.33.2.

## Banned strings (hard CI fail)

Never write: <list>.
```

Three multi-agent runs in this session all produced consistent
"28 tools" prose without a single correction round. Without the
YAML+prompt-frontmatter pattern, the first two got it wrong on
their first attempt in prior sessions.

## When this pattern is wrong

- The task is small enough for one agent. Don't parallelize
three-page docs.
- The repo has NO stale artifacts. Then subagents reading the
README directly is fine.
- The ground truth is rapidly changing during the session (e.g.
you're editing server.ts and documenting it in the same turn).
Regenerate explore.yaml between rounds or tell agents to re-read
the source.
40 changes: 40 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Pages

on:
push:
branches: [main]
paths:
- "packages/docs/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm -F @opencodehub/docs build
- uses: actions/upload-pages-artifact@v4
with:
path: packages/docs/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ examples/fixtures/**/.codehub/
.claude/settings.local.json
.claude/worktrees/
.handoff/

# Astro build cache
packages/docs/.astro/
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# OpenCodeHub

[![CI](https://github.com/theagenticguy/opencodehub/actions/workflows/ci.yml/badge.svg)](https://github.com/theagenticguy/opencodehub/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/badge/Docs-Starlight-6ee7b7)](https://theagenticguy.github.io/opencodehub/)
[![CodeQL](https://github.com/theagenticguy/opencodehub/actions/workflows/codeql.yml/badge.svg)](https://github.com/theagenticguy/opencodehub/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/theagenticguy/opencodehub/badge)](https://securityscorecards.dev/viewer/?uri=github.com/theagenticguy/opencodehub)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
Expand Down Expand Up @@ -135,6 +136,18 @@ commit log and summarised in each release's generated CHANGELOG.
- **CI gates**: license allowlist, banned-strings grep, OSV vulnerability scan, CodeQL SAST, OpenSSF Scorecard
- **Zero open CVEs** on the lockfile at release time

## Documentation

Full user guide, MCP tool reference, and contributor documentation
are published at **https://theagenticguy.github.io/opencodehub/**.

Prefer to read locally:

```bash
mise run docs:dev
# http://localhost:4321/opencodehub
```

## Contributing

See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Issues and discussions welcome;
Expand Down
5 changes: 4 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"packages/**/src/**",
"packages/**/test/**",
"scripts/**/*.{js,mjs,ts}",
"!packages/**/corpus/repos"
"!packages/**/corpus/repos",
"!packages/docs/src/**/*.astro",
"!packages/docs/src/**/*.mdx",
"!packages/docs/src/content/docs"
],
"ignoreUnknown": true
},
Expand Down
21 changes: 21 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,24 @@ run = "node packages/cli/dist/index.js status"
description = "Start the codehub MCP server (stdio)"
depends = ["build:cli"]
run = "node packages/cli/dist/index.js mcp"

# ---------------------------------------------------------------------------
# Docs site (Astro + Starlight)
# ---------------------------------------------------------------------------

[tasks."docs:dev"]
description = "Run the Starlight docs site in dev mode (http://localhost:4321/opencodehub)"
depends = ["install"]
run = "pnpm -F @opencodehub/docs dev"

[tasks."docs:build"]
description = "Build the Starlight docs site (writes to packages/docs/dist)"
depends = ["install"]
run = "pnpm -F @opencodehub/docs build"
sources = ["packages/docs/src/**", "packages/docs/astro.config.mjs", "packages/docs/package.json"]
outputs = ["packages/docs/dist/**"]

[tasks."docs:preview"]
description = "Preview the built docs locally"
depends = ["docs:build"]
run = "pnpm -F @opencodehub/docs preview"
46 changes: 46 additions & 0 deletions packages/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# @opencodehub/docs

Astro + Starlight documentation site for OpenCodeHub. Deployed to
GitHub Pages at https://theagenticguy.github.io/opencodehub/.

## Local development

```bash
pnpm install
pnpm -F @opencodehub/docs dev # http://localhost:4321/opencodehub
pnpm -F @opencodehub/docs build # writes to packages/docs/dist
pnpm -F @opencodehub/docs preview # serves dist/ locally
```

Prefer the mise tasks from the repo root:

```bash
mise run docs:dev
mise run docs:build
mise run docs:preview
```

## Authoring

Pages live under `src/content/docs/`. Starlight picks up any
`.md` or `.mdx` file automatically; the sidebar auto-generates
per top-level directory.

Frontmatter fields we use:

```yaml
---
title: Page title
description: One-sentence SEO/summary
sidebar:
order: 1 # lower first; ties break alphabetically
label: Short # optional override
---
```

## Deploy

`.github/workflows/pages.yml` runs on pushes to `main` that touch
`packages/docs/**` or the workflow itself. It builds with
`withastro/action@v6` pinned to Node 22 and deploys with
`actions/deploy-pages@v5`.
Loading
Loading