Document Jerboa site publishing contract
ober
a9d33e697e894f986d94a5d5b29d1636f3f82208
--- a/README.md +++ b/README.md @@ -281,6 +281,8 @@ Start with these docs: - [`docs/build.md`](docs/build.md), [`docs/cross-compile.md`](docs/cross-compile.md), [`docs/single-binary.md`](docs/single-binary.md), and [`docs/jpkg-guide.md`](docs/jpkg-guide.md) for builds and packages. +- [`docs/site-publishing.md`](docs/site-publishing.md) for keeping + `jerboa.sh` synchronized with this repository. ## License new file mode 100644 --- /dev/null +++ b/docs/site-publishing.md @@ -0,0 +1,84 @@ +# Jerboa Site Publishing + +`jerboa.sh` presents Jerboa, but the source of truth for language and +runtime facts lives in this repository. The website repository owns +layout, style, the deployable web binary, and production-specific glue. + +## Ownership + +Jerboa owns: + +- canonical documentation under `docs/` +- runnable examples under `examples/` +- machine-readable feature, API, changelog, and security data under `data/` +- public brand assets under `assets/` + +`jerboa-site` owns: + +- page composition and visual design +- site-specific copy that is not canonical language documentation +- generated snapshots imported from this repository +- the web binary and deployment workflow + +This split keeps documentation close to the code that changes while +letting the website stay small and private. + +## Metadata Export + +Use `tools/emit-site-data` to produce a pinned metadata snapshot for a +site sync: + +```sh +tools/emit-site-data --out-dir dist/site-data +``` + +The command writes `jerboa-source.json` from the committed `HEAD` tree. +The JSON records: + +- repository path, remote, branch, commit, and `VERSION` +- whether the local worktree had uncommitted changes when exported +- UTC generation time +- checksums and byte sizes for top-level `docs/*.md` +- checksums and byte sizes for top-level `data/*` +- checksums and byte sizes for top-level `examples/*` +- checksums and byte sizes for top-level `assets/*` + +The site should store this generated file as +`data/jerboa-source.json` and treat its `repo.commit` value as the +exact Jerboa revision represented by the website. + +## Sync Workflow + +Normal local update flow: + +```sh +cd ~/mine/jerboa +git status --short +tools/emit-site-data --out-dir dist/site-data + +cd ~/mine/jerboa-site +tools/sync-jerboa --jerboa ~/mine/jerboa --write +git diff +make binary +``` + +Commit Jerboa documentation updates in this repository with the code or +library behavior that made them necessary. Commit the `jerboa-site` +generated snapshot separately, after reviewing the site diff. + +## LLM-Assisted Updates + +An LLM may draft summaries for homepage highlights, "what changed" copy, +or migration notes. It must not be the source of truth. + +Required guardrails: + +- feed the LLM only the emitted metadata, selected Jerboa docs, and a + bounded `git diff` or commit range +- require source paths or commit ids in generated notes +- review the rendered diff before committing +- do not let the LLM deploy or edit production state +- never replace extracted API/module facts with prose guesses + +The reliable path is: Jerboa emits facts, the site imports a pinned +snapshot, and optional LLM text is reviewed as ordinary documentation. new file mode 100755 --- /dev/null +++ b/tools/emit-site-data @@ -0,0 +1,163 @@ +#!/bin/sh +set -eu + +usage() { + cat <<'USAGE' +usage: tools/emit-site-data [--out-dir DIR] + +Emit the Jerboa metadata snapshot consumed by jerboa-site. + +Without --out-dir the JSON is written to stdout. With --out-dir, the +snapshot is written to DIR/jerboa-source.json. +USAGE +} + +out_dir= + +while [ "$#" -gt 0 ]; do + case "$1" in + --out-dir) + shift + if [ "$#" -eq 0 ]; then + echo "emit-site-data: --out-dir requires a directory" >&2 + exit 2 + fi + out_dir=$1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "emit-site-data: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(CDPATH= cd -- "$script_dir/.." && pwd) + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +json_string() { + printf '"%s"' "$(json_escape "$1")" +} + +git_value() { + git -C "$repo_root" "$@" 2>/dev/null || printf 'unknown' +} + +stdin_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum | awk '{print $1}' + else + shasum -a 256 | awk '{print $1}' + fi +} + +blob_sha256() { + git -C "$repo_root" show "HEAD:$1" | stdin_sha256 +} + +blob_bytes() { + git -C "$repo_root" cat-file -s "HEAD:$1" +} + +is_dirty() { + if ! git -C "$repo_root" diff --quiet -- .; then + return 0 + fi + if ! git -C "$repo_root" diff --cached --quiet -- .; then + return 0 + fi + return 1 +} + +emit_git_file_list() { + rel_dir=$1 + pattern=$2 + + first=1 + git -C "$repo_root" ls-tree -r --name-only HEAD -- "$rel_dir" | sort | while IFS= read -r rel_path; do + rest=${rel_path#"$rel_dir/"} + case "$rest" in + */*) continue ;; + esac + base=${rel_path##*/} + case "$base" in + $pattern) ;; + *) continue ;; + esac + if [ "$first" -eq 1 ]; then + first=0 + else + printf ',\n' + fi + printf ' {"path": ' + json_string "$rel_path" + printf ', "sha256": ' + json_string "$(blob_sha256 "$rel_path")" + printf ', "bytes": %s}' "$(blob_bytes "$rel_path")" + done +} + +emit_json() { + remote=$(git_value config --get remote.origin.url) + branch=$(git_value rev-parse --abbrev-ref HEAD) + commit=$(git_value rev-parse HEAD) + version=unknown + if [ -f "$repo_root/VERSION" ]; then + version=$(sed -n '1p' "$repo_root/VERSION") + fi + generated_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ') + working_tree_dirty=false + if is_dirty; then + working_tree_dirty=true + fi + + printf '{\n' + printf ' "schema": "jerboa.site-source.v1",\n' + printf ' "repo": {\n' + printf ' "path": '; json_string "$repo_root"; printf ',\n' + printf ' "remote": '; json_string "$remote"; printf ',\n' + printf ' "branch": '; json_string "$branch"; printf ',\n' + printf ' "commit": '; json_string "$commit"; printf ',\n' + printf ' "version": '; json_string "$version"; printf ',\n' + printf ' "source_tree": "HEAD",\n' + printf ' "working_tree_dirty": %s\n' "$working_tree_dirty" + printf ' },\n' + printf ' "generated_at": '; json_string "$generated_at"; printf ',\n' + printf ' "site_contract": {\n' + printf ' "human_docs": "docs/",\n' + printf ' "machine_data": "data/",\n' + printf ' "examples": "examples/",\n' + printf ' "assets": "assets/"\n' + printf ' },\n' + printf ' "canonical_sources": {\n' + printf ' "docs": [\n' + emit_git_file_list docs '*.md' + printf '\n ],\n' + printf ' "data": [\n' + emit_git_file_list data '*' + printf '\n ],\n' + printf ' "examples": [\n' + emit_git_file_list examples '*' + printf '\n ],\n' + printf ' "assets": [\n' + emit_git_file_list assets '*' + printf '\n ]\n' + printf ' }\n' + printf '}\n' +} + +if [ -n "$out_dir" ]; then + mkdir -p "$out_dir" + emit_json > "$out_dir/jerboa-source.json" +else + emit_json +fi