- Write long-lived feature and configuration docs in the present tense for the behavior readers will use when the docs are published. Avoid time-relative wording such as "upcoming", "new", or "now" that requires later cleanup.
- PRs for unreleased behavior can be ready for review. Coordinate merge timing with the relevant release or weekly update, and record that dependency in the PR description, not in customer prose.
- Describe the current behavior concisely. Put release transitions and old/new comparisons in upgrade notes rather than permanent feature docs. Retain version-specific guidance only when readers still need it to use or upgrade supported releases.
- Number ordered Markdown lists sequentially (
1.,2.,3.), rather than writing every item as1..
- Type Check:
npx tsc --noEmit - Package manager:
pnpm(packageManagerinpackage.json;mise installgives you the pinned Node and pnpm from.tool-versions). Nevernpm run - Build:
pnpm run build - Dev:
pnpm run dev - Lint:
pnpm run lint(ESLint 9 flat config ineslint.config.mjs; React Compiler rules are warnings until the vendoredsrc/components/search/docsearchcode is rewritten) - Framework: Next.js 16.
next builduses Turbopack;dev/build-content.mjsrunscontentlayer2 buildfirst, with its cache under.next/cacheso Vercel keeps it between deploys, and with each.mdxfile's mtime set from its content, since contentlayer2 keys its cache on mtime and a fresh clone resets those (workaround; drop once timlrx/contentlayer2#94 ships).next dev --webpackstill uses thenext-contentlayer2webpack plugin to regenerate content on change (next.config.jsapplies it only in the dev phase). The request rewrite lives insrc/proxy.ts;/api/releasesand/api/versionsopt into static caching withexport const dynamic = 'force-static' - Checks:
pnpm run checkruns the checks indev/checks.mjs(links, filenames, images);pnpm run buildruns filenames and images first, so a finding from those fails a deploy. Links is not in the build: it runs as its own PR check (.github/workflows/check-links.yml) - Check redirects:
node dev/check-redirects.mjsreports broken entries insrc/data/redirects.ts(CI comments on PRs that break redirects; see the script header for what it checks). Not part ofpnpm run check: main has hundreds of pre-existing findings, and CI only reports the ones a PR adds. When a check script crashes, CI comments that the check could not run, linking the job log, instead of failing the PR: an empty report says nothing about the PR - Prove changed links resolve on a deploy:
node dev/verify-links-live.mjs --site <vercel-preview-url>prints a Markdown table for the PR description - Spell check:
.github/workflows/spellcheck.ymlrunsdev/check-spelling.mjson the lines a PR adds plus its title and description, and comments the findings (advisory, never fails the PR). Add product names and identifiers tocspell-allow-list.txt, in alphabetical order; the check reports out-of-order entries. CSpell is not a project dependency:npx cspell@10 --no-progress <file>to run it locally - Example hostnames: placeholder hostnames in docs are
*.example.com(sourcegraph.example.comfor the reader's instance,github.example.comfor a code host,https://sourcegraph.example.comwhere a whole URL is meant); never<URL>,[hostname], or a$VARIABLEthe reader has to guess. Example URLs usehttps://, except where needed, eg. loopback, in-cluster, proxy, and XML-namespace addresses. In a shell snippet a$VARIABLEis fine only when the same snippet exports it with an example value..github/workflows/check-hostnames.ymlrunsdev/check-hostnames.mjson the lines a PR adds and comments the findings with suggested changes (advisory, never fails the PR).dev/example-hostnames.jsonmaps each recommended hostname to the placeholders seen in its place, as either literal text, a*glob, or a/regex/, matched case-insensitively with-,_, and.interchangeable; add to it when the check misses one, keeping keys and lists sorted.node dev/check-hostnames.mjschecks all ofdocs/The🤖 Sync generated docsPRs overwrite theSCHEMA_SYNC_START…SCHEMA_SYNC_ENDblocks and whole pages (self-hosted/observability/{alerts,dashboards}.mdx,cli/references/,ai/models.mdx,cody/capabilities/supported-models.mdx,admin/telemetry/); fix those in sourcegraph/sourcegraph instead.git log --author=sourcegraph-buildkite -- <file>shows whether a page is one
pnpm run check links --check-anchors --check-self-links. CI comments on PRs that break links and fails theBroken links introduced by this PRcheck; seedev/check-links.mjs.- When moving a page or renaming a heading, update every link to it; a
redirect in
src/data/redirects.tsdoes not satisfy the check. - Link to this site with relative paths (
/admin/config/site-config), neverhttps://sourcegraph.com/docs/…orhttps://docs.sourcegraph.com/…. - main has hundreds of pre-existing findings. To see only the ones your
branch adds, as CI does, record the merge base with
--format jsonand pass it as--baseline;--diffdoes not filter findings, it only scopes--check-externaland the review comments to added lines:
base=$(git merge-base origin/main HEAD)
git worktree add --detach /tmp/docs-base "$base"
node dev/check-links.mjs --check-anchors --check-self-links --format json \
--root /tmp/docs-base > /tmp/docs-base-links.json
git worktree remove /tmp/docs-base
git diff -U0 "$base" | pnpm run check links --check-anchors --check-self-links \
--check-external --baseline /tmp/docs-base-links.json --diff /dev/stdin- Pipe the diff to
--diff /dev/stdin, not--diff <(git diff ...):dev/checks.mjsspawns the check with only stdin/stdout/stderr, so the process-substitution descriptor is gone and the check fails withEBADF
The spelling, links, redirects, and example hostnames checks comment on the
PR: a summary comment per check, plus an inline review comment with a
suggestion block on each flagged line. After pushing, read them and follow
the instructions they give (fix the spelling, the link, the redirect, or the
hostname); do not work around a finding, and add a word to
cspell-allow-list.txt only when it is correct.
gh pr checks <pr>
gh api repos/sourcegraph/docs/issues/<pr>/comments --paginate --jq '.[].body'
gh api repos/sourcegraph/docs/pulls/<pr>/comments --paginate \
--jq '.[] | "\(.path):\(.line)\n\(.body)\n"'Fork PRs get no comments; the findings are in the job log
(gh run view <run-id> --log). The checks resolve their own comments on the
next run.
- Vercel shows build logs only to its team members, so
.github/workflows/vercel-build-log.ymlattaches the log to the Vercel Slack app's "failed to deploy" post in theSLACK_CHANNEL_IDchannel and comments a link to it on the PR (seedev/vercel-build-log.mjs). The log itself never goes on the PR, since the repository is public. - It reads Vercel with the
VERCEL_TOKENrepo secret, a token scoped to thesourcegraph-docsproject that expires 2026-12-10; mint a new one withPOST /v3/user/tokens?teamId=<team>andprojectIdin the body. - Slack needs the
SLACK_BOT_TOKENrepo secret andSLACK_CHANNEL_IDrepo variable. The bot is the Slack app indev/vercel-build-log-slack-app-manifest.json; to recreate it, paste that manifest at https://api.slack.com/apps?new_app=1 (From a manifest), install it, copy its Bot User OAuth Token into the secret, and/invite @Vercel build logto the channel.
- Prefix every branch with a name that identifies the human it belongs to
(first name, username, or nickname), then a slash:
marc/fix-moved-page-links. Agents use the name of the human they are working for, never their own