Skip to content

Commit 93dafe2

Browse files
feat: adopt directory-based docs versioning with Edge channel
Switch docs.crewai.com from navigation-only versioning (every version selector entry rendered the same docs/<lang>/* source files) to Mintlify's directory-based versioning so each version selector entry renders its own snapshot. Add an "Edge" channel under docs/edge/<lang>/* that always reflects main HEAD for unreleased work, eliminating pre-release leakage onto frozen release labels. External links to canonical /<lang>/* URLs are preserved via wildcard redirects that always land on the current default version. Layout: - docs/edge/<lang>/* rolling source (you edit here) - docs/edge/enterprise-api.*.yaml - docs/v<X.Y.Z>/<lang>/* frozen, immutable snapshots - docs/v<X.Y.Z>/enterprise-api.*.yaml - docs/images/ shared, append-only - docs/docs.json nav + redirects URLs follow the Mintlify-idiomatic shape: /edge/<lang>/<page> for Edge, /v<X.Y.Z>/<lang>/<page> for every frozen snapshot. The wildcard redirects /<lang>/:slug* -> /<default>/<lang>/:slug* keep stale links working, and every freeze rewrites them (plus all per-section/per-page redirects) so destinations always resolve to the current default without depending on a second redirect hop. Release flow integration (devtools release): - New module crewai_devtools.docs_versioning.freeze() materialises docs/v<X.Y.Z>/ from docs/edge/, rewrites openapi: refs inside the snapshot, inserts the version into every language block in docs.json, and refreshes all redirect destinations. - _update_docs_and_create_pr() in cli.py now calls that freeze during Phase 2 of devtools release. Edge changelogs are updated first (so the snapshot freeze picks them up), then the snapshot is staged alongside docs.json, branched as docs/freeze-v<X.Y.Z>, and the PR is titled [docs-freeze] docs: snapshot and changelog for v<X.Y.Z> — the title prefix the new CI guard reads. - The PR still gates tag, GitHub release, PyPI publish, and the enterprise release as before; no new PRs are added. - Pre-releases (1.X.YaN, 1.X.YbN, ...) skip the snapshot — they ride Edge — and the docs PR title omits the [docs-freeze] prefix. - docs_check (AI-generated docs scaffolding) writes to docs/edge/<lang>/* so newly-generated unreleased docs land in Edge and never accidentally touch a frozen snapshot. Migration scripts (one-shot): - scripts/docs/freeze_historical_versions.py reconstructs all 16 historical snapshots (v1.10.0 .. v1.14.7) from git tags via git archive | tar, rewriting openapi: MDX refs so each snapshot reads its own enterprise-api YAML rather than the live one. - scripts/docs/prefix_version_paths.py one-shot-migrates docs.json: rewrites every page path in 16 versioned blocks to point under docs/v<X.Y.Z>/, inserts a new Edge entry per language, tags v1.14.7 as Latest (default), prunes pages whose target file doesn't exist in the snapshot (e.g. docs/ar/ didn't exist before v1.12.0), and writes the wildcard + per-section redirects. - scripts/docs/freeze_current_edge.py is now a thin CLI wrapper around docs_versioning.freeze for manual one-off freezes (e.g. retroactively snapshotting a forgotten release). CI guards (.github/workflows/docs-snapshots.yml): - Frozen snapshots under docs/v[0-9]*/ are immutable; only PRs whose title contains [docs-freeze] (i.e. release-cut PRs generated by devtools release or the manual wrapper) may modify them. - Images under docs/images/ are append-only since snapshots share a single image directory. Deleting or renaming an image breaks every historical snapshot that still references it. Restored docs/images/crewai-otel-export.png from PR #3673; it was deleted in PR #4908 but v1.10.0 / v1.10.1 snapshots still reference it. Restoring instead of editing the snapshots preserves historical rendering fidelity and validates the new append-only rule retroactively. Tests: - lib/devtools/tests/test_docs_versioning.py covers the freeze: file copy, openapi rewrite, version insertion, default demotion, redirect upserts, per-section redirect rewriting, idempotency, and invalid inputs. Verified locally with mintlify broken-links: 0 broken links across the full site (Edge + 16 frozen versions, 4 locales). AGENTS.md (repo root) is the contributor guide for the new model; RELEASING.md is the release-cut runbook; README's Contribution section links to both. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7bb9bc7 commit 93dafe2

15,793 files changed

Lines changed: 3236520 additions & 16361 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Docs Snapshots Guard
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "docs/**"
7+
8+
permissions:
9+
contents: read
10+
pull-requests: read
11+
12+
jobs:
13+
guard:
14+
name: Protect frozen snapshots and append-only assets
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Determine merge base
22+
id: base
23+
run: |
24+
base_sha="$(git merge-base "origin/${{ github.event.pull_request.base.ref }}" HEAD)"
25+
echo "sha=$base_sha" >> "$GITHUB_OUTPUT"
26+
27+
- name: Detect escape-hatch label
28+
id: escape
29+
env:
30+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
PR_NUMBER: ${{ github.event.pull_request.number }}
32+
PR_TITLE: ${{ github.event.pull_request.title }}
33+
run: |
34+
# The [docs-freeze] marker (in the PR title) is the only way to
35+
# legitimately modify frozen snapshots or remove published assets.
36+
# Detect it from the title since the workflow runs on
37+
# pull_request (not pull_request_target) and can't always read
38+
# labels reliably.
39+
if [[ "$PR_TITLE" == *"[docs-freeze]"* ]]; then
40+
echo "allowed=true" >> "$GITHUB_OUTPUT"
41+
else
42+
echo "allowed=false" >> "$GITHUB_OUTPUT"
43+
fi
44+
45+
- name: Guard frozen snapshots
46+
env:
47+
ALLOWED: ${{ steps.escape.outputs.allowed }}
48+
BASE_SHA: ${{ steps.base.outputs.sha }}
49+
run: |
50+
set -euo pipefail
51+
# Anything under docs/v<X.Y.Z>/ is a frozen release snapshot and
52+
# must not change after the release-cut PR that introduced it.
53+
# The release-cut PR uses the [docs-freeze] title prefix to opt
54+
# out of this guard. ``docs/v[0-9]*/**`` is the defensive form so
55+
# we never catch a hypothetical ``docs/vendor/`` etc.
56+
violations="$(git diff --name-only --diff-filter=AMDRT \
57+
"$BASE_SHA"..HEAD -- 'docs/v[0-9]*/**' || true)"
58+
59+
if [[ -z "$violations" ]]; then
60+
echo "OK: no changes under docs/v*/"
61+
exit 0
62+
fi
63+
64+
if [[ "$ALLOWED" == "true" ]]; then
65+
echo "OK: [docs-freeze] PR is allowed to touch docs/v*/:"
66+
echo "$violations"
67+
exit 0
68+
fi
69+
70+
echo "::error::This PR modifies frozen release snapshots under docs/v*/."
71+
echo "Frozen snapshots are immutable. To intentionally edit a snapshot"
72+
echo "(e.g. a release-cut PR generated by 'devtools release' or the"
73+
echo "manual 'scripts/docs/freeze_current_edge.py' wrapper), prefix"
74+
echo "the PR title with [docs-freeze]."
75+
echo
76+
echo "Offending files:"
77+
echo "$violations"
78+
exit 1
79+
80+
- name: Guard append-only images
81+
env:
82+
ALLOWED: ${{ steps.escape.outputs.allowed }}
83+
BASE_SHA: ${{ steps.base.outputs.sha }}
84+
run: |
85+
set -euo pipefail
86+
# Deleting or renaming an image breaks every frozen snapshot that
87+
# still references it (snapshots reuse docs/images/ at the docs
88+
# root). Only [docs-freeze] PRs are allowed to do that.
89+
deletions="$(git diff --name-only --diff-filter=DR \
90+
"$BASE_SHA"..HEAD -- 'docs/images/**' || true)"
91+
92+
if [[ -z "$deletions" ]]; then
93+
echo "OK: no images deleted or renamed."
94+
exit 0
95+
fi
96+
97+
if [[ "$ALLOWED" == "true" ]]; then
98+
echo "OK: [docs-freeze] PR is allowed to delete/rename images:"
99+
echo "$deletions"
100+
exit 0
101+
fi
102+
103+
echo "::error::This PR deletes or renames files under docs/images/."
104+
echo "Images are append-only because frozen snapshots in docs/v*/"
105+
echo "share a single docs/images/ directory and would break if an"
106+
echo "asset they reference disappears or moves."
107+
echo
108+
echo "If the asset is wrong, add a new file with a new name and"
109+
echo "reference the new name in Edge (docs/edge/<lang>/...). Leave"
110+
echo "the old file in place so historical snapshots keep rendering."
111+
echo
112+
echo "Offending files:"
113+
echo "$deletions"
114+
exit 1

AGENTS.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Docs contributor guide
2+
3+
The `docs/` directory is published at [docs.crewai.com](https://docs.crewai.com)
4+
by [Mintlify](https://www.mintlify.com/). Mintlify watches `docs/docs.json`
5+
and the MDX files referenced from it.
6+
7+
## TL;DR for editing docs
8+
9+
- Edit MDX under `docs/edge/<lang>/...` (e.g. `docs/edge/en/concepts/agents.mdx`).
10+
- Your change ships under the **Edge** version selector the moment it merges
11+
to `main`. Edge follows `main` and is the channel for unreleased work.
12+
- On release cut, the current Edge state is frozen into `docs/v<X.Y.Z>/` and
13+
that snapshot becomes the new default version in the selector (tag:
14+
`Latest`). Canonical URLs (`/<lang>/...`) auto-redirect to the new default.
15+
- Never modify files under `docs/v*/`. Those are frozen release snapshots
16+
and the `docs-snapshots` CI guard rejects writes. The only exception is a
17+
release-cut PR (auto-generated by `devtools release` or the manual
18+
`scripts/docs/freeze_current_edge.py` wrapper), which uses a
19+
`[docs-freeze]` title prefix to opt out.
20+
- Never delete or rename files under `docs/images/`. Images are append-only.
21+
See [Images](#images) below.
22+
23+
## The version model
24+
25+
The site has one rolling channel (Edge) plus one frozen snapshot per
26+
release.
27+
28+
```
29+
docs/
30+
edge/ <-- Edge sources (you edit here)
31+
en/...
32+
pt-BR/ ko/ ar/
33+
enterprise-api.*.yaml
34+
35+
v1.14.7/ <-- frozen snapshot of v1.14.7
36+
en/...
37+
pt-BR/ ko/ ar/
38+
enterprise-api.*.yaml
39+
v1.14.6/...
40+
...
41+
42+
images/ <-- shared, append-only
43+
docs.json <-- Mintlify config: navigation + redirects
44+
```
45+
46+
`docs/docs.json` lists one navigation block per version per language. Edge
47+
points at `docs/edge/<lang>/...`; every other version points at its own
48+
`docs/v<X.Y.Z>/<lang>/...` subtree. Mintlify scopes both the sidebar and the
49+
in-site search to whichever version the reader selects, so picking
50+
`v1.10.0` genuinely shows the v1.10.0 docs (and only those).
51+
52+
### URLs and canonical redirects
53+
54+
Each Mintlify version corresponds to its own URL prefix:
55+
56+
- Edge: `/edge/<lang>/<page>` (e.g. `/edge/en/concepts/agents`)
57+
- Frozen: `/v<X.Y.Z>/<lang>/<page>` (e.g. `/v1.14.7/en/concepts/agents`)
58+
59+
External links to the old, unversioned `/<lang>/<page>` URLs would 404 under
60+
this layout. To keep them working, `docs.json` ships wildcard redirects:
61+
62+
```jsonc
63+
{ "source": "/en/:slug*", "destination": "/v1.14.7/en/:slug*", "permanent": false }
64+
```
65+
66+
The release-cut step rewrites the destination on every release so canonical
67+
`/<lang>/...` URLs always resolve to the latest stable docs.
68+
69+
## Lifecycle
70+
71+
1. **During development.** You add or edit pages under
72+
`docs/edge/<lang>/...` in normal PRs. They land in Edge as soon as the PR
73+
merges. Both `/edge/<lang>/<page>` and the version selector's `Edge` entry
74+
reflect the change immediately.
75+
2. **Release cut.** The release engineer runs `devtools release X.Y.Z`. As
76+
part of that flow the CLI opens a `[docs-freeze]` PR that copies Edge into
77+
`docs/v<X.Y.Z>/`, rewrites internal OpenAPI references, updates
78+
`docs/docs.json` to make `v<X.Y.Z>` the new default + `Latest`, and rewires
79+
the canonical-URL redirects to the new default. The PR must merge before
80+
the tag and PyPI publish run.
81+
3. **After release.** Edge keeps rolling. Patch fixes to the just-released
82+
docs go into Edge and ship with the next release. We do not back-edit
83+
frozen snapshots.
84+
85+
See [`RELEASING.md`](RELEASING.md) for the full release runbook.
86+
87+
## Images
88+
89+
Snapshots share a single `docs/images/` directory. If an image is deleted
90+
or renamed, every frozen snapshot that referenced it breaks. So the rule
91+
is:
92+
93+
- Adding new images is always fine.
94+
- Deleting or renaming an existing image fails CI unless the PR is a
95+
`[docs-freeze]` release-cut PR.
96+
- If an asset is wrong, add a new file with a new name and reference the
97+
new name in the Edge MDX (`docs/edge/<lang>/...`). Leave the old file
98+
alone.
99+
100+
## Local preview
101+
102+
Install the Mintlify CLI and run from `docs/`:
103+
104+
```bash
105+
npm i -g mintlify
106+
mintlify dev
107+
```
108+
109+
Use the version selector at the top of the rendered page to switch between
110+
Edge and frozen versions.
111+
112+
To check links across every version:
113+
114+
```bash
115+
mintlify broken-links
116+
```
117+
118+
CI runs the broken-links check on every PR that touches `docs/**` via
119+
[`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml).
120+
121+
## Scripts
122+
123+
- `scripts/docs/freeze_historical_versions.py` — one-time migration that
124+
reconstructed `docs/v1.10.0/` through `docs/v1.14.7/` from git tags. You
125+
should not need to run this again.
126+
- `scripts/docs/prefix_version_paths.py` — one-time migration that switched
127+
`docs/docs.json` to directory-based versioning, inserted Edge, and added
128+
the canonical-URL redirects. You should not need to run this again.
129+
- `scripts/docs/freeze_current_edge.py` — thin CLI wrapper around
130+
`crewai_devtools.docs_versioning.freeze`. `devtools release` calls the
131+
same module during its docs PR step; this script is the manual escape
132+
hatch (e.g. retroactively freezing a forgotten release).
133+
134+
## CI guards
135+
136+
- [`.github/workflows/docs-snapshots.yml`](.github/workflows/docs-snapshots.yml)
137+
enforces the two rules above (frozen snapshots immutable, images
138+
append-only). Both checks accept the `[docs-freeze]` PR-title escape
139+
hatch.
140+
- [`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml)
141+
runs `mintlify broken-links` against the whole site, so adding a new
142+
page or moving a snapshot file that breaks a link will fail CI.

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,19 @@ CrewAI is open-source and we welcome contributions. If you're looking to contrib
601601
- Send a pull request.
602602
- We appreciate your input!
603603

604+
### Contributing to the docs
605+
606+
The site at [docs.crewai.com](https://docs.crewai.com) is published from
607+
`docs/` by [Mintlify](https://www.mintlify.com/). The docs use directory-based
608+
versioning: edits to `docs/edge/<lang>/...` (e.g.
609+
`docs/edge/en/concepts/agents.mdx`) land under the **Edge** version selector
610+
immediately and are frozen into a new versioned snapshot under
611+
`docs/v<X.Y.Z>/` at the next release cut. Frozen snapshots are immutable — CI
612+
rejects PRs that modify them without a `[docs-freeze]` title prefix. The
613+
release CLI (`devtools release`) handles the freeze automatically; see
614+
[`AGENTS.md`](AGENTS.md) for the full contributor guide and
615+
[`RELEASING.md`](RELEASING.md) for the release-cut runbook.
616+
604617
### Installing Dependencies
605618

606619
```bash

RELEASING.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Releasing crewai
2+
3+
The release CLI (`devtools release`) drives the full end-to-end flow,
4+
including the docs-versioning step that has to happen at every release cut.
5+
This runbook is the human-facing summary; the canonical implementation lives
6+
in [`lib/devtools/src/crewai_devtools/cli.py`](lib/devtools/src/crewai_devtools/cli.py).
7+
8+
## Why a docs-versioning step exists
9+
10+
Until the v1.15 series, `docs/docs.json` had 16 "versions" in its selector
11+
but every one of them rendered the same single-source MDX files. Picking
12+
v1.10.0 in the dropdown silently served the latest docs from `main`. We
13+
fixed that by adopting Mintlify's directory-based versioning: each release
14+
gets its own frozen snapshot under `docs/v<X.Y.Z>/`, an `Edge` selector
15+
renders the rolling `main` state (`docs/edge/...`) for unreleased work,
16+
and the canonical `/<lang>/...` URLs redirect to whichever version is
17+
currently `default` + `Latest`.
18+
19+
The release-cut step keeps this model honest. Skip it and the new release
20+
will not appear in the selector and the canonical URLs will keep pointing
21+
at the previous default — i.e. users following a stale link land on docs
22+
that don't describe the version they just installed.
23+
24+
## Happy path: `devtools release`
25+
26+
For a normal release:
27+
28+
```bash
29+
devtools release 1.15.0
30+
```
31+
32+
This runs the full pipeline:
33+
34+
1. Phase 1 — version bump PR, polls until merged.
35+
2. Phase 2 — generates AI release notes, then opens the docs PR titled
36+
`[docs-freeze] docs: snapshot and changelog for v1.15.0`. That PR:
37+
- prepends a release entry to `docs/edge/<lang>/changelog.mdx` for every
38+
supported locale,
39+
- copies `docs/edge/` into `docs/v1.15.0/`,
40+
- rewrites `openapi:` MDX refs inside the snapshot so each frozen page
41+
reads its own OpenAPI YAML instead of the live one,
42+
- inserts a `v1.15.0` entry into every language block in
43+
`docs/docs.json`, marks it `default: true` with tag `"Latest"`, and
44+
demotes the previous default,
45+
- rewires the wildcard redirects so `/<lang>/:slug*` lands on
46+
`/v1.15.0/<lang>/:slug*`.
47+
48+
The CLI polls until you (or another reviewer) merge the docs PR.
49+
3. Phase 2 (cont.) — tags `main`, creates the GitHub release, triggers
50+
`publish.yml`, and bumps the deployment_test repo.
51+
4. Phase 3 — clones the enterprise repo, bumps versions, opens its bump
52+
PR, polls, then tags + releases enterprise.
53+
54+
The `[docs-freeze]` PR title prefix is what the
55+
[`docs-snapshots.yml`](.github/workflows/docs-snapshots.yml) CI guard reads
56+
to allow the snapshot directory and any image deletions to land. The CLI
57+
sets it automatically.
58+
59+
Pre-releases (e.g. `1.15.0a1`) skip the snapshot step — they ride Edge —
60+
and the docs PR title omits the `[docs-freeze]` prefix.
61+
62+
## Manual escape hatch: freeze script
63+
64+
If you ever need to freeze without going through the full release flow (e.g.
65+
retroactively snapshotting a release that shipped without docs versioning,
66+
or testing the freeze locally):
67+
68+
```bash
69+
python scripts/docs/freeze_current_edge.py 1.15.0
70+
```
71+
72+
This is a thin wrapper around the same `crewai_devtools.docs_versioning.freeze`
73+
function used by `devtools release`. It updates the snapshot + `docs.json`
74+
+ redirects but does not touch changelogs, open a PR, or coordinate with the
75+
rest of the release flow. Pair it with a manual PR titled
76+
`[docs-freeze] snapshot docs for v1.15.0`.
77+
78+
## Lifecycle reminders
79+
80+
- Edge (`docs/edge/...`) always reflects `main`. After a release cut, fixes
81+
to the just-released docs go into Edge as normal PRs and ship with the
82+
next release.
83+
- We do not back-port docs fixes into older frozen snapshots. If a fix
84+
matters enough to publish on an older version, it is a deliberate
85+
`[docs-freeze]` PR — treat that as an exception.
86+
- The freeze function is idempotent. If you have to re-run it (e.g. you
87+
pushed a docs fix between snapshotting and merging the PR), delete the
88+
partially-built `docs/v<X.Y.Z>/` directory first and run again.
89+
90+
## Troubleshooting
91+
92+
- **The freeze step warned that the snapshot was already current.** Either
93+
someone else already cut this version, or a previous run left a stale
94+
`docs/v<X.Y.Z>/` directory. Inspect it, then either keep going or delete
95+
the directory and re-run.
96+
- **CI fails on a non-`[docs-freeze]` PR claiming you modified frozen
97+
snapshots.** Check the diff — almost always this is an accidental edit
98+
under `docs/v*/`. Move the change to the matching path under
99+
`docs/edge/<lang>/...` instead. If you truly need to edit a frozen
100+
snapshot, re-title the PR with the `[docs-freeze]` prefix and document
101+
the reason in the PR description.
102+
- **CI fails on a non-`[docs-freeze]` PR claiming you deleted an image.**
103+
Add a new image under a new filename and reference that from Edge. Leave
104+
the old file in place so older snapshots keep rendering.

0 commit comments

Comments
 (0)