-
Notifications
You must be signed in to change notification settings - Fork 467
🤖 ci: Sync Docs With LibreChat dev Weekly #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 (`<sha> <author date> <subject>`), 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 <sha>`, `git -C .sync/librechat show origin/dev:<path>` and `git -C .sync/librechat grep -n <pattern> origin/dev -- <paths>`. | ||
| - 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 | ||
|
|
||
| <One short paragraph: what this sync documents and the commit range.> | ||
|
|
||
| ## Changes | ||
|
|
||
| - <page>: <what was added or corrected> (<LibreChat PR numbers>) | ||
|
|
||
| ## Reviewed without docs changes | ||
|
|
||
| <One line per group, e.g. "Internal refactors and tests: #16592, #16610"> | ||
|
|
||
| ## Needs a human | ||
|
|
||
| - <Unverified claims, outdated screenshots, decisions you could not make from the source> | ||
| ``` | ||
|
|
||
| If nothing needs documentation, leave `content/docs/` untouched and say so in the report. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "librechat": { | ||
| "branch": "dev", | ||
| "lastSyncedCommit": "92432e95d5bd9e1937117c40184bdba466f95426" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
+84
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a merge commit contains conflict-resolution or other manual edits not present in either parent, Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the pending range is large enough for Useful? React with 👍 / 👎. |
||
| 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:*)" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because this step deliberately processes untrusted commit messages and source, an injected instruction can use the permitted Bash rule to run Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛡️ Codex Security Review · Automatically triggered
When Useful? React with 👍 / 👎. |
||
|
|
||
| - 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the range crosses an upstream merge with new commits on both parents,
--no-mergesleaves incomparable parent-side commits in this file, whilelastlater selects only one of them as the persisted marker. Git'sA..Brevision range excludes only commits reachable fromA, so the next run sees the other parent, saves that SHA, and subsequent runs can alternate between the two sides indefinitely. I reproduced this with a two-parent merge; the workflow repeatedly reviews the same commits and never converges. Derive the checkpoint from a merge or first-parent boundary that contains the entire reviewed set rather than the final line of the filtered log.Useful? React with 👍 / 👎.