Compare commits

...

1 Commits

Author SHA1 Message Date
Lucas Gomide
93dafe2637 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>
2026-06-17 11:08:45 -03:00
15793 changed files with 3237032 additions and 16873 deletions

114
.github/workflows/docs-snapshots.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
name: Docs Snapshots Guard
on:
pull_request:
paths:
- "docs/**"
permissions:
contents: read
pull-requests: read
jobs:
guard:
name: Protect frozen snapshots and append-only assets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Determine merge base
id: base
run: |
base_sha="$(git merge-base "origin/${{ github.event.pull_request.base.ref }}" HEAD)"
echo "sha=$base_sha" >> "$GITHUB_OUTPUT"
- name: Detect escape-hatch label
id: escape
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
# The [docs-freeze] marker (in the PR title) is the only way to
# legitimately modify frozen snapshots or remove published assets.
# Detect it from the title since the workflow runs on
# pull_request (not pull_request_target) and can't always read
# labels reliably.
if [[ "$PR_TITLE" == *"[docs-freeze]"* ]]; then
echo "allowed=true" >> "$GITHUB_OUTPUT"
else
echo "allowed=false" >> "$GITHUB_OUTPUT"
fi
- name: Guard frozen snapshots
env:
ALLOWED: ${{ steps.escape.outputs.allowed }}
BASE_SHA: ${{ steps.base.outputs.sha }}
run: |
set -euo pipefail
# Anything under docs/v<X.Y.Z>/ is a frozen release snapshot and
# must not change after the release-cut PR that introduced it.
# The release-cut PR uses the [docs-freeze] title prefix to opt
# out of this guard. ``docs/v[0-9]*/**`` is the defensive form so
# we never catch a hypothetical ``docs/vendor/`` etc.
violations="$(git diff --name-only --diff-filter=AMDRT \
"$BASE_SHA"..HEAD -- 'docs/v[0-9]*/**' || true)"
if [[ -z "$violations" ]]; then
echo "OK: no changes under docs/v*/"
exit 0
fi
if [[ "$ALLOWED" == "true" ]]; then
echo "OK: [docs-freeze] PR is allowed to touch docs/v*/:"
echo "$violations"
exit 0
fi
echo "::error::This PR modifies frozen release snapshots under docs/v*/."
echo "Frozen snapshots are immutable. To intentionally edit a snapshot"
echo "(e.g. a release-cut PR generated by 'devtools release' or the"
echo "manual 'scripts/docs/freeze_current_edge.py' wrapper), prefix"
echo "the PR title with [docs-freeze]."
echo
echo "Offending files:"
echo "$violations"
exit 1
- name: Guard append-only images
env:
ALLOWED: ${{ steps.escape.outputs.allowed }}
BASE_SHA: ${{ steps.base.outputs.sha }}
run: |
set -euo pipefail
# Deleting or renaming an image breaks every frozen snapshot that
# still references it (snapshots reuse docs/images/ at the docs
# root). Only [docs-freeze] PRs are allowed to do that.
deletions="$(git diff --name-only --diff-filter=DR \
"$BASE_SHA"..HEAD -- 'docs/images/**' || true)"
if [[ -z "$deletions" ]]; then
echo "OK: no images deleted or renamed."
exit 0
fi
if [[ "$ALLOWED" == "true" ]]; then
echo "OK: [docs-freeze] PR is allowed to delete/rename images:"
echo "$deletions"
exit 0
fi
echo "::error::This PR deletes or renames files under docs/images/."
echo "Images are append-only because frozen snapshots in docs/v*/"
echo "share a single docs/images/ directory and would break if an"
echo "asset they reference disappears or moves."
echo
echo "If the asset is wrong, add a new file with a new name and"
echo "reference the new name in Edge (docs/edge/<lang>/...). Leave"
echo "the old file in place so historical snapshots keep rendering."
echo
echo "Offending files:"
echo "$deletions"
exit 1

142
AGENTS.md Normal file
View File

@@ -0,0 +1,142 @@
# Docs contributor guide
The `docs/` directory is published at [docs.crewai.com](https://docs.crewai.com)
by [Mintlify](https://www.mintlify.com/). Mintlify watches `docs/docs.json`
and the MDX files referenced from it.
## TL;DR for editing docs
- Edit MDX under `docs/edge/<lang>/...` (e.g. `docs/edge/en/concepts/agents.mdx`).
- Your change ships under the **Edge** version selector the moment it merges
to `main`. Edge follows `main` and is the channel for unreleased work.
- On release cut, the current Edge state is frozen into `docs/v<X.Y.Z>/` and
that snapshot becomes the new default version in the selector (tag:
`Latest`). Canonical URLs (`/<lang>/...`) auto-redirect to the new default.
- Never modify files under `docs/v*/`. Those are frozen release snapshots
and the `docs-snapshots` CI guard rejects writes. The only exception is a
release-cut PR (auto-generated by `devtools release` or the manual
`scripts/docs/freeze_current_edge.py` wrapper), which uses a
`[docs-freeze]` title prefix to opt out.
- Never delete or rename files under `docs/images/`. Images are append-only.
See [Images](#images) below.
## The version model
The site has one rolling channel (Edge) plus one frozen snapshot per
release.
```
docs/
edge/ <-- Edge sources (you edit here)
en/...
pt-BR/ ko/ ar/
enterprise-api.*.yaml
v1.14.7/ <-- frozen snapshot of v1.14.7
en/...
pt-BR/ ko/ ar/
enterprise-api.*.yaml
v1.14.6/...
...
images/ <-- shared, append-only
docs.json <-- Mintlify config: navigation + redirects
```
`docs/docs.json` lists one navigation block per version per language. Edge
points at `docs/edge/<lang>/...`; every other version points at its own
`docs/v<X.Y.Z>/<lang>/...` subtree. Mintlify scopes both the sidebar and the
in-site search to whichever version the reader selects, so picking
`v1.10.0` genuinely shows the v1.10.0 docs (and only those).
### URLs and canonical redirects
Each Mintlify version corresponds to its own URL prefix:
- Edge: `/edge/<lang>/<page>` (e.g. `/edge/en/concepts/agents`)
- Frozen: `/v<X.Y.Z>/<lang>/<page>` (e.g. `/v1.14.7/en/concepts/agents`)
External links to the old, unversioned `/<lang>/<page>` URLs would 404 under
this layout. To keep them working, `docs.json` ships wildcard redirects:
```jsonc
{ "source": "/en/:slug*", "destination": "/v1.14.7/en/:slug*", "permanent": false }
```
The release-cut step rewrites the destination on every release so canonical
`/<lang>/...` URLs always resolve to the latest stable docs.
## Lifecycle
1. **During development.** You add or edit pages under
`docs/edge/<lang>/...` in normal PRs. They land in Edge as soon as the PR
merges. Both `/edge/<lang>/<page>` and the version selector's `Edge` entry
reflect the change immediately.
2. **Release cut.** The release engineer runs `devtools release X.Y.Z`. As
part of that flow the CLI opens a `[docs-freeze]` PR that copies Edge into
`docs/v<X.Y.Z>/`, rewrites internal OpenAPI references, updates
`docs/docs.json` to make `v<X.Y.Z>` the new default + `Latest`, and rewires
the canonical-URL redirects to the new default. The PR must merge before
the tag and PyPI publish run.
3. **After release.** Edge keeps rolling. Patch fixes to the just-released
docs go into Edge and ship with the next release. We do not back-edit
frozen snapshots.
See [`RELEASING.md`](RELEASING.md) for the full release runbook.
## Images
Snapshots share a single `docs/images/` directory. If an image is deleted
or renamed, every frozen snapshot that referenced it breaks. So the rule
is:
- Adding new images is always fine.
- Deleting or renaming an existing image fails CI unless the PR is a
`[docs-freeze]` release-cut PR.
- If an asset is wrong, add a new file with a new name and reference the
new name in the Edge MDX (`docs/edge/<lang>/...`). Leave the old file
alone.
## Local preview
Install the Mintlify CLI and run from `docs/`:
```bash
npm i -g mintlify
mintlify dev
```
Use the version selector at the top of the rendered page to switch between
Edge and frozen versions.
To check links across every version:
```bash
mintlify broken-links
```
CI runs the broken-links check on every PR that touches `docs/**` via
[`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml).
## Scripts
- `scripts/docs/freeze_historical_versions.py` — one-time migration that
reconstructed `docs/v1.10.0/` through `docs/v1.14.7/` from git tags. You
should not need to run this again.
- `scripts/docs/prefix_version_paths.py` — one-time migration that switched
`docs/docs.json` to directory-based versioning, inserted Edge, and added
the canonical-URL redirects. You should not need to run this again.
- `scripts/docs/freeze_current_edge.py` — thin CLI wrapper around
`crewai_devtools.docs_versioning.freeze`. `devtools release` calls the
same module during its docs PR step; this script is the manual escape
hatch (e.g. retroactively freezing a forgotten release).
## CI guards
- [`.github/workflows/docs-snapshots.yml`](.github/workflows/docs-snapshots.yml)
enforces the two rules above (frozen snapshots immutable, images
append-only). Both checks accept the `[docs-freeze]` PR-title escape
hatch.
- [`.github/workflows/docs-broken-links.yml`](.github/workflows/docs-broken-links.yml)
runs `mintlify broken-links` against the whole site, so adding a new
page or moving a snapshot file that breaks a link will fail CI.

View File

@@ -601,6 +601,19 @@ CrewAI is open-source and we welcome contributions. If you're looking to contrib
- Send a pull request.
- We appreciate your input!
### Contributing to the docs
The site at [docs.crewai.com](https://docs.crewai.com) is published from
`docs/` by [Mintlify](https://www.mintlify.com/). The docs use directory-based
versioning: edits to `docs/edge/<lang>/...` (e.g.
`docs/edge/en/concepts/agents.mdx`) land under the **Edge** version selector
immediately and are frozen into a new versioned snapshot under
`docs/v<X.Y.Z>/` at the next release cut. Frozen snapshots are immutable — CI
rejects PRs that modify them without a `[docs-freeze]` title prefix. The
release CLI (`devtools release`) handles the freeze automatically; see
[`AGENTS.md`](AGENTS.md) for the full contributor guide and
[`RELEASING.md`](RELEASING.md) for the release-cut runbook.
### Installing Dependencies
```bash

104
RELEASING.md Normal file
View File

@@ -0,0 +1,104 @@
# Releasing crewai
The release CLI (`devtools release`) drives the full end-to-end flow,
including the docs-versioning step that has to happen at every release cut.
This runbook is the human-facing summary; the canonical implementation lives
in [`lib/devtools/src/crewai_devtools/cli.py`](lib/devtools/src/crewai_devtools/cli.py).
## Why a docs-versioning step exists
Until the v1.15 series, `docs/docs.json` had 16 "versions" in its selector
but every one of them rendered the same single-source MDX files. Picking
v1.10.0 in the dropdown silently served the latest docs from `main`. We
fixed that by adopting Mintlify's directory-based versioning: each release
gets its own frozen snapshot under `docs/v<X.Y.Z>/`, an `Edge` selector
renders the rolling `main` state (`docs/edge/...`) for unreleased work,
and the canonical `/<lang>/...` URLs redirect to whichever version is
currently `default` + `Latest`.
The release-cut step keeps this model honest. Skip it and the new release
will not appear in the selector and the canonical URLs will keep pointing
at the previous default — i.e. users following a stale link land on docs
that don't describe the version they just installed.
## Happy path: `devtools release`
For a normal release:
```bash
devtools release 1.15.0
```
This runs the full pipeline:
1. Phase 1 — version bump PR, polls until merged.
2. Phase 2 — generates AI release notes, then opens the docs PR titled
`[docs-freeze] docs: snapshot and changelog for v1.15.0`. That PR:
- prepends a release entry to `docs/edge/<lang>/changelog.mdx` for every
supported locale,
- copies `docs/edge/` into `docs/v1.15.0/`,
- rewrites `openapi:` MDX refs inside the snapshot so each frozen page
reads its own OpenAPI YAML instead of the live one,
- inserts a `v1.15.0` entry into every language block in
`docs/docs.json`, marks it `default: true` with tag `"Latest"`, and
demotes the previous default,
- rewires the wildcard redirects so `/<lang>/:slug*` lands on
`/v1.15.0/<lang>/:slug*`.
The CLI polls until you (or another reviewer) merge the docs PR.
3. Phase 2 (cont.) — tags `main`, creates the GitHub release, triggers
`publish.yml`, and bumps the deployment_test repo.
4. Phase 3 — clones the enterprise repo, bumps versions, opens its bump
PR, polls, then tags + releases enterprise.
The `[docs-freeze]` PR title prefix is what the
[`docs-snapshots.yml`](.github/workflows/docs-snapshots.yml) CI guard reads
to allow the snapshot directory and any image deletions to land. The CLI
sets it automatically.
Pre-releases (e.g. `1.15.0a1`) skip the snapshot step — they ride Edge —
and the docs PR title omits the `[docs-freeze]` prefix.
## Manual escape hatch: freeze script
If you ever need to freeze without going through the full release flow (e.g.
retroactively snapshotting a release that shipped without docs versioning,
or testing the freeze locally):
```bash
python scripts/docs/freeze_current_edge.py 1.15.0
```
This is a thin wrapper around the same `crewai_devtools.docs_versioning.freeze`
function used by `devtools release`. It updates the snapshot + `docs.json`
+ redirects but does not touch changelogs, open a PR, or coordinate with the
rest of the release flow. Pair it with a manual PR titled
`[docs-freeze] snapshot docs for v1.15.0`.
## Lifecycle reminders
- Edge (`docs/edge/...`) always reflects `main`. After a release cut, fixes
to the just-released docs go into Edge as normal PRs and ship with the
next release.
- We do not back-port docs fixes into older frozen snapshots. If a fix
matters enough to publish on an older version, it is a deliberate
`[docs-freeze]` PR — treat that as an exception.
- The freeze function is idempotent. If you have to re-run it (e.g. you
pushed a docs fix between snapshotting and merging the PR), delete the
partially-built `docs/v<X.Y.Z>/` directory first and run again.
## Troubleshooting
- **The freeze step warned that the snapshot was already current.** Either
someone else already cut this version, or a previous run left a stale
`docs/v<X.Y.Z>/` directory. Inspect it, then either keep going or delete
the directory and re-run.
- **CI fails on a non-`[docs-freeze]` PR claiming you modified frozen
snapshots.** Check the diff — almost always this is an accidental edit
under `docs/v*/`. Move the change to the matching path under
`docs/edge/<lang>/...` instead. If you truly need to edit a frozen
snapshot, re-title the PR with the `[docs-freeze]` prefix and document
the reason in the PR description.
- **CI fails on a non-`[docs-freeze]` PR claiming you deleted an image.**
Add a new image under a new filename and reference that from Edge. Leave
the old file in place so older snapshots keep rendering.

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More