.PHONY: all check-mdx dev build export htmltest export-htmltest format lint test install install_vale clean lint_md lint_md_fix lint_prose broken-links broken-links-with-anchors format-check code-snippets test-code-samples update-code-sample-traces check-cross-refs check-legacy-links skills # Default target all: help dev: @echo "Starting development mode..." npm install PYTHONPATH=$(CURDIR) uv run pipeline dev build: @echo "Building documentation..." npm install PYTHONPATH=$(CURDIR) uv run pipeline build # Offline zip via Mintlify (https://www.mintlify.com/docs/deploy/export). # Must run from build/: docs.json paths are oss/python/... and oss/javascript/... but sources live under src/oss/... until the pipeline emits build/oss/{python,javascript}/... # Default mint output when run from build/ is build/export.zip. Override with MINT_EXPORT_ARGS='--output other.zip' (path relative to build/) and matching EXPORT_ZIP=build/other.zip for htmltest. # Requires: recent mint CLI (mint export), Node LTS 20/22 (Node 25+ unsupported), Enterprise Mintlify plan. export: build @command -v mint >/dev/null 2>&1 || { echo "Error: mint not installed. Run: npm install -g mint@latest"; exit 1; } @mint help 2>&1 | grep -q 'mint export' || { \ echo "Error: 'mint export' is missing from mint $$(mint --version 2>/dev/null || echo unknown)."; \ echo "Upgrade: npm install -g mint@latest"; \ echo "Also needs Node LTS (20 or 22; Node 25 is unsupported) and an Enterprise Mintlify plan."; \ exit 1; \ } @NODE_MAJOR=$$(node -p "process.versions.node.split('.')[0]"); \ if [ "$$NODE_MAJOR" -ge 25 ]; then \ echo "Error: mint does not support Node $$NODE_MAJOR. Switch to Node 20 or 22 (e.g. nvm use 22), install mint for that Node (npm install -g mint@latest), then retry."; \ exit 1; \ fi @cd build && mint export $(MINT_EXPORT_ARGS) # Zip produced by make export (default Mintlify name: export.zip in build/). Override if you used --output. EXPORT_ZIP ?= build/export.zip # Unpacked copy for htmltest (gitignored under build/). HTMLTEST_UNPACK_DIR ?= build/mint-export-htmltest-unpacked # Extra htmltest CLI flags (config already checks external URLs only). Example: HTMLTEST_ARGS='-l 1' HTMLTEST_ARGS ?= # Unzip EXPORT_ZIP and run htmltest (https://github.com/wjdp/htmltest). Run after make export. # Uses htmltest-mint-export.yml: external URLs only (mint export omits many pages, so internals are noisy). htmltest: @command -v htmltest >/dev/null 2>&1 || { echo "Error: htmltest not found. Install: brew install htmltest OR curl https://htmltest.wjdp.uk | sudo bash -s -- -b /usr/local/bin"; exit 1; } @command -v unzip >/dev/null 2>&1 || { echo "Error: unzip not found."; exit 1; } @test -f $(EXPORT_ZIP) || { echo "Error: $(EXPORT_ZIP) not found. Run make export first, or set EXPORT_ZIP to your mint export zip path."; exit 1; } mkdir -p $(HTMLTEST_UNPACK_DIR) unzip -q -o $(EXPORT_ZIP) -d $(HTMLTEST_UNPACK_DIR) @bash -ec 'ROOT="$(HTMLTEST_UNPACK_DIR)"; \ COUNT=$$(find "$$ROOT" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l | tr -d " "); \ if [ "$$COUNT" -eq 1 ]; then \ ONLY=$$(find "$$ROOT" -mindepth 1 -maxdepth 1 2>/dev/null | head -n 1); \ if [ -d "$$ONLY" ]; then ROOT="$$ONLY"; fi; \ fi; \ echo "htmltest root: $$ROOT"; \ htmltest $(HTMLTEST_ARGS) -c "$(CURDIR)/htmltest-mint-export.yml" "$$ROOT"' # make export then make htmltest (sequential; use this for a one-shot check). export-htmltest: @$(MAKE) export @$(MAKE) htmltest # Define a variable for the test file path. TEST_FILE ?= tests/unit_tests # Define a variable for Python and notebook files. PYTHON_FILES=. lint: uv run ruff format $(PYTHON_FILES) --diff uv run ruff check $(PYTHON_FILES) uv run ty check uv run codespell src format: uv run ruff format $(PYTHON_FILES) uv run ruff check --fix $(PYTHON_FILES) # Check formatting without applying changes (for CI) format-check: uv run ruff format $(PYTHON_FILES) --check --diff uv run ruff check $(PYTHON_FILES) lint_md: @echo "Linting markdown files..." @if command -v markdownlint >/dev/null 2>&1; then \ find src -name "*.md" -o -name "*.mdx" | xargs markdownlint; \ else \ echo "markdownlint not found. Install with: npm install -g markdownlint-cli or VSCode extension"; \ exit 1; \ fi lint_md_fix: @echo "Linting and fixing markdown files..." @if command -v markdownlint >/dev/null 2>&1; then \ find src -name "*.md" -o -name "*.mdx" | xargs markdownlint --fix; \ else \ echo "markdownlint not found. Install with: npm install -g markdownlint-cli or VSCode extension"; \ exit 1; \ fi VALE_BIN ?= .bin/vale # Single source of truth for the Vale pin is .mise.toml. Override on the # command line only for a one-off test: make lint_prose VALE_VERSION=3.16.0 VALE_VERSION ?= $(shell sed -n 's/^[[:space:]]*vale[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .mise.toml | head -n 1) install_vale: @bash scripts/install-vale.sh "$(VALE_BIN)" "$(VALE_VERSION)" lint_prose: @echo "Linting prose with Vale..." @bash scripts/install-vale.sh "$(VALE_BIN)" "$(VALE_VERSION)" @if [ -n "$(FILES)" ]; then \ "$(VALE_BIN)" --glob='!{**/node_modules/**,src/code-samples/**}' $(FILES); \ else \ "$(VALE_BIN)" --glob='!{**/node_modules/**,src/code-samples/**}' src/; \ fi test: uv run pytest --disable-socket --allow-unix-socket $(TEST_FILE) -vv install: @echo "Installing all dependencies" uv sync --all-groups npm install @for attempt in 1 2 3; do \ if npm install -g mint@latest; then \ break; \ elif [ "$$attempt" -eq 3 ]; then \ echo "Mintlify CLI installation failed after $$attempt attempts." >&2; \ exit 1; \ fi; \ delay=$$((attempt * 10)); \ echo "Mintlify CLI installation failed; retrying in $${delay}s ($$attempt/3)..." >&2; \ sleep "$$delay"; \ done @$(MAKE) --no-print-directory skills @echo "If the docs command is not available, relaunch your shell so it picks up the docs binary." clean: @echo "Cleaning build artifacts..." @rm -rf build/ @rm -rf __pycache__/ @find . -name "*.pyc" -delete @find . -name "*.pyo" -delete @find . -name "*.pyd" -delete @find . -name "__pycache__" -type d -exec rm -rf {} + # Mintlify commands (run from build directory where final docs are generated) # broken-links: Checks for broken links, excluding OpenAPI-generated pages and snippet files # Excluded: /langsmith/agent-server-api/, /api-reference/ (Mintlify-generated at deploy, not in local build) # Excluded: entire snippets/ report sections (scripts/filter_mint_broken_links.py) # Snippet /oss/ links are absolute language-prefixed paths under # build/snippets/{python|javascript}/...; mint checks snippets as standalone files # so those look broken until inlined into a page. # --check-redirects also validates that every docs.json redirect destination resolves. # Without it, 261 redirects pointing at unversioned /oss/ paths sat broken undetected. # Reported as indented "source -> destination" lines, so the same failure test catches them. # Failure: only when filtered output still has indented link lines (real broken links we care about) # Run mint, capture output, filter exclusions. Only show output when failing. broken-links: build @command -v mint >/dev/null 2>&1 || { echo "Error: mint not installed. Run 'npm install -g mint@latest'"; exit 1; } @KATEX_MJS="$$(npm root -g 2>/dev/null)/mint/node_modules/katex/dist/katex.mjs"; \ if [ -f "$$KATEX_MJS" ] && grep -q '__VERSION__' "$$KATEX_MJS" 2>/dev/null; then \ KATEX_DIR="$$(cd "$$(dirname "$$KATEX_MJS")/.." && pwd)"; \ VERSION=$$(node -e "console.log(require('$$KATEX_DIR/package.json').version)" 2>/dev/null); \ if [ -n "$$VERSION" ]; then sed -i.bak "s/__VERSION__/\"$$VERSION\"/g" "$$KATEX_MJS" 2>/dev/null || true; fi; \ fi @cd build && mint broken-links --check-redirects 2>&1 | tee /tmp/broken-links.txt > /dev/null; \ filtered=$$(python3 ../scripts/filter_mint_broken_links.py --input /tmp/broken-links.txt); \ if echo "$$filtered" | grep -qE '^[[:space:]]+[^[:space:]]'; then \ echo "$$filtered"; echo ""; echo "❌ Broken links found"; exit 1; \ else \ echo "✅ No broken links"; \ fi broken-links-with-anchors: build @command -v mint >/dev/null 2>&1 || { echo "Error: mint not installed. Run 'npm install -g mint@latest'"; exit 1; } @KATEX_MJS="$$(npm root -g 2>/dev/null)/mint/node_modules/katex/dist/katex.mjs"; \ if [ -f "$$KATEX_MJS" ] && grep -q '__VERSION__' "$$KATEX_MJS" 2>/dev/null; then \ KATEX_DIR="$$(cd "$$(dirname "$$KATEX_MJS")/.." && pwd)"; \ VERSION=$$(node -e "console.log(require('$$KATEX_DIR/package.json').version)" 2>/dev/null); \ if [ -n "$$VERSION" ]; then sed -i.bak "s/__VERSION__/\"$$VERSION\"/g" "$$KATEX_MJS" 2>/dev/null || true; fi; \ fi @cd build && mint broken-links --check-anchors --check-redirects 2>&1 | tee /tmp/broken-links.txt > /dev/null; \ filtered=$$(python3 ../scripts/filter_mint_broken_links.py --check-anchors --input /tmp/broken-links.txt); \ if echo "$$filtered" | grep -qE '^[[:space:]]+[^[:space:]]'; then \ echo "$$filtered"; echo ""; echo "❌ Broken links found"; exit 1; \ else \ echo "✅ No broken links"; \ fi check-legacy-links: @bash scripts/check-legacy-doc-links.sh # check-mdx: Fails on JSX components that are neither built in nor imported, and on # foreign syntax such as :::note. mint exits 0 on warnings with --no-build, so count them. check-mdx: build @cd build && mint validate --no-build --disable all --enable unknown-component,foreign-syntax --format json 2>/dev/null | \ python3 -c 'import json, sys; r = json.load(sys.stdin); [print("%s:%s: %s" % (d["file"], d["line"], d["message"])) for d in r["diagnostics"]]; sys.exit(bool(r["diagnostics"]))' check-openapi: build @echo "Checking openapi spec validity" @command -v mint >/dev/null 2>&1 || { echo "Error: mint is not installed. Run 'npm install -g mint@latest'"; exit 1; } @cd build && output=$$(mint openapi-check langsmith/agent-server-openapi.json) && echo "$$output" # Extract code snippets from src/code-samples (line-based, Bluehawk-compatible tags) code-snippets: @echo "Extracting code snippets..." @mkdir -p src/code-samples-generated @PYTHONPATH=$(CURDIR) python scripts/extract_code_snippets.py @PYTHONPATH=$(CURDIR) python scripts/generate_code_snippet_mdx.py # Run code samples. By default runs all; pass FILES to test specific paths. # make test-code-samples # make test-code-samples FILES="src/code-samples/langchain/return-a-string.py" # Set CODE_SAMPLE_PATHS to space-separated directories under src/code-samples/ # to run one product's samples (CI runs one matrix leg per product this way): # CODE_SAMPLE_PATHS="src/code-samples/langsmith" make test-code-samples # Set CODE_SAMPLE_JOBS=N (default 4) to run samples concurrently. # Samples that share a LangSmith dataset/fixture (evaluate-rag-*, # experiment-runs-query-*) stay serialized within their group. test-code-samples: @if [ -f src/code-samples/package.json ]; then (cd src/code-samples && npm install --silent); fi @FILES="$(FILES)" PYTHONPATH=$(CURDIR) uv run python scripts/test_code_samples.py # Run code samples with LangSmith tracing, update public share links in # src/code-samples/trace-links.json, then regenerate snippet MDX so docs show # "View example trace" under single-snippet samples that produced an agent run. # Each traced sample stamps LANGSMITH_DOCS_SNIPPET=; collection # matches that metadata (no time-window LangGraph fallback). Multi-snippet # source files are skipped until split. Requires LANGSMITH_API_KEY. # make update-code-sample-traces # make update-code-sample-traces FILES="src/code-samples/deepagents/overview-quickstart.py" update-code-sample-traces: @if [ -f src/code-samples/package.json ]; then (cd src/code-samples && npm install --silent); fi @CODE_SAMPLE_TRACING=1 \ LANGSMITH_PROJECT="$${LANGSMITH_PROJECT:-docs-code-samples}" \ FILES="$(FILES)" \ PYTHONPATH=$(CURDIR) uv run python scripts/test_code_samples.py @$(MAKE) code-snippets # Check that all @[ref] cross-references in source files resolve against link_map.py check-cross-refs: @PYTHONPATH=$(CURDIR) uv run python scripts/check_cross_refs.py skills: @mkdir -p .claude/skills @for d in .agents/skills/*/; do \ n=$$(basename "$$d"); \ if [ -e ".claude/skills/$$n" ] && [ ! -L ".claude/skills/$$n" ]; then \ echo "Skipped $$n: .claude/skills/$$n exists and is not a symlink"; \ else \ ln -sfn "../../.agents/skills/$$n" ".claude/skills/$$n"; \ echo "Linked .claude/skills/$$n"; \ fi; \ done @for l in .claude/skills/*; do \ if [ -L "$$l" ] && [ ! -e "$$l" ]; then rm "$$l"; echo "Removed stale link $$l"; fi; \ done help: @echo "Available commands:" @echo " make dev - Start development mode with file watching and mint dev" @echo " make build - Build documentation to ./build directory" @echo " make export - Run mint export from ./build (optional: MINT_EXPORT_ARGS)" @echo " make htmltest - Unzip EXPORT_ZIP, run htmltest on external URLs only (HTMLTEST_ARGS)" @echo " make export-htmltest - make export then make htmltest" @echo " make broken-links - Check for broken links in built documentation" @echo " make check-cross-refs - Check for unresolved @[ref] cross-references" @echo " make broken-links-with-anchors - Same as above, also validates anchor links" @echo " make check-mdx - Check build/ for unknown components and foreign syntax" @echo " make format - Format code" @echo " make lint - Lint code" @echo " make lint_md - Lint markdown files" @echo " make lint_md_fix - Lint and fix markdown files" @echo " make lint_prose - Lint prose with Vale (terminology, style)" @echo " make test - Run tests" @echo " make install - Install dependencies and link skills" @echo " make code-snippets - Extract code snippets (line-based, Bluehawk-compatible)" @echo " make test-code-samples - Run code samples (FILES=\"path ...\" for specific)" @echo " make skills - Link .agents/skills into .claude/skills for Claude Code" @echo " make update-code-sample-traces - Trace samples, update share links, regenerate snippets" @echo " make clean - Clean build artifacts" @echo " make help - Show this help message"