591 lines · 19.1 KB · Text
RawDownload
#!/usr/bin/env bash## Create a draft documentation PR by auto-applying batched suggestions.## Usage:#   script/docs-suggest-publish [--dry-run] [--model MODEL]## This script:# 1. Reads pending suggestions from the docs/suggestions-pending branch# 2. Uses Droid to apply suggestions in batches (default 10 per batch)# 3. Runs docs formatting# 4. Validates docs build (action references, JSON schemas, links)# 5. Creates a draft PR for human review/merge# 6. Optionally resets the suggestions branch after successful PR creation## Options:#   --dry-run          Show what would be done without creating PR#   --keep-queue       Don't reset the suggestions branch after PR creation#   --model MODEL      Override Droid model used for auto-apply#   --batch-size N     Suggestions per Droid invocation (default: 10)#   --skip-validation  Skip the docs build validation step#   --verbose          Show detailed progress## Run this as part of the preview release workflow. set -euo pipefail DRY_RUN=falseKEEP_QUEUE=falseVERBOSE=falseSKIP_VALIDATION=falseBATCH_SIZE=10MODEL="${DROID_MODEL:-claude-sonnet-4-5-latest}" SUGGESTIONS_BRANCH="docs/suggestions-pending" # ColorsRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[0;33m'BLUE='\033[0;34m'NC='\033[0m' log() {    if [[ "$VERBOSE" == "true" ]]; then        echo -e "${BLUE}[docs-publish]${NC} $*" >&2    fi} error() {    echo -e "${RED}Error:${NC} $*" >&2    exit 1} # Parse argumentswhile [[ $# -gt 0 ]]; do    case $1 in        --dry-run)            DRY_RUN=true            shift            ;;        --keep-queue)            KEEP_QUEUE=true            shift            ;;        --verbose)            VERBOSE=true            shift            ;;        --model)            MODEL="$2"            shift 2            ;;        --batch-size)            BATCH_SIZE="$2"            shift 2            ;;        --skip-validation)            SKIP_VALIDATION=true            shift            ;;        -h|--help)            head -30 "$0" | tail -28            exit 0            ;;        *)            error "Unknown option: $1"            ;;    esacdone # Get repo rootREPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"cd "$REPO_ROOT" # Check if suggestions branch existslog "Checking for suggestions branch..."if ! git ls-remote --exit-code --heads origin "$SUGGESTIONS_BRANCH" > /dev/null 2>&1; then    echo "No pending suggestions found (branch $SUGGESTIONS_BRANCH doesn't exist)."    echo "Suggestions are queued automatically when PRs are merged to main."    exit 0fi # Fetch the suggestions branchlog "Fetching suggestions branch..."git fetch origin "$SUGGESTIONS_BRANCH" # Check for manifestif ! git show "origin/$SUGGESTIONS_BRANCH:manifest.json" > /dev/null 2>&1; then    echo "No manifest found on suggestions branch."    exit 0fi # Read manifestMANIFEST=$(git show "origin/$SUGGESTIONS_BRANCH:manifest.json")SUGGESTION_COUNT=$(echo "$MANIFEST" | jq '.suggestions | length') if [[ "$SUGGESTION_COUNT" -eq 0 ]]; then    echo "No pending suggestions in queue."    exit 0fi echo "Found $SUGGESTION_COUNT pending suggestion(s):"echo ""echo "$MANIFEST" | jq -r '.suggestions[] | "  PR #\(.pr): \(.title)"'echo "" if [[ "$DRY_RUN" == "true" ]]; then    echo -e "${YELLOW}=== DRY RUN ===${NC}"    echo ""    echo "Would auto-apply suggestions to docs via Droid and create a draft PR."    echo "Model: $MODEL"    echo ""        # Show each suggestion file    for file in $(echo "$MANIFEST" | jq -r '.suggestions[].file'); do        echo "--- $file ---"        git show "origin/$SUGGESTIONS_BRANCH:$file" 2>/dev/null || echo "(file not found)"        echo ""    done        echo -e "${YELLOW}=== END DRY RUN ===${NC}"    echo ""    echo "Run without --dry-run to create the PR."    exit 0fi # Ensure clean working state (ignore untracked files with grep -v '??')if [[ -n "$(git status --porcelain | grep -v '^??' || true)" ]]; then    error "Working directory has uncommitted changes. Please commit or stash first."fi REQUIRED_COMMANDS=(git gh jq droid)if [[ "$SKIP_VALIDATION" != "true" ]]; then    REQUIRED_COMMANDS+=(mdbook)fifor command in "${REQUIRED_COMMANDS[@]}"; do    if ! command -v "$command" > /dev/null 2>&1; then        error "Required command not found: $command"    fidone # Remember current branchORIGINAL_BRANCH=$(git branch --show-current)log "Current branch: $ORIGINAL_BRANCH" # Create new branch for docs PR from latest maingit fetch origin mainDOCS_BRANCH="docs/preview-auto-$(date +%Y-%m-%d-%H%M%S)"log "Creating docs branch: $DOCS_BRANCH" git checkout -b "$DOCS_BRANCH" origin/main TMPDIR=$(mktemp -d)trap 'rm -rf "$TMPDIR"' EXIT APPLY_SUMMARY_FILE="$TMPDIR/apply-summary.md"touch "$APPLY_SUMMARY_FILE" # Collect suggestion files into an arraySUGGESTION_FILES=()while IFS= read -r file; do    SUGGESTION_FILES+=("$file")done < <(echo "$MANIFEST" | jq -r '.suggestions[].file') # Determine which PRs are already in the latest stable release.# Suggestions queued with --preview may reference features that shipped in stable# by the time this script runs, so their Preview callouts should be stripped.STABLE_PRS=()STABLE_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -v 'pre' | head -1 || true)if [[ -n "$STABLE_TAG" ]]; then    log "Latest stable release tag: $STABLE_TAG"    for file in "${SUGGESTION_FILES[@]}"; do        pr_num=$(echo "$MANIFEST" | jq -r --arg f "$file" '.suggestions[] | select(.file == $f) | .pr')        # Find the merge commit for this PR        merge_sha=$(gh pr view "$pr_num" --json mergeCommit --jq '.mergeCommit.oid' 2>/dev/null || true)        if [[ -n "$merge_sha" ]] && git merge-base --is-ancestor "$merge_sha" "$STABLE_TAG" 2>/dev/null; then            STABLE_PRS+=("$pr_num")            log "PR #$pr_num is in stable ($STABLE_TAG)"        fi    done    if [[ ${#STABLE_PRS[@]} -gt 0 ]]; then        echo -e "${YELLOW}Note:${NC} ${#STABLE_PRS[@]} suggestion(s) are for PRs already in stable ($STABLE_TAG)."        echo "  Preview callouts will be stripped for: ${STABLE_PRS[*]}"        echo ""    fielse    log "No stable release tag found, treating all suggestions as preview-only"fi # Determine which PRs touch code gated behind feature flags.# Features behind flags aren't generally available and shouldn't be documented yet.FLAGGED_PRS=()FLAGS_FILE="$REPO_ROOT/crates/feature_flags/src/flags.rs"if [[ -f "$FLAGS_FILE" ]]; then    # Extract feature flag struct names (e.g. SubagentsFeatureFlag, GitGraphFeatureFlag)    FLAG_NAMES=$(grep -oE 'pub struct \w+FeatureFlag' "$FLAGS_FILE" | awk '{print $3}')    if [[ -n "$FLAG_NAMES" ]]; then        FLAG_PATTERN=$(echo "$FLAG_NAMES" | tr '\n' '|' | sed 's/|$//')        log "Feature flags found: $(echo "$FLAG_NAMES" | tr '\n' ' ')"        for file in "${SUGGESTION_FILES[@]}"; do            pr_num=$(echo "$MANIFEST" | jq -r --arg f "$file" '.suggestions[] | select(.file == $f) | .pr')            # Skip PRs already marked as stable (no need to double-check)            is_already_stable=false            for stable_pr in "${STABLE_PRS[@]+"${STABLE_PRS[@]}"}"; do                if [[ "$stable_pr" == "$pr_num" ]]; then                    is_already_stable=true                    break                fi            done            if [[ "$is_already_stable" == "true" ]]; then                continue            fi            # Check if the PR diff references any feature flag            pr_diff=$(gh pr diff "$pr_num" 2>/dev/null || true)            if [[ -n "$pr_diff" ]] && echo "$pr_diff" | grep -qE "$FLAG_PATTERN"; then                matched_flags=$(echo "$pr_diff" | grep -oE "$FLAG_PATTERN" | sort -u | tr '\n' ', ' | sed 's/,$//')                FLAGGED_PRS+=("$pr_num")                log "PR #$pr_num is behind feature flag(s): $matched_flags"            fi        done        if [[ ${#FLAGGED_PRS[@]} -gt 0 ]]; then            echo -e "${YELLOW}Note:${NC} ${#FLAGGED_PRS[@]} suggestion(s) are for features behind feature flags."            echo "  These will be skipped: ${FLAGGED_PRS[*]}"            echo ""        fi    fielse    log "Feature flags file not found, skipping flag detection"fi # Split into batchesTOTAL=${#SUGGESTION_FILES[@]}BATCH_COUNT=$(( (TOTAL + BATCH_SIZE - 1) / BATCH_SIZE )) if [[ "$BATCH_COUNT" -gt 1 ]]; then    echo "Processing $TOTAL suggestions in $BATCH_COUNT batches of up to $BATCH_SIZE..."else    echo "Processing $TOTAL suggestions..."fiecho "" for (( batch=0; batch<BATCH_COUNT; batch++ )); do    START=$(( batch * BATCH_SIZE ))    END=$(( START + BATCH_SIZE ))    if [[ "$END" -gt "$TOTAL" ]]; then        END=$TOTAL    fi     BATCH_NUM=$(( batch + 1 ))    BATCH_SUGGESTIONS_FILE="$TMPDIR/batch-${BATCH_NUM}-suggestions.md"    BATCH_PROMPT_FILE="$TMPDIR/batch-${BATCH_NUM}-prompt.md"    BATCH_SUMMARY_FILE="$TMPDIR/batch-${BATCH_NUM}-summary.md"     echo -e "${BLUE}Batch $BATCH_NUM/$BATCH_COUNT${NC} (suggestions $(( START + 1 ))-$END of $TOTAL)"     # Combine suggestion files for this batch, skipping flagged PRs and annotating stable PRs    BATCH_HAS_SUGGESTIONS=false    for (( i=START; i<END; i++ )); do        file="${SUGGESTION_FILES[$i]}"        pr_num=$(echo "$MANIFEST" | jq -r --arg f "$file" '.suggestions[] | select(.file == $f) | .pr')         # Skip PRs behind feature flags entirely        is_flagged=false        for flagged_pr in "${FLAGGED_PRS[@]+"${FLAGGED_PRS[@]}"}"; do            if [[ "$flagged_pr" == "$pr_num" ]]; then                is_flagged=true                break            fi        done        if [[ "$is_flagged" == "true" ]]; then            log "Skipping PR #$pr_num (behind feature flag)"            {                echo "### Skipped: PR #$pr_num"                echo ""                echo "This PR is behind a feature flag and was not applied."                echo ""            } >> "$APPLY_SUMMARY_FILE"            continue        fi         BATCH_HAS_SUGGESTIONS=true         # Check if PR is already in stable        is_stable=false        for stable_pr in "${STABLE_PRS[@]+"${STABLE_PRS[@]}"}"; do            if [[ "$stable_pr" == "$pr_num" ]]; then                is_stable=true                break            fi        done        {            echo "## Source: $file"            if [[ "$is_stable" == "true" ]]; then                echo ""                echo "> **ALREADY IN STABLE**: PR #$pr_num shipped in $STABLE_TAG."                echo "> Do NOT add Preview or Changed-in-Preview callouts for this suggestion."                echo "> Apply the documentation content only, without any preview-related callouts."            fi            echo ""            git show "origin/$SUGGESTIONS_BRANCH:$file" 2>/dev/null || error "Suggestion file missing: $file"            echo ""            echo "---"            echo ""        } >> "$BATCH_SUGGESTIONS_FILE"    done     # Skip this batch if all its suggestions were flagged    if [[ "$BATCH_HAS_SUGGESTIONS" == "false" ]]; then        echo -e "  ${YELLOW}Batch $BATCH_NUM skipped (all suggestions behind feature flags)${NC}"        continue    fi     # Build auto-apply prompt for this batch    cat > "$BATCH_PROMPT_FILE" << 'EOF'# Documentation Auto-Apply Request (Preview Release) Apply all queued documentation suggestions below directly to docs files in this repository. Before making edits, read and follow these rule files:- `.rules`- `docs/.rules` ## Required behavior 1. Apply concrete documentation edits (not suggestion text) to the appropriate files.2. Edit only docs content files under `docs/src/` unless a suggestion explicitly requires another docs path.3. For every docs file you modify, run a full-file brand voice pass (entire file, not only edited sections).4. Enforce the brand rubric exactly; final file content must score 4+ on every criterion:   - Technical Grounding   - Natural Syntax   - Quiet Confidence   - Developer Respect   - Information Priority   - Specificity   - Voice Consistency   - Earned Claims5. Keep SEO/frontmatter/linking requirements from the suggestions where applicable.6. Keep preview callout semantics correct:   - Additive features: `> **Preview:** ...`   - Behavior modifications: `> **Changed in Preview (vX.XXX).** ...`   - **Exception**: Suggestions marked "ALREADY IN STABLE" must NOT get any preview callouts.     These features already shipped in a stable release. Apply the content changes only.   - Suggestions for features behind feature flags have been pre-filtered and excluded.     If you encounter references to feature-flagged functionality, do not document it.7. If a suggestion is too ambiguous to apply safely, skip it and explain why in the summary.8. **Do not invent `{#kb}` or `{#action}` references.** Only use action names that already   appear in the existing docs files you are editing. If unsure whether an action name is   valid, use plain text instead. The docs build validates all action references against   the compiled binary and will reject unknown names. ## Output format (after making edits) Return markdown with: - `## Applied Suggestions`- `## Skipped Suggestions`- `## Files Updated`- `## Brand Voice Verification` (one line per updated file confirming full-file pass) Do not include a patch in the response; apply edits directly to files. ## Queued Suggestions EOF     cat "$BATCH_SUGGESTIONS_FILE" >> "$BATCH_PROMPT_FILE"     log "Running Droid auto-apply (batch $BATCH_NUM) with model: $MODEL"    if ! droid exec -m "$MODEL" -f "$BATCH_PROMPT_FILE" --auto high > "$BATCH_SUMMARY_FILE" 2>&1; then        echo "Droid exec output (batch $BATCH_NUM):"        cat "$BATCH_SUMMARY_FILE"        error "Droid exec failed on batch $BATCH_NUM. See output above."    fi     # Append batch summary    {        echo "### Batch $BATCH_NUM"        echo ""        cat "$BATCH_SUMMARY_FILE"        echo ""    } >> "$APPLY_SUMMARY_FILE"     echo -e "  ${GREEN}Batch $BATCH_NUM complete${NC}"doneecho "" log "All batches completed, checking results..." if [[ -n "$(git status --porcelain | grep -v '^??' | grep -vE '^.. docs/' || true)" ]]; then    error "Auto-apply modified non-doc files. Revert and re-run."fi if [[ -z "$(git status --porcelain docs/ | grep '^.. docs/src/' || true)" ]]; then    error "Auto-apply produced no docs/src changes."fi log "Running docs formatter"./script/prettier --write if [[ -z "$(git status --porcelain docs/ | grep '^.. docs/src/' || true)" ]]; then    error "No docs/src changes remain after formatting; aborting PR creation."fi # Validate docs build before creating PRif [[ "$SKIP_VALIDATION" != "true" ]]; then    echo "Validating docs build..."    log "Generating action metadata..."    if ! ./script/generate-action-metadata > /dev/null 2>&1; then        echo -e "${YELLOW}Warning:${NC} Could not generate action metadata (cargo build may have failed)."        echo "Skipping docs build validation. CI will still catch errors."    else        VALIDATION_DIR="$TMPDIR/docs-validation"        if ! mdbook build ./docs --dest-dir="$VALIDATION_DIR" 2>"$TMPDIR/validation-errors.txt"; then            echo ""            echo -e "${RED}Docs build validation failed:${NC}"            cat "$TMPDIR/validation-errors.txt"            echo ""            error "Fix the errors above and re-run, or use --skip-validation to bypass."        fi        echo -e "${GREEN}Docs build validation passed.${NC}"    fi    echo ""fi # Build PR body from suggestionsPR_BODY_FILE="$TMPDIR/pr-body.md"cat > "$PR_BODY_FILE" << 'EOF'# Documentation Updates for Preview Release This draft PR auto-applies queued documentation suggestions collected fromrecently merged PRs. ## How to Use This PR 1. Review the applied changes file-by-file.2. Verify brand voice on each touched file as a full-file pass.3. Ensure docs use the correct callout type:   - Additive features: Preview callout   - Behavior modifications: Changed in Preview callout4. Merge when ready. ## Auto-Apply Summary EOF cat "$APPLY_SUMMARY_FILE" >> "$PR_BODY_FILE" cat >> "$PR_BODY_FILE" << 'EOF' ## Preview Callouts Use the Preview callout for new/additive features: ```markdown> **Preview:** This feature is available in Zed Preview. It will be included in the next Stable release.``` Use this callout for behavior modifications to existing functionality: ```markdown> **Changed in Preview (v0.XXX).** See [release notes](/releases#0.XXX).``` --- ## Pending Suggestions EOF # Append each suggestion to PR bodyfor file in $(echo "$MANIFEST" | jq -r '.suggestions[].file'); do    log "Adding $file to PR body..."    echo "" >> "$PR_BODY_FILE"    git show "origin/$SUGGESTIONS_BRANCH:$file" >> "$PR_BODY_FILE" 2>/dev/null || true    echo "" >> "$PR_BODY_FILE"    echo "---" >> "$PR_BODY_FILE"done # Add tracking info

Lines 1–500 of 591.