From d3e7db6dc543e0816b49797ebafcdaf9fc2fa411 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:08:57 +0200 Subject: [PATCH 1/2] ci: Sync docs with LibreChat dev on a weekly schedule Review the commits merged to LibreChat dev since the last synced commit, let Claude document what needs it, and open or refresh one draft pull request. The sync marker in .github/docs-sync/state.json only advances when that pull request merges. The agent reads untrusted text, so it can only edit content/docs and its report, only run read-only git commands, and never sees a stored token; the job fails if git config or hooks change, and refuses locale files, files outside content/docs, and dashes used as punctuation. --- .github/docs-sync/prompt.md | 74 ++++++++++++ .github/docs-sync/state.json | 6 + .github/workflows/docs_sync_dev.yml | 174 ++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 .github/docs-sync/prompt.md create mode 100644 .github/docs-sync/state.json create mode 100644 .github/workflows/docs_sync_dev.yml diff --git a/.github/docs-sync/prompt.md b/.github/docs-sync/prompt.md new file mode 100644 index 000000000..f48598e32 --- /dev/null +++ b/.github/docs-sync/prompt.md @@ -0,0 +1,74 @@ +# Docs sync: document what merged to LibreChat `dev` + +You are updating the librechat.ai documentation so it covers changes that recently merged to the LibreChat application's `dev` branch. Work in the current checkout of the docs repository. + +## Inputs + +- `.sync/commits.txt`: the LibreChat commits to review, one per line (` `), oldest first. +- `.sync/librechat/`: a clone of `LibreChat-AI/LibreChat` at `origin/dev`. It is the source of truth for behavior. Read it with `git -C .sync/librechat show `, `git -C .sync/librechat show origin/dev:` and `git -C .sync/librechat grep -n origin/dev -- `. +- Docs live in `content/docs/`. English sources are the `*.mdx` and `meta.json` files without a locale suffix. + +Useful app paths: + +| What | Where | +| --- | --- | +| `librechat.yaml` schema, defaults, ranges | `packages/data-provider/src/config.ts` | +| Example config with comments | `librechat.example.yaml` | +| Environment variables | `.env.example` | +| English UI strings (exact labels) | `client/src/locales/en/translation.json` | +| Settings dialog entries | `client/src/components/Nav/Settings/registry.tsx` | + +## Step 1: triage + +For every commit in `.sync/commits.txt`, decide whether it needs documentation. It does when it: + +- adds or changes an environment variable, a `librechat.yaml` key, a default, a range, or precedence between them; +- adds or changes a user-visible feature, setting, workflow or label; +- changes admin-facing behavior (auth, permissions, retention, rate limits, logging an admin acts on); +- makes existing docs wrong (for example a page that still describes a removed menu). + +Internal refactors, tests, performance work, styling with no behavior change, and CI changes need no docs. Group related commits (a feature and its follow-up fixes) and document the final behavior on `origin/dev`, not the intermediate states. + +## Step 2: check coverage + +For each candidate, search the English docs for the keys, labels and concepts involved. If the docs already describe the current behavior correctly, record it as covered and move on. + +## Step 3: write + +- Verify every fact against `.sync/librechat` at `origin/dev` before writing it. A commit message is a lead, never evidence. Quote exact key names, defaults, ranges and UI labels from the source. +- Edit only English sources. Never create or edit locale copies such as `*.de.mdx` or `meta.de.json`; a separate workflow translates English changes. Write reader-facing prose inline in the MDX page, not in `components/repeated/`, because those partials are not translated. +- Put each change where a reader would look for it: feature behavior on the matching `content/docs/features/*.mdx` page, `librechat.yaml` keys on the matching page under `content/docs/configuration/librechat_yaml/object_structure/`, and environment variables in `content/docs/configuration/dotenv.mdx`. A genuinely new feature can get a new page; register it in the folder's `meta.json`. +- Match the page you are editing: its heading levels, tone and MDX components (`Callout`, `Steps`, `Tabs`, `OptionTable`). `OptionTable` cells render as plain text, so do not put backticks, bold or links inside them; put links in the surrounding prose. +- Lead with what the feature does and when to use it, then a minimal working example, then reference details, then caveats. Use exact UI labels in bold. Be complete but tight. +- The live docs follow `dev`. Do not add "available since" or "newer than vX" notes; released versions are served from `content/docs-archive/`, which you must never edit. +- Never use an em dash or an en dash as punctuation, and never use ` -- ` as a substitute. Use commas, colons, semicolons, parentheses or separate sentences. No emojis. +- Do not add or replace images. If a change makes an existing screenshot outdated, list it in the report instead. +- Do not touch files outside `content/docs/`, except `.sync/report.md`. + +## Step 4: verify + +After writing, re-check each changed claim against the source, ideally with an independent subagent that reads only the diff (`git diff -- content/docs`) and `.sync/librechat`. Fix every confirmed problem. Also check that every internal link you added points to an existing page and heading anchor. + +## Step 5: report + +Write `.sync/report.md`, which becomes the pull request description. Keep it to one screen: + +```markdown +## Summary + + + +## Changes + +- : () + +## Reviewed without docs changes + + + +## Needs a human + +- +``` + +If nothing needs documentation, leave `content/docs/` untouched and say so in the report. diff --git a/.github/docs-sync/state.json b/.github/docs-sync/state.json new file mode 100644 index 000000000..372cfecc1 --- /dev/null +++ b/.github/docs-sync/state.json @@ -0,0 +1,6 @@ +{ + "librechat": { + "branch": "dev", + "lastSyncedCommit": "92432e95d5bd9e1937117c40184bdba466f95426" + } +} diff --git a/.github/workflows/docs_sync_dev.yml b/.github/workflows/docs_sync_dev.yml new file mode 100644 index 000000000..a32e39449 --- /dev/null +++ b/.github/workflows/docs_sync_dev.yml @@ -0,0 +1,174 @@ +name: Docs Sync From LibreChat dev + +# Weekly: review what merged to LibreChat's dev branch since the last synced +# commit, let Claude document whatever needs it, and open (or refresh) one +# draft pull request. Nothing merges on its own. +# +# State: .github/docs-sync/state.json records the last LibreChat commit the +# docs were synced to. It only advances when the sync pull request merges, so +# an unmerged run is regenerated from main, covering the wider range, the next +# time the workflow runs. +# +# Secrets: +# ANTHROPIC_API_KEY required +# DOCS_SYNC_TOKEN optional; a token with contents and pull-requests write. +# Pull requests opened with the default GITHUB_TOKEN do not +# trigger other workflows, so CI only runs on the sync PR +# when this is set. + +on: + schedule: + - cron: '17 6 * * 1' + workflow_dispatch: + inputs: + since: + description: 'LibreChat commit to start after (defaults to state.json)' + type: string + default: '' + max_commits: + description: 'Most commits to review in one run' + type: number + default: 150 + model: + description: 'Claude model' + type: string + default: 'claude-opus-5-5' + +permissions: + contents: write + pull-requests: write + id-token: write + +env: + SYNC_BRANCH: automation/docs-sync-dev + STATE_FILE: .github/docs-sync/state.json + +concurrency: + group: docs-sync-dev + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + # No stored credentials: the agent below reads untrusted text (commit + # messages, app source), so nothing it can reach should hold a token. + persist-credentials: false + + - name: Prepare sync branch + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git switch -C "$SYNC_BRANCH" origin/main + + - name: Clone LibreChat dev + run: | + git clone --quiet --filter=blob:none --branch dev \ + https://github.com/LibreChat-AI/LibreChat.git .sync/librechat + + - name: Collect commits to review + id: range + env: + SINCE: ${{ inputs.since }} + MAX_COMMITS: ${{ inputs.max_commits || 150 }} + run: | + since="${SINCE:-$(jq -r '.librechat.lastSyncedCommit' "$STATE_FILE")}" + if ! git -C .sync/librechat cat-file -e "${since}^{commit}" 2>/dev/null; then + echo "::error::start commit $since is not in LibreChat dev" + exit 1 + fi + git -C .sync/librechat log --reverse --no-merges \ + --format='%H %as %s' "${since}..origin/dev" | head -n "$MAX_COMMITS" > .sync/commits.txt + count=$(wc -l < .sync/commits.txt | tr -d ' ') + echo "count=$count" >> "$GITHUB_OUTPUT" + if [ "$count" -eq 0 ]; then + echo "No new commits on LibreChat dev since $since." + exit 0 + fi + last=$(tail -n 1 .sync/commits.txt | cut -d' ' -f1) + echo "since=$since" >> "$GITHUB_OUTPUT" + echo "last=$last" >> "$GITHUB_OUTPUT" + echo "Reviewing $count commits: ${since:0:10}..${last:0:10}" + sha256sum .git/config > .sync/git-config.sha256 + ls -la .git/hooks > .sync/git-hooks.list + + - name: Document changes with Claude + id: claude + if: steps.range.outputs.count != '0' + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + Follow the instructions in .github/docs-sync/prompt.md. + Review the ${{ steps.range.outputs.count }} LibreChat commits listed in .sync/commits.txt + (range ${{ steps.range.outputs.since }}..${{ steps.range.outputs.last }}). + claude_args: | + --model ${{ inputs.model || 'claude-opus-5-5' }} + --max-turns 400 + --allowedTools "Read,Glob,Grep,Task,TodoWrite,Edit(./content/docs/**),Write(./content/docs/**),Edit(./.sync/report.md),Write(./.sync/report.md),Bash(git -C .sync/librechat show:*),Bash(git -C .sync/librechat log:*),Bash(git -C .sync/librechat grep:*),Bash(git -C .sync/librechat ls-tree:*),Bash(git -C .sync/librechat diff:*),Bash(git diff:*),Bash(git status:*),Bash(ls:*),Bash(wc:*)" + + - name: Check the result + id: check + if: steps.claude.outputs.conclusion == 'success' + run: | + # Refuse to go on if anything tampered with git's own config or hooks. + sha256sum -c --quiet .sync/git-config.sha256 + ls -la .git/hooks | diff -q - .sync/git-hooks.list + git config --local core.hooksPath /dev/null + # Only English docs sources may change. + changed=$(git status --porcelain --untracked-files=all -- . ':!.sync' | awk '{print $2}') + bad=$(printf '%s\n' "$changed" | grep -v -E '^content/docs/' || true) + locale=$(printf '%s\n' "$changed" | grep -E '\.[a-z]{2}(-[A-Z]{2})?\.(mdx|json)$' || true) + if [ -n "$bad$locale" ]; then + echo "::error::unexpected files changed:"; printf '%s\n' $bad $locale + exit 1 + fi + # No em dashes, en dashes or " -- " in added lines. + git add -N content/docs + if git diff -U0 -- content/docs | grep -E '^\+[^+]' | grep -n -E '—|–| -- '; then + echo "::error::added lines contain a dash used as punctuation" + exit 1 + fi + if [ ! -s .sync/report.md ]; then + echo "::error::Claude did not write .sync/report.md" + exit 1 + fi + if [ -z "$changed" ]; then + echo "docs_changed=false" >> "$GITHUB_OUTPUT" + else + echo "docs_changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Open or update the sync pull request + if: steps.check.outcome == 'success' + env: + GH_TOKEN: ${{ secrets.DOCS_SYNC_TOKEN || github.token }} + LAST: ${{ steps.range.outputs.last }} + DOCS_CHANGED: ${{ steps.check.outputs.docs_changed }} + run: | + jq --arg sha "$LAST" '.librechat.lastSyncedCommit = $sha' "$STATE_FILE" > "$STATE_FILE.tmp" + mv "$STATE_FILE.tmp" "$STATE_FILE" + git add content/docs "$STATE_FILE" + git commit -q --no-verify -m "docs: Sync with LibreChat dev up to ${LAST:0:10}" + auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w0) + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth" \ + push --no-verify --force origin "HEAD:$SYNC_BRANCH" + + title="📚 docs: Sync With LibreChat dev" + [ "$DOCS_CHANGED" = "true" ] || title="🔖 chore: Advance Docs Sync Marker (No Docs Changes)" + existing=$(gh pr list --head "$SYNC_BRANCH" --state open --json number --jq '.[0].number') + if [ -n "$existing" ]; then + gh pr edit "$existing" --title "$title" --body-file .sync/report.md + echo "Updated #$existing" + else + gh pr create --draft --base main --head "$SYNC_BRANCH" --title "$title" --body-file .sync/report.md + fi + + - name: Summary + if: always() && steps.range.outputs.count != '0' + run: | + [ -f .sync/report.md ] && cat .sync/report.md >> "$GITHUB_STEP_SUMMARY" || true From 8f7e2693fb07063c030a250fcf82f06462f02852 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:37:12 +0200 Subject: [PATCH 2/2] style: Format the docs sync prompt --- .github/docs-sync/prompt.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/docs-sync/prompt.md b/.github/docs-sync/prompt.md index f48598e32..e0d6a8026 100644 --- a/.github/docs-sync/prompt.md +++ b/.github/docs-sync/prompt.md @@ -10,13 +10,13 @@ You are updating the librechat.ai documentation so it covers changes that recent Useful app paths: -| What | Where | -| --- | --- | -| `librechat.yaml` schema, defaults, ranges | `packages/data-provider/src/config.ts` | -| Example config with comments | `librechat.example.yaml` | -| Environment variables | `.env.example` | -| English UI strings (exact labels) | `client/src/locales/en/translation.json` | -| Settings dialog entries | `client/src/components/Nav/Settings/registry.tsx` | +| What | Where | +| ----------------------------------------- | ------------------------------------------------- | +| `librechat.yaml` schema, defaults, ranges | `packages/data-provider/src/config.ts` | +| Example config with comments | `librechat.example.yaml` | +| Environment variables | `.env.example` | +| English UI strings (exact labels) | `client/src/locales/en/translation.json` | +| Settings dialog entries | `client/src/components/Nav/Settings/registry.tsx` | ## Step 1: triage @@ -41,7 +41,7 @@ For each candidate, search the English docs for the keys, labels and concepts in - Match the page you are editing: its heading levels, tone and MDX components (`Callout`, `Steps`, `Tabs`, `OptionTable`). `OptionTable` cells render as plain text, so do not put backticks, bold or links inside them; put links in the surrounding prose. - Lead with what the feature does and when to use it, then a minimal working example, then reference details, then caveats. Use exact UI labels in bold. Be complete but tight. - The live docs follow `dev`. Do not add "available since" or "newer than vX" notes; released versions are served from `content/docs-archive/`, which you must never edit. -- Never use an em dash or an en dash as punctuation, and never use ` -- ` as a substitute. Use commas, colons, semicolons, parentheses or separate sentences. No emojis. +- Never use an em dash or an en dash as punctuation, and never use `--` as a substitute. Use commas, colons, semicolons, parentheses or separate sentences. No emojis. - Do not add or replace images. If a change makes an existing screenshot outdated, list it in the report instead. - Do not touch files outside `content/docs/`, except `.sync/report.md`.