Skip to content

feat: add auto-generated "See also" links to docs pages - #202

Open
zoltan-baba wants to merge 5 commits into
mainfrom
feature/see-also-embeddings
Open

feat: add auto-generated "See also" links to docs pages#202
zoltan-baba wants to merge 5 commits into
mainfrom
feature/see-also-embeddings

Conversation

@zoltan-baba

Copy link
Copy Markdown
Collaborator

Summary

  • Adds an auto-generated "See also" section to every docs page, using local sentence-transformer embeddings (no external API calls) to surface related pages by cosine similarity, capped per source directory so tightly-clustered sections don't crowd out cross-cutting matches. Writers can override or exclude picks per page via see_also/see_also_exclude frontmatter.
  • Resolves each related link live, per-locale, from Docusaurus's own doc data — see_also.json stores only a relatedness graph of doc ids, never a baked title/permalink. This is what makes the same data correctly serve both /en/ and /ja/ without regenerating per locale, and it's why a stale or removed reference is silently dropped at render time instead of ever becoming a dead link.
  • Adds CI automation across three workflows: a cheap, dependency-free prune of stale ids on any PR that deletes or renames a page (skips cleanly on fork PRs, which can't push back); a cost-gated scheduled full re-embed that only runs once ~40 docs files (excluding generated API reference pages) have changed since the last regeneration — measured against this repo's actual edit history to land around once a week; and a test suite for the prune/pruning logic itself.

Test plan

  • npx tsc --noEmit passes — no type errors in the SeeAlso component's Docusaurus hook usage
  • python3 scripts/test_prune_see_also.py — 14/14 passing (id derivation, diff parsing, delete/rename/no--M-fallback edge cases)
  • Regenerated see_also.json from a clean npx docusaurus build --locale en and confirmed the id-only output shape
  • Verified in a local dev/build preview that See also links render with correct titles and resolve to the right locale on both /en/ and /ja/ builds — no doubled locale prefixes
  • Verified the CPU-only torch install order in regenerate-see-also.yml actually avoids reinstalling a CUDA build
  • All three new/changed workflow YAML files parse correctly
  • Not yet exercised inside actual GitHub Actions (only simulated locally) — worth watching the first real run of each workflow after merge

zoltan-baba and others added 5 commits August 19, 2026 14:24
Embeds every docs page locally (sentence-transformers, no external API
calls) and surfaces the top related pages by cosine similarity, capped
per source directory so tightly-clustered sections (e.g. Bazel/Gradle
build-cache pages) don't crowd out cross-cutting matches. Generated API
reference docs and top-level product landing pages are excluded from
the embedding corpus entirely. Writers can override or exclude specific
picks per page via `see_also`/`see_also_exclude` frontmatter.

Renders via a DocItem/Paginator swizzle so it appears automatically
between the article content and the Previous/Next buttons on every
page, without touching individual content files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Storing a page's title/permalink at generation time broke under the
repo's new i18n setup: a locale-prefixed permalink baked into
see_also.json only resolves correctly on the locale that happened to
generate it, and doubles up (/ja/en/...) on every other locale, since
Docusaurus's <Link> prepends whichever locale is currently rendering.

see_also.json now maps each page's doc id to a list of related doc ids
only. <SeeAlso> resolves the title and locale-correct permalink live,
per render, from Docusaurus's own per-locale doc data -- so the same
relatedness graph serves every locale correctly without regenerating
per locale, and a related id that no longer exists (page deleted or
renamed since the last regeneration) is silently dropped instead of
rendered as a dead link.

generate_see_also.py is also repinned to `npx docusaurus build --locale
en` instead of the multi-locale `npm run build`: building every locale
in one invocation leaves the Docusaurus cache reflecting whichever
locale happened to build last, making the embedding corpus's
structural data depend on build order rather than being deterministic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Companion to the scheduled full regeneration: dropping a dead
reference, or updating one to match a rename, doesn't require
re-embedding anything, so this is pure git-diff + JSON bookkeeping with
no numpy/torch/sentence-transformers dependency -- cheap enough to run
on every PR that touches docs/, not just the ones a full regeneration
is scheduled for.

Reads `git diff --name-status -M` from stdin (same format
translate_docs.py already reads changed files from). A deleted page's
id is dropped from its own entry and from every other page's
related-ids list; a renamed page's id is remapped everywhere instead
of dropped, since the content didn't change. Without -M, a rename
falls back to being treated as a plain delete rather than doing
nothing.

Includes stdlib unittest coverage for the id derivation, diff parsing,
and prune/remap logic -- the exact edge cases (delete, rename, the
no -M fallback, malformed input, excluded API-reference dirs) verified
by hand while building this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three workflows, each scoped to a different cost tier:

- prune-see-also.yml: runs the cheap prune script on every PR touching
  docs/, pushing the correction back onto the same PR branch so a page
  deletion/rename and its See also fix land together. Skips cleanly on
  fork PRs, which get a read-only GITHUB_TOKEN and can never push back
  here -- their changes fall back to the scheduled regeneration below,
  which is safe since a stale id is dropped at render time rather than
  rendered as a dead link.

- regenerate-see-also.yml: the actual re-embedding is real cost
  (sentence-transformers/torch), so a cheap gate job counts docs files
  changed since the last regeneration (scripts/see_also_sync_state.json)
  and only lets it run once that reaches 40 -- measured against this
  repo's real edit history to land at roughly once a week, erring
  towards fewer/cheaper runs since staleness here is safe, not just
  tolerated. Installs the CPU-only torch wheel first (no GPU on this
  runner) so the subsequent requirements install doesn't pull the much
  larger CUDA build, with an extra-index fallback in case a future
  version bump forces torch to be re-resolved. Opens a PR with the
  regenerated data and the updated sync state, reporting exactly how
  many files and since which commit triggered it.

- test-see-also.yml: runs scripts/test_prune_see_also.py whenever the
  pipeline's own code changes (not on ordinary docs content, which
  never touches it) -- a fast, dependency-free regression check
  distinct from the two workflows above, which exercise the pipeline
  against real data rather than testing its logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
package.json already picked up docusaurus-plugin-image-zoom and
docusaurus-plugin-llms from main; npm install brings the lockfile back
in sync with it.
@github-actions

Copy link
Copy Markdown

@ilanazholobovsky ilanazholobovsky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read all 13 code files; verified on the preview that the section renders correctly on both /en/ and /ja/ (same id-only graph, locale-correct permalinks, no doubled prefixes). The id-only design and the render-time drop hold up exactly as described. Approving — three non-blocking notes, in descending order of importance:

  1. prune-see-also.yml diffs against the base tip, not the merge-base. git diff -M "$BASE" HEAD with BASE = github.event.pull_request.base.sha is a two-dot diff against main's current tip, so main-side changes since the branch point leak in reversed: a page added on main shows up as D (usually a no-op here, since the branch's see_also.json predates that id), but a page renamed on main shows up as R and gets remapped in the PR's stale copy — a committed change that then conflicts with main's own see_also.json. Three-dot fixes it: git diff --name-status -M "$BASE...HEAD" -- docs. The test suite can't catch this one — it sits above the parse_changes boundary.
  2. The bot's [skip ci] commit becomes the PR head with no check runs. If required checks are ever enforced on this repo, a pruned PR can hang on "Expected" statuses, since the head commit skipped everything. Worth adding to the "watch the first real runs" list from the test plan.
  3. see_also / see_also_exclude (and the bare locale-less path form) are only documented in generate_see_also.py's docstring. CLAUDE.md is where authors and agents will actually look — the same gap #203 just closed for diff blocks. Fine as a follow-up ticket rather than in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants