620 lines · 19.5 KB · Text
RawDownload
#!/usr/bin/env bash## Analyze code changes and suggest documentation updates.## Usage:#   script/docs-suggest --pr 49177           # Analyze a PR (immediate mode)#   script/docs-suggest --commit abc123      # Analyze a commit#   script/docs-suggest --diff file.patch    # Analyze a diff file#   script/docs-suggest --staged             # Analyze staged changes## Modes:#   --batch       Append suggestions to batch file for later PR (default for main)#   --immediate   Output suggestions directly (default for cherry-picks)## Options:#   --dry-run     Show assembled context without calling LLM#   --output FILE Write suggestions to file instead of stdout (immediate mode)#   --verbose     Show detailed progress#   --model NAME  Override default model#   --preview     Add preview callout to suggested docs (auto-detected)## Batch mode:#   Suggestions are appended to docs/.suggestions/pending.md#   Use script/docs-suggest-publish to create a PR from batched suggestions## Examples:#   # Analyze a PR to main (batches by default)#   script/docs-suggest --pr 49100##   # Analyze a cherry-pick PR (immediate by default)#   script/docs-suggest --pr 49152##   # Force immediate output for testing#   script/docs-suggest --pr 49100 --immediate##   # Dry run to see context#   script/docs-suggest --pr 49100 --dry-run set -euo pipefail # DefaultsMODE=""TARGET=""DRY_RUN=falseVERBOSE=falseOUTPUT=""MODEL="${DROID_MODEL:-claude-sonnet-4-5-20250929}"OUTPUT_MODE=""  # batch or immediate, auto-detected if not setADD_PREVIEW_CALLOUT=""  # auto-detected if not set # Colors for outputRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[0;33m'BLUE='\033[0;34m'NC='\033[0m' # No Color log() {    if [[ "$VERBOSE" == "true" ]]; then        echo -e "${BLUE}[docs-suggest]${NC} $*" >&2    fi} error() {    echo -e "${RED}Error:${NC} $*" >&2    exit 1} warn() {    echo -e "${YELLOW}Warning:${NC} $*" >&2} # Parse argumentswhile [[ $# -gt 0 ]]; do    case $1 in        --pr)            MODE="pr"            TARGET="$2"            shift 2            ;;        --commit)            MODE="commit"            TARGET="$2"            shift 2            ;;        --diff)            MODE="diff"            TARGET="$2"            shift 2            ;;        --staged)            MODE="staged"            shift            ;;        --batch)            OUTPUT_MODE="batch"            shift            ;;        --immediate)            OUTPUT_MODE="immediate"            shift            ;;        --preview)            ADD_PREVIEW_CALLOUT="true"            shift            ;;        --no-preview)            ADD_PREVIEW_CALLOUT="false"            shift            ;;        --dry-run)            DRY_RUN=true            shift            ;;        --verbose)            VERBOSE=true            shift            ;;        --output)            OUTPUT="$2"            shift 2            ;;        --model)            MODEL="$2"            shift 2            ;;        -h|--help)            head -42 "$0" | tail -40            exit 0            ;;        *)            error "Unknown option: $1"            ;;    esacdone # Validate modeif [[ -z "$MODE" ]]; then    error "Must specify one of: --pr, --commit, --diff, or --staged"fi # Get repo rootREPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"cd "$REPO_ROOT" # Batch file locationBATCH_DIR="$REPO_ROOT/docs/.suggestions"BATCH_FILE="$BATCH_DIR/pending.md" # Temp directory for context assemblyTMPDIR=$(mktemp -d)trap 'rm -rf "$TMPDIR"' EXIT log "Mode: $MODE, Target: ${TARGET:-staged changes}"log "Temp dir: $TMPDIR" # ============================================================================# Step 1: Get the diff and detect context# ============================================================================ get_diff() {    case $MODE in        pr)            if ! command -v gh &> /dev/null; then                error "gh CLI required for --pr mode. Install: https://cli.github.com"            fi            log "Fetching PR #$TARGET info..."                        # Get PR metadata for auto-detection            PR_JSON=$(gh pr view "$TARGET" --json baseRefName,title,number)            PR_BASE=$(echo "$PR_JSON" | grep -o '"baseRefName":"[^"]*"' | cut -d'"' -f4)            PR_TITLE=$(echo "$PR_JSON" | grep -o '"title":"[^"]*"' | cut -d'"' -f4)            PR_NUMBER=$(echo "$PR_JSON" | grep -o '"number":[0-9]*' | cut -d':' -f2)                        log "PR #$PR_NUMBER: $PR_TITLE (base: $PR_BASE)"                        # Auto-detect output mode based on target branch            if [[ -z "$OUTPUT_MODE" ]]; then                if [[ "$PR_BASE" == "main" ]]; then                    OUTPUT_MODE="batch"                    log "Auto-detected: batch mode (PR targets main)"                else                    OUTPUT_MODE="immediate"                    log "Auto-detected: immediate mode (PR targets $PR_BASE)"                fi            fi                        # Auto-detect preview callout            if [[ -z "$ADD_PREVIEW_CALLOUT" ]]; then                if [[ "$PR_BASE" == "main" ]]; then                    ADD_PREVIEW_CALLOUT="true"                    log "Auto-detected: will add preview callout (new feature going to main)"                else                    # Cherry-pick to release branch - check if it's preview or stable                    ADD_PREVIEW_CALLOUT="false"                    log "Auto-detected: no preview callout (cherry-pick)"                fi            fi                        # Store metadata for batch mode            echo "$PR_NUMBER" > "$TMPDIR/pr_number"            echo "$PR_TITLE" > "$TMPDIR/pr_title"            echo "$PR_BASE" > "$TMPDIR/pr_base"                        log "Fetching PR #$TARGET diff..."            gh pr diff "$TARGET" > "$TMPDIR/changes.diff"            gh pr diff "$TARGET" --name-only > "$TMPDIR/changed_files.txt"            ;;        commit)            log "Getting commit $TARGET diff..."            git show "$TARGET" --format="" > "$TMPDIR/changes.diff"            git show "$TARGET" --format="" --name-only > "$TMPDIR/changed_files.txt"                        # Default to immediate for commits            OUTPUT_MODE="${OUTPUT_MODE:-immediate}"            ADD_PREVIEW_CALLOUT="${ADD_PREVIEW_CALLOUT:-false}"            ;;        diff)            if [[ ! -f "$TARGET" ]]; then                error "Diff file not found: $TARGET"            fi            log "Using provided diff file..."            cp "$TARGET" "$TMPDIR/changes.diff"            grep -E '^\+\+\+ b/' "$TARGET" | sed 's|^+++ b/||' > "$TMPDIR/changed_files.txt" || true                        OUTPUT_MODE="${OUTPUT_MODE:-immediate}"            ADD_PREVIEW_CALLOUT="${ADD_PREVIEW_CALLOUT:-false}"            ;;        staged)            log "Getting staged changes..."            git diff --cached > "$TMPDIR/changes.diff"            git diff --cached --name-only > "$TMPDIR/changed_files.txt"                        OUTPUT_MODE="${OUTPUT_MODE:-immediate}"            ADD_PREVIEW_CALLOUT="${ADD_PREVIEW_CALLOUT:-false}"            ;;    esac     if [[ ! -s "$TMPDIR/changes.diff" ]]; then        error "No changes found"    fi     log "Found $(wc -l < "$TMPDIR/changed_files.txt" | tr -d ' ') changed files"    log "Output mode: $OUTPUT_MODE, Preview callout: $ADD_PREVIEW_CALLOUT"} # ============================================================================# Step 2: Filter to relevant changes# ============================================================================ filter_changes() {    log "Filtering to documentation-relevant changes..."        # Keep only source code changes (not tests, not CI, not docs themselves)    grep -E '^crates/.*\.rs$' "$TMPDIR/changed_files.txt" | \        grep -v '_test\.rs$' | \        grep -v '/tests/' | \        grep -v '/test_' > "$TMPDIR/source_files.txt" || true        # Also track if settings/keybindings changed    grep -E '(settings|keymap|actions)' "$TMPDIR/changed_files.txt" > "$TMPDIR/config_files.txt" || true        local source_count=$(wc -l < "$TMPDIR/source_files.txt" | tr -d ' ')    local config_count=$(wc -l < "$TMPDIR/config_files.txt" | tr -d ' ')        log "Relevant files: $source_count source, $config_count config"        if [[ "$source_count" -eq 0 && "$config_count" -eq 0 ]]; then        echo "No documentation-relevant changes detected (only tests, CI, or docs modified)."        exit 0    fi} # ============================================================================# Step 3: Assemble context# ============================================================================ assemble_context() {    log "Assembling context..."        # Start the prompt    cat > "$TMPDIR/prompt.md" << 'PROMPT_HEADER'# Documentation Suggestion Request You are analyzing code changes to determine if documentation updates are needed. Before analysis, read and follow these rule files:- `.rules`- `docs/.rules` ## Your Task 1. Analyze the diff below for user-facing changes2. Determine if any documentation updates are warranted3. If yes, provide specific, actionable suggestions4. If no, explain why no updates are needed ## Guidelines PROMPT_HEADER     # Add conventions    log "Adding documentation conventions..."    cat >> "$TMPDIR/prompt.md" << 'CONVENTIONS'### Documentation Conventions - **Voice**: Second person ("you"), present tense, direct and concise- **No hedging**: Avoid "simply", "just", "easily"- **Settings pattern**: Show Settings Editor UI first, then JSON as alternative- **Keybindings**: Use `{#kb action::Name}` syntax, not hardcoded keys- **Terminology**: "folder" not "directory", "project" not "workspace", "Settings Editor" not "settings UI"- **SEO keyword targeting**: For each docs page you suggest updating, choose one  primary keyword/intent phrase using the page's user intent- **SEO metadata**: Every updated/new docs page should include frontmatter with  `title` and `description` (single-line `key: value` entries)- **Metadata quality**: Titles should clearly state page intent (~50-60 chars),  descriptions should summarize the reader outcome (~140-160 chars)- **Keyword usage**: Use the primary keyword naturally in frontmatter and in page  body at least once; never keyword-stuff- **SEO structure**: Keep exactly one H1 and preserve logical H1→H2→H3  hierarchy- **Internal links minimum**: Non-reference pages should include at least 3  useful internal docs links; reference pages can include fewer when extra links  would be noise- **Marketing links**: For main feature pages, include a relevant `zed.dev`  marketing link alongside docs links ### Brand Voice Rubric (Required) For suggested doc text, apply the brand rubric scoring exactly and only pass textthat scores 4+ on every criterion: | Criterion            || -------------------- || Technical Grounding  || Natural Syntax       || Quiet Confidence     || Developer Respect    || Information Priority || Specificity          || Voice Consistency    || Earned Claims        | Pass threshold: all criteria 4+ (minimum 32/40 total). Also reject suggestions containing obvious taboo phrasing (hype, emotionalmanipulation, or marketing-style superlatives). For every docs file you suggest changing, treat the entire file as in scope forbrand review (not only the edited section). Include any additional full-filevoice fixes needed to reach passing rubric scores. ### What Requires Documentation - New user-facing features or commands- Changed keybindings or default behaviors- New or modified settings- Deprecated or removed functionality ### What Does NOT Require Documentation - Internal refactoring without behavioral changes- Performance optimizations (unless user-visible)- Bug fixes that restore documented behavior- Test changes, CI changesCONVENTIONS     # Add preview callout instructions if needed    if [[ "$ADD_PREVIEW_CALLOUT" == "true" ]]; then        # Get current preview version for modification callouts        local preview_version        preview_version=$(gh release list --limit 5 2>/dev/null | grep -E '\-pre\s' | head -1 | grep -oE 'v[0-9]+\.[0-9]+' | head -1 || echo "v0.XXX")        preview_version="${preview_version#v}"  # Remove leading 'v'                cat >> "$TMPDIR/prompt.md" << PREVIEW_INSTRUCTIONS ### Preview Release Callouts This change is going into Zed Preview first. Use the appropriate callout based on the type of change: #### For NEW/ADDITIVE features (new commands, new settings, new UI elements): \`\`\`markdown> **Preview:** This feature is available in Zed Preview. It will be included in the next Stable release.\`\`\` #### For BEHAVIOR MODIFICATIONS (changed defaults, altered behavior of existing features): \`\`\`markdown> **Changed in Preview (v${preview_version}).** See [release notes](/releases#${preview_version}).\`\`\` **Guidelines:**- Use the "Preview" callout for entirely new features or sections- Use the "Changed in Preview" callout when modifying documentation of existing behavior- Place callouts immediately after the section heading, before any content- Both callout types will be stripped when the feature ships to StablePREVIEW_INSTRUCTIONS    fi     echo "" >> "$TMPDIR/prompt.md"    echo "## Changed Files" >> "$TMPDIR/prompt.md"    echo "" >> "$TMPDIR/prompt.md"    echo '```' >> "$TMPDIR/prompt.md"    cat "$TMPDIR/changed_files.txt" >> "$TMPDIR/prompt.md"    echo '```' >> "$TMPDIR/prompt.md"    echo "" >> "$TMPDIR/prompt.md"        # Add the diff (truncated if huge)    echo "## Code Diff" >> "$TMPDIR/prompt.md"    echo "" >> "$TMPDIR/prompt.md"        local diff_lines=$(wc -l < "$TMPDIR/changes.diff" | tr -d ' ')    if [[ "$diff_lines" -gt 2000 ]]; then        warn "Diff is large ($diff_lines lines), truncating to 2000 lines"        echo '```diff' >> "$TMPDIR/prompt.md"        head -2000 "$TMPDIR/changes.diff" >> "$TMPDIR/prompt.md"        echo "" >> "$TMPDIR/prompt.md"        echo "[... truncated, $((diff_lines - 2000)) more lines ...]" >> "$TMPDIR/prompt.md"        echo '```' >> "$TMPDIR/prompt.md"    else        echo '```diff' >> "$TMPDIR/prompt.md"        cat "$TMPDIR/changes.diff" >> "$TMPDIR/prompt.md"        echo '```' >> "$TMPDIR/prompt.md"    fi        # Add output format instructions    cat >> "$TMPDIR/prompt.md" << 'OUTPUT_FORMAT' ## Output Format Respond with ONE of these formats: ### If documentation updates ARE needed: ```markdown## Documentation Suggestions ### Summary[1-2 sentence summary of what changed and why docs need updating] ### Suggested Changes #### 1. [docs/src/path/to/file.md]- **Section**: [existing section to update, or "New section"]- **Change**: [Add/Update/Remove]- **Target keyword**: [single keyword/intent phrase for this page]- **Frontmatter**:  ```yaml  ---  title: ...  description: ...  ---  ```- **Links**: [List at least 3 internal docs links for non-reference pages; if  this is a main feature page, include one relevant `zed.dev` marketing link]- **Suggestion**: [Specific text or description of what to add/change]- **Full-file brand pass**: [Required: yes. Note any additional voice edits  elsewhere in the same file needed to pass rubric across the entire file.]- **Brand voice scorecard**:   | Criterion            | Score | Notes |  | -------------------- | ----- | ----- |  | Technical Grounding  | /5    |       |  | Natural Syntax       | /5    |       |  | Quiet Confidence     | /5    |       |  | Developer Respect    | /5    |       |  | Information Priority | /5    |       |  | Specificity          | /5    |       |  | Voice Consistency    | /5    |       |  | Earned Claims        | /5    |       |  | **TOTAL**            | /40   |       |   Pass threshold: all criteria 4+. #### 2. [docs/src/another/file.md]... ### Notes for Reviewer[Any context or uncertainty worth flagging]``` ### If NO documentation updates are needed: ```markdown## No Documentation Updates Needed **Reason**: [Brief explanation - e.g., "Internal refactoring only", "Test changes", "Bug fix restoring existing behavior"] **Changes reviewed**:- [Brief summary of what the code changes do]- [Why they don't affect user-facing documentation]``` Be conservative. Only suggest documentation changes when there's a clear user-facing impact.OUTPUT_FORMAT     log "Context assembled: $(wc -l < "$TMPDIR/prompt.md" | tr -d ' ') lines"} 

Lines 1–500 of 620.