Cleanup unused workflows and script files (#66024)

Removes a lot of dead code we have floating around here that is no
longer used and has not been for ages.

If we ever need to check something out here again, we can just revert
parts of these. Currently, these just trigger security warnings if
anything and thus are dead weight. Hence removing here.

Drive-by fix of our collab staging deploys as well.

Release Notes:

- N/A
This commit is contained in:
Finn Evers authored and GitHub committed 2026-10-23 09:30:55 +00:00
1 parent e841324d0d
commit 4f862ffbc6
19 files changed
+6 -3218

No files matched your search

-84
View File
@@ -1,84 +0,0 @@
# Crash Fix
You are fixing a crash that has been analyzed and has a reproduction test case. Your goal is to implement a minimal, correct fix that resolves the root cause and makes the reproduction test pass.
## Inputs
Before starting, you should have:
1. **ANALYSIS.md** — the crash analysis from the investigation phase. Read it thoroughly.
2. **A failing test** — a reproduction test that triggers the crash. Run it first to confirm it fails as expected.
If either is missing, ask the user to provide them or run the investigation phase first (`/prompt crash/investigate`).
## Workflow
### Step 1: Confirm the Failing Test
Run the reproduction test and verify it fails with the expected crash:
```
cargo test -p <crate> <test_name>
```
Read the failure output. Confirm the panic message and stack trace match what ANALYSIS.md describes. If the test doesn't fail, or fails differently than expected, stop and reassess before proceeding.
### Step 2: Understand the Fix
Read the "Suggested Fix" section of ANALYSIS.md and the relevant source code. Before writing any code, be clear on:
1. **What invariant is being violated** — what property of the data does the crashing code assume?
2. **Where the invariant breaks** — which function produces the bad state?
### Step 3: Implement the Fix
Apply the minimal change needed to resolve the root cause. Guidelines:
- **Fix the root cause, not the symptom.** Don't just catch the panic with a bounds check if the real problem is an incorrect offset calculation. Fix the calculation.
- **Preserve existing behavior** for all non-crashing cases. The fix should only change what happens in the scenario that was previously crashing.
- **Don't add unnecessary changes.** No drive-by improvements, keep the diff focused.
- **Add a comment only if the fix is non-obvious.** If a reader might wonder "why is this check here?", a brief comment explaining the crash scenario is appropriate.
- **Consider long term maintainability** Please make a targeted fix while being sure to consider the long term maintainability and reliability of the codebase
### Step 4: Verify the Fix
Run the reproduction test and confirm it passes:
```
cargo test -p <crate> <test_name>
```
Then run the full test suite for the affected crate to check for regressions:
```
cargo test -p <crate>
```
If any tests fail, determine whether the fix introduced a regression. Fix regressions before proceeding.
### Step 5: Run Clippy
```
./script/clippy
```
Address any new warnings introduced by your change.
### Step 6: Summarize
Write a brief summary of the fix for use in a PR description. Include:
- **What was the bug** — one sentence on the root cause.
- **What the fix does** — one sentence on the change.
- **How it was verified** — note that the reproduction test now passes.
- **Sentry issue link** — if available from ANALYSIS.md.
We use the following template for pull request descriptions. Please add information to answer the relevant sections, especially for release notes.
```
<Description of change, what the issue was and the fix.>
Release Notes:
- N/A *or* Added/Fixed/Improved ...
```
-89
View File
@@ -1,89 +0,0 @@
# Crash Investigation
You are investigating a crash that was observed in the wild. Your goal is to understand the root cause and produce a minimal reproduction test case that triggers the same crash. This test will be used to verify a fix and prevent regressions.
## Workflow
### Step 1: Get the Crash Report
If given a Sentry issue ID (like `ZED-4VS` or a numeric ID), there are several ways to fetch the crash data:
**Option A: Sentry MCP server (preferred if available)**
If the Sentry MCP server is configured as a context server, use its tools directly (e.g., `get_sentry_issue`) to fetch the issue details and stack trace. This is the simplest path — no tokens or scripts needed.
**Option B: Fetch script**
Run the fetch script from the terminal:
```
script/sentry-fetch <issue-id>
```
This reads authentication from `~/.sentryclirc` (set up via `sentry-cli login`) or the `SENTRY_AUTH_TOKEN` environment variable.
**Option C: Crash report provided directly**
If the crash report was provided inline or as a file, read it carefully before proceeding.
### Step 2: Analyze the Stack Trace
Read the stack trace bottom-to-top (from crash site upward) and identify:
1. **The crash site** — the exact function and line where the panic/abort occurs.
2. **The immediate cause** — what operation failed (e.g., slice indexing on a non-char-boundary, out-of-bounds access, unwrap on None).
3. **The relevant application frames** — filter out crash handler, signal handler, parking_lot, and stdlib frames. Focus on frames marked "(In app)".
4. **The data flow** — trace how the invalid data reached the crash site. What computed the bad index, the None value, etc.?
Find the relevant source files in the repository and read them. Pay close attention to:
- The crashing function and its callers
- How inputs to the crashing operation are computed
- Any assumptions the code makes about its inputs (string encoding, array lengths, option values)
### Step 3: Identify the Root Cause
Work backwards from the crash site to determine **what sequence of events or data conditions** produces the invalid state.
Ask yourself: *What user action or sequence of actions could lead to this state?* The crash came from a real user, so there is some natural usage pattern that triggers it.
### Step 4: Write a Reproduction Test
Write a minimal test case that:
1. **Mimics user actions** rather than constructing corrupt state directly. Work from the top down: what does the user do (open a file, type text, trigger a completion, etc.) that eventually causes the internal state to become invalid?
2. **Exercises the same code path** as the crash. The test should fail in the same function with the same kind of error (e.g., same panic message pattern).
3. **Is minimal** — include only what's necessary to trigger the crash. Remove anything that isn't load-bearing.
4. **Lives in the right place** — add the test to the existing test module of the crate where the bug lives. Follow the existing test patterns in that module.
5. **Avoid overly verbose comments** - the test should be self-explanatory and concise. More detailed descriptions of the test can go in ANALYSIS.md (see the next section).
When the test fails, its stack trace should share the key application frames from the original crash report. The outermost frames (crash handler, signal handling) will differ since we're in a test environment — that's expected.
If you can't reproduce the exact crash but can demonstrate the same class of bug (e.g., same function panicking with a similar invalid input), that is still valuable. Note the difference in your analysis.
### Step 5: Write the Analysis
Create an `ANALYSIS.md` file (in the working directory root, or wherever instructed) with these sections:
```markdown
# Crash Analysis: <short description>
## Crash Summary
- **Sentry Issue:** <ID and link if available>
- **Error:** <the panic/error message>
- **Crash Site:** <function name and file>
## Root Cause
<Explain what goes wrong and why. Be specific about the data flow.>
## Reproduction
<Describe what the test does and how it triggers the same crash.
Include the exact command to run the test, e.g.:
`cargo test -p <crate> <test_name>`>
## Suggested Fix
<Describe the fix approach. Be specific: which function, what check to add,
what computation to change. If there are multiple options, list them with tradeoffs.>
```
## Guidelines
- **Don't guess.** If you're unsure about a code path, read the source. Use `grep` to find relevant functions, types, and call sites.
- **Check the git history.** If the crash appeared in a specific version, `git log` on the relevant files may reveal a recent change that introduced the bug.
- **Look at existing tests.** The crate likely has tests that show how to set up the relevant subsystem. Follow those patterns rather than inventing new test infrastructure.
-89
View File
@@ -1,89 +0,0 @@
# Crash Issue Linking
You are linking a crash to potentially related GitHub issues so human reviewers can quickly validate whether a fix may resolve multiple reports.
## Inputs
Before starting, you should have:
1. **Crash report** (from `script/sentry-fetch <issue-id>` or Sentry MCP)
2. **ANALYSIS.md** from investigation phase, including root cause and crash site
If either is missing, stop and report what is missing.
## Goal
Search GitHub issues and produce a reviewer-ready shortlist grouped by confidence:
- **High confidence**
- **Medium confidence**
- **Low confidence**
The output is advisory only. Humans must confirm before adding closing keywords or making release claims.
## Workflow
### Step 1: Build Search Signals
Extract concrete signals from the crash + analysis:
1. Crash site function, file, and crate
2. Error message / panic text
3. Key stack frames (especially in-app)
4. Reproduction trigger phrasing (user actions)
5. Affected platform/version tags if available
### Step 2: Search GitHub Issues
Search **only** issues in `zed-industries/zed` (prefer `gh issue list` / `gh issue view` / GraphQL if available) by:
1. Panic/error text
2. Function/file names
3. Crate/module names + symptom keywords
4. Similar reproduction patterns
Check both open and recently closed issues in `zed-industries/zed`.
### Step 3: Score Confidence
Assign confidence based on evidence quality:
- **High:** direct technical overlap (same crash site or same invariant violation with matching repro language)
- **Medium:** partial overlap (same subsystem and symptom, but indirect stack/repro match)
- **Low:** thematic similarity only (same area/keywords without solid technical match)
Avoid inflated confidence. If uncertain, downgrade.
### Step 4: Produce Structured Output
Write `LINKED_ISSUES.md` using this exact structure:
```markdown
# Potentially Related GitHub Issues
## High Confidence
- [#12345](https://github.com/zed-industries/zed/issues/12345) — <title>
- Why: <1-2 sentence evidence-backed rationale>
- Evidence: <stack frame / error text / repro alignment>
## Medium Confidence
- ...
## Low Confidence
- ...
## Reviewer Checklist
- [ ] Confirm High confidence issues should be referenced in PR body
- [ ] Confirm any issue should receive closing keywords (`Fixes #...`)
- [ ] Reject false positives before merge
```
If no credible matches are found, keep sections present and write `- None found` under each.
## Rules
- Do not fabricate issues or URLs.
- Do not include issues from any repository other than `zed-industries/zed`.
- Do not add closing keywords automatically.
- Keep rationale short and evidence-based.
- Favor precision over recall.
+1
View File
@@ -7,6 +7,7 @@ on:
push:
tags:
- collab-production
- collab-staging
permissions:
contents: read
jobs:
-489
View File
@@ -1,489 +0,0 @@
name: Documentation Suggestions
# Stable release callout stripping plan (not wired yet):
# 1. Add a separate stable-only workflow trigger on `release.published`
# with `github.event.release.prerelease == false`.
# 2. In that workflow, run `script/docs-strip-preview-callouts` on `main`.
# 3. Open a PR with stripped preview callouts for human review.
# 4. Fail loudly on script errors or when no callout changes are produced.
# 5. Keep this workflow focused on suggestions only until that stable workflow is added.
on:
# Analyze merged PRs on main and cherry-picks to release branches. Each job
# further restricts the event actions and target branch that it handles.
pull_request:
types: [closed, opened, synchronize]
branches:
- main
- "v0.*"
paths:
- "crates/**/*.rs"
# Manual trigger for testing
workflow_dispatch:
inputs:
pr_number:
description: "PR number to analyze"
required: true
type: string
mode:
description: "Output mode"
required: true
type: choice
options:
- batch
- immediate
default: batch
# Both jobs below declare their own `permissions:` blocks, so no job uses this
# top-level default. Least privilege therefore grants nothing here.
permissions: {}
env:
DROID_MODEL: claude-sonnet-4-5-20250929
SUGGESTIONS_BRANCH: docs/suggestions-pending
jobs:
# Job for PRs merged to main - batch suggestions to branch
# Only runs for PRs from the same repo (not forks) since secrets aren't available for fork PRs
batch-suggestions:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: read
if: |
(github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && inputs.mode == 'batch')
steps:
- name: Checkout repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Droid CLI
run: |
# Retry with exponential backoff for transient network/auth issues
MAX_RETRIES=3
for i in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
if curl -fsSL https://app.factory.ai/cli | sh; then
echo "Droid CLI installed successfully"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
exit 1
fi
sleep $((i * 5))
done
env:
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
- name: Get PR info
id: pr
env:
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
run: |
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_NUM="$INPUT_PR_NUMBER"
else
PR_NUM="$EVENT_PR_NUMBER"
fi
if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid PR number: $PR_NUM"
exit 1
fi
echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
PR_TITLE=$(gh pr view "$PR_NUM" --json title --jq '.title' | tr -d '\n\r' | head -c 200)
EOF_MARKER="EOF_$(openssl rand -hex 8)"
{
echo "title<<$EOF_MARKER"
echo "$PR_TITLE"
echo "$EOF_MARKER"
} >> "$GITHUB_OUTPUT"
- name: Analyze PR for documentation needs
id: analyze
env:
GH_TOKEN: ${{ github.token }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
run: |
export PATH="${HOME}/.local/bin:${PATH}"
# Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
# Unset GH_TOKEN first to allow gh auth login to store credentials
echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)
OUTPUT_FILE=$(mktemp)
# Retry with exponential backoff for transient Factory API failures
MAX_RETRIES=3
for i in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $i of $MAX_RETRIES to analyze PR..."
if ./script/docs-suggest \
--pr "$PR_NUMBER" \
--immediate \
--preview \
--output "$OUTPUT_FILE" \
--verbose; then
echo "Analysis completed successfully"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Analysis failed after $MAX_RETRIES attempts"
exit 1
fi
echo "Retrying in $((i * 5)) seconds..."
sleep $((i * 5))
done
# Check if we got actionable suggestions (not "no updates needed")
if grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
echo "output_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
else
echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
echo "No actionable documentation suggestions for this PR"
cat "$OUTPUT_FILE"
fi
- name: Commit suggestions to queue branch
if: steps.analyze.outputs.has_suggestions == 'true'
env:
PR_NUM: ${{ steps.pr.outputs.number }}
PR_TITLE: ${{ steps.pr.outputs.title }}
OUTPUT_FILE: ${{ steps.analyze.outputs.output_file }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Retry loop for handling concurrent pushes
MAX_RETRIES=3
for i in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $i of $MAX_RETRIES"
# Fetch and checkout suggestions branch (create if doesn't exist)
if git ls-remote --exit-code --heads origin "$SUGGESTIONS_BRANCH" > /dev/null 2>&1; then
git fetch origin "$SUGGESTIONS_BRANCH"
git checkout -B "$SUGGESTIONS_BRANCH" "origin/$SUGGESTIONS_BRANCH"
else
# Create orphan branch for clean history
git checkout --orphan "$SUGGESTIONS_BRANCH"
git rm -rf . > /dev/null 2>&1 || true
# Initialize with README
cat > README.md << 'EOF'
# Documentation Suggestions Queue
This branch contains batched documentation suggestions for the next Preview release.
Each file represents suggestions from a merged PR. At preview branch cut time,
run `script/docs-suggest-publish` to create a documentation PR from these suggestions.
## Structure
- `suggestions/PR-XXXXX.md` - Suggestions for PR #XXXXX
- `manifest.json` - Index of all pending suggestions
## Workflow
1. PRs merged to main trigger documentation analysis
2. Suggestions are committed here as individual files
3. At preview release, suggestions are collected into a docs PR
4. After docs PR is created, this branch is reset
EOF
mkdir -p suggestions
echo '{"suggestions":[]}' > manifest.json
git add README.md suggestions manifest.json
git commit -m "Initialize documentation suggestions queue"
fi
# Create suggestion file
SUGGESTION_FILE="suggestions/PR-${PR_NUM}.md"
{
echo "# PR #${PR_NUM}: ${PR_TITLE}"
echo ""
echo "_Merged: $(date -u +%Y-%m-%dT%H:%M:%SZ)_"
echo "_PR: https://github.com/${REPO}/pull/${PR_NUM}_"
echo ""
cat "$OUTPUT_FILE"
} > "$SUGGESTION_FILE"
# Update manifest
MANIFEST=$(cat manifest.json)
NEW_ENTRY="{\"pr\":${PR_NUM},\"title\":$(echo "$PR_TITLE" | jq -R .),\"file\":\"$SUGGESTION_FILE\",\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
# Add to manifest if not already present
if ! echo "$MANIFEST" | jq -e ".suggestions[] | select(.pr == $PR_NUM)" > /dev/null 2>&1; then
echo "$MANIFEST" | jq ".suggestions += [$NEW_ENTRY]" > manifest.json
fi
# Commit
git add "$SUGGESTION_FILE" manifest.json
git commit -m "docs: Add suggestions for PR #${PR_NUM}
${PR_TITLE}
Auto-generated documentation suggestions for review at next preview release."
# Try to push
if git push origin "$SUGGESTIONS_BRANCH"; then
echo "Successfully pushed suggestions"
break
else
echo "Push failed, retrying..."
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed after $MAX_RETRIES attempts"
exit 1
fi
sleep $((i * 2))
fi
done
- name: Summary
if: always()
env:
HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
PR_NUM: ${{ steps.pr.outputs.number }}
REPO: ${{ github.repository }}
run: |
{
echo "## Documentation Suggestions"
echo ""
if [ "$HAS_SUGGESTIONS" == "true" ]; then
echo "✅ Suggestions queued for PR #${PR_NUM}"
echo ""
echo "View pending suggestions: [docs/suggestions-pending branch](https://github.com/${REPO}/tree/${SUGGESTIONS_BRANCH})"
else
echo "No documentation updates needed for this PR."
fi
} >> "$GITHUB_STEP_SUMMARY"
# Job for cherry-picks to release branches - immediate output as PR comment
cherry-pick-suggestions:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: write
concurrency:
group: docs-suggestions-${{ github.event.pull_request.number || inputs.pr_number || 'manual' }}
cancel-in-progress: true
if: |
(github.event_name == 'pull_request' &&
contains(fromJSON('["opened","synchronize"]'), github.event.action) &&
startsWith(github.event.pull_request.base.ref, 'v0.') &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(fromJSON('["MEMBER","OWNER"]'),
github.event.pull_request.author_association)) ||
(github.event_name == 'workflow_dispatch' && inputs.mode == 'immediate')
steps:
- name: Checkout repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || '' }}
persist-credentials: false
- name: Install Droid CLI
run: |
# Retry with exponential backoff for transient network/auth issues
MAX_RETRIES=3
for i in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
if curl -fsSL https://app.factory.ai/cli | sh; then
echo "Droid CLI installed successfully"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
exit 1
fi
sleep $((i * 5))
done
env:
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
- name: Get PR number
id: pr
env:
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_NUM="$INPUT_PR_NUMBER"
else
PR_NUM="$EVENT_PR_NUMBER"
fi
if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid PR number: $PR_NUM"
exit 1
fi
echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
- name: Analyze PR for documentation needs
id: analyze
env:
GH_TOKEN: ${{ github.token }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
run: |
export PATH="${HOME}/.local/bin:${PATH}"
# Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
# Unset GH_TOKEN first to allow gh auth login to store credentials
echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)
OUTPUT_FILE="${RUNNER_TEMP}/suggestions.md"
# Cherry-picks don't get preview callout
# Retry with exponential backoff for transient Factory API failures
MAX_RETRIES=3
for i in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $i of $MAX_RETRIES to analyze PR..."
if ./script/docs-suggest \
--pr "$PR_NUMBER" \
--immediate \
--no-preview \
--output "$OUTPUT_FILE" \
--verbose; then
echo "Analysis completed successfully"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Analysis failed after $MAX_RETRIES attempts"
exit 1
fi
echo "Retrying in $((i * 5)) seconds..."
sleep $((i * 5))
done
# Check if we got actionable suggestions
if [ -s "$OUTPUT_FILE" ] && \
grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
echo "suggestions_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
else
echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
fi
- name: Post suggestions as PR comment
if: steps.analyze.outputs.has_suggestions == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
SUGGESTIONS_FILE: ${{ steps.analyze.outputs.suggestions_file }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
with:
script: |
const fs = require('fs');
// Read suggestions from file
const suggestionsRaw = fs.readFileSync(process.env.SUGGESTIONS_FILE, 'utf8');
// Sanitize AI-generated content
let sanitized = suggestionsRaw
// Strip HTML tags
.replace(/<[^>]*>/g, '')
// Strip markdown links but keep display text
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
// Strip raw URLs
.replace(/https?:\/\/[^\s)>\]]+/g, '[link removed]')
// Strip protocol-relative URLs
.replace(/\/\/[^\s)>\]]+\.[^\s)>\]]+/g, '[link removed]')
// Neutralize @-mentions (preserve JSDoc-style annotations)
.replace(/@(?!param\b|returns?\b|throws?\b|typedef\b|type\b|see\b|example\b|since\b|deprecated\b|default\b)(\w+)/g, '`@$1`')
// Strip cross-repo references that could be confused with real links
.replace(/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#\d+/g, '[ref removed]');
// Truncate to 20,000 characters
if (sanitized.length > 20000) {
sanitized = sanitized.substring(0, 20000) + '\n\n…(truncated)';
}
// Parse and validate PR number
const prNumber = parseInt(process.env.PR_NUMBER, 10);
if (isNaN(prNumber) || prNumber <= 0) {
core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`);
return;
}
const body = `## 📚 Documentation Suggestions
This cherry-pick contains changes that may need documentation updates.
${sanitized}
---
> **Note:** This comment was generated automatically by an AI model analyzing
> code changes. Suggestions may contain inaccuracies — please verify before acting.
<details>
<summary>About this comment</summary>
This comment was generated automatically by analyzing code changes in this cherry-pick.
Cherry-picks typically don't need new documentation since the feature was already
documented when merged to main, but please verify.
</details>`;
// Find existing comment to update (avoid spam)
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const botComment = comments.find(c =>
c.user.type === 'Bot' &&
c.body.includes('Documentation Suggestions')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
}
- name: Summary
if: always()
env:
HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
PR_NUM: ${{ steps.pr.outputs.number }}
run: |
{
echo "## 📚 Documentation Suggestions (Cherry-pick)"
echo ""
if [ "$HAS_SUGGESTIONS" == "true" ]; then
echo "Suggestions posted as PR comment on #${PR_NUM}."
else
echo "No documentation suggestions for this cherry-pick."
fi
} >> "$GITHUB_STEP_SUMMARY"
-115
View File
@@ -1,115 +0,0 @@
# Hotfix Review Monitor
#
# Runs daily and checks for merged PRs with the 'hotfix' label that have not
# received a post-merge review approval within one business day. Posts a summary to
# Slack if any are found. This is a SOC2 compensating control for the
# emergency hotfix fast path.
#
# Security note: No untrusted input (PR titles, bodies, etc.) is interpolated
# into shell commands. All PR metadata is read via gh API + jq, not via
# github.event context expressions.
#
# Required secrets:
# SLACK_WEBHOOK_PR_REVIEW_BOT - Incoming webhook URL for the #pr-review-ops channel
name: Hotfix Review Monitor
on:
schedule:
- cron: "30 13 * * 1-5" # 1:30 PM UTC weekdays
workflow_dispatch: {}
permissions:
contents: read
jobs:
check-hotfix-reviews:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
permissions:
pull-requests: read
timeout-minutes: 5
env:
REPO: ${{ github.repository }}
steps:
- name: Find unreviewed hotfixes
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# 80h lookback covers the Friday-to-Monday gap (72h) with buffer.
# Overlap on weekdays is harmless — reviewed PRs are filtered out below.
SINCE=$(date -u -v-80H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -d '80 hours ago' +%Y-%m-%dT%H:%M:%SZ)
SINCE_DATE=$(echo "$SINCE" | cut -dT -f1)
# Use the Search API to find hotfix PRs merged in the lookback window.
# The Pulls API with state=closed paginates through all closed PRs in
# the repo, which times out on large repos. The Search API supports
# merged:>DATE natively so GitHub does the filtering server-side.
gh api --paginate \
"search/issues?q=repo:${REPO}+is:pr+is:merged+label:hotfix+merged:>${SINCE_DATE}&per_page=100" \
--jq '[.items[] | {number, title, merged_at: .pull_request.merged_at}]' \
> /tmp/hotfix_prs.json
# Check each hotfix PR for a post-merge approving review
jq -r '.[].number' /tmp/hotfix_prs.json | while read -r PR_NUMBER; do
APPROVALS=$(gh api \
"repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
--jq "[.[] | select(.state == \"APPROVED\")] | length")
if [ "$APPROVALS" -eq 0 ]; then
jq ".[] | select(.number == ${PR_NUMBER})" /tmp/hotfix_prs.json
fi
done | jq -s '.' > /tmp/unreviewed.json
COUNT=$(jq 'length' /tmp/unreviewed.json)
echo "count=$COUNT" >> "$GITHUB_OUTPUT"
- name: Notify Slack
if: steps.check.outputs.count != '0'
env:
SLACK_WEBHOOK_PR_REVIEW_BOT: ${{ secrets.SLACK_WEBHOOK_PR_REVIEW_BOT }}
COUNT: ${{ steps.check.outputs.count }}
run: |
# Build Block Kit payload from JSON — no shell interpolation of PR titles.
# Why jq? PR titles are attacker-controllable input. By reading them
# through jq -r from the JSON file and passing the result to jq --arg,
# the content stays safely JSON-encoded in the final payload. Block Kit
# doesn't change this — the same jq pipeline feeds into the blocks
# structure instead of plain text.
PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (merged \(.merged_at | split("T")[0]))"' /tmp/unreviewed.json)
jq -n \
--arg count "$COUNT" \
--arg prs "$PRS" \
'{
text: ($count + " hotfix PR(s) still need post-merge review"),
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: (":rotating_light: *" + $count + " Hotfix PR(s) Need Post-Merge Review*")
}
},
{
type: "section",
text: { type: "mrkdwn", text: $prs }
},
{ type: "divider" },
{
type: "context",
elements: [{
type: "mrkdwn",
text: "Hotfix PRs require review within one business day of merge."
}]
}
]
}' | \
curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \
-H 'Content-Type: application/json' \
-d @-
defaults:
run:
shell: bash -euxo pipefail {0}
-116
View File
@@ -1,116 +0,0 @@
# Stale PR Review Reminder
#
# Runs daily on weekdays (second run at 8 PM UTC disabled during rollout) and posts a Slack summary of open PRs that
# have been awaiting review for more than 72 hours. Team-level signal only —
# no individual shaming.
#
# Security note: No untrusted input is interpolated into shell commands.
# All PR metadata is read via gh API + jq.
#
# Required secrets:
# SLACK_WEBHOOK_PR_REVIEW_BOT - Incoming webhook URL for the #pr-review-ops channel
name: Stale PR Review Reminder
on:
schedule:
- cron: "0 14 * * 1-5" # 2 PM UTC weekdays
# - cron: "0 20 * * 1-5" # 8 PM UTC weekdays — enable after initial rollout
workflow_dispatch: {}
permissions:
contents: read
jobs:
check-stale-prs:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
env:
REPO: ${{ github.repository }}
# Only surface PRs created on or after this date. Update this if the
# review process enforcement date changes.
PROCESS_START_DATE: "2026-03-19T00:00:00Z"
steps:
- name: Find PRs awaiting review longer than 72h
id: stale
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CUTOFF=$(date -u -v-72H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -d '72 hours ago' +%Y-%m-%dT%H:%M:%SZ)
# Get open, non-draft PRs with pending review requests, created before cutoff
# but after the review process start date (to exclude pre-existing backlog)
gh api --paginate \
"repos/${REPO}/pulls?state=open&sort=updated&direction=asc&per_page=100" \
--jq "[
.[] |
select(.draft == false) |
select(.created_at > \"$PROCESS_START_DATE\") |
select(.created_at < \"$CUTOFF\") |
select((.requested_reviewers | length > 0) or (.requested_teams | length > 0))
]" > /tmp/candidates.json
# Filter to PRs with zero approving reviews
jq -r '.[].number' /tmp/candidates.json | while read -r PR_NUMBER; do
APPROVALS=$(gh api \
"repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
--jq "[.[] | select(.state == \"APPROVED\")] | length" 2>/dev/null || echo "0")
if [ "$APPROVALS" -eq 0 ]; then
jq ".[] | select(.number == ${PR_NUMBER}) | {number, title, author: .user.login, created_at}" \
/tmp/candidates.json
fi
done | jq -s '.' > /tmp/awaiting.json
COUNT=$(jq 'length' /tmp/awaiting.json)
echo "count=$COUNT" >> "$GITHUB_OUTPUT"
- name: Notify Slack
if: steps.stale.outputs.count != '0'
env:
SLACK_WEBHOOK_PR_REVIEW_BOT: ${{ secrets.SLACK_WEBHOOK_PR_REVIEW_BOT }}
COUNT: ${{ steps.stale.outputs.count }}
run: |
# Build Block Kit payload from JSON — no shell interpolation of PR titles.
# Why jq? PR titles are attacker-controllable input. By reading them
# through jq -r from the JSON file and passing the result to jq --arg,
# the content stays safely JSON-encoded in the final payload.
PRS=$(jq -r '.[] | "• <https://github.com/'"${REPO}"'/pull/\(.number)|#\(.number)> — \(.title) (by \(.author), opened \(.created_at | split("T")[0]))"' /tmp/awaiting.json)
jq -n \
--arg count "$COUNT" \
--arg prs "$PRS" \
'{
text: ($count + " PR(s) awaiting review for >72 hours"),
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: (":hourglass_flowing_sand: *" + $count + " PR(s) Awaiting Review >72 Hours*")
}
},
{
type: "section",
text: { type: "mrkdwn", text: $prs }
},
{ type: "divider" },
{
type: "context",
elements: [{
type: "mrkdwn",
text: "PRs awaiting review are surfaced daily. Reviewers: pick one up or reassign."
}]
}
]
}' | \
curl -s -X POST "$SLACK_WEBHOOK_PR_REVIEW_BOT" \
-H 'Content-Type: application/json' \
-d @-
defaults:
run:
shell: bash -euxo pipefail {0}
-69
View File
@@ -1,69 +0,0 @@
#!/usr/bin/env python3
"""
This script analyzes all the highlights.scm files in our embedded languages and extensions.
It counts the number of unique instances of @{name} and the languages in which they are used.
This is useful to help avoid accidentally introducing new tags when appropriate ones already exist when adding new languages.
Flags:
-v, --verbose: Include a detailed list of languages for each tag found in the highlights.scm files.
"""
import argparse
import re
from collections import defaultdict
from pathlib import Path
from typing import Any
pattern = re.compile(r'@(?!_)[a-zA-Z_.]+')
def parse_arguments():
parser = argparse.ArgumentParser(description='Analyze highlights.scm files for unique instances and their languages.')
parser.add_argument('-v', '--verbose', action='store_true', help='Include a list of languages for each tag.')
return parser.parse_args()
def find_highlight_files(root_dir):
for path in Path(root_dir).rglob('highlights.scm'):
yield path
def count_instances(files):
instances: defaultdict[list[Any], dict[str, Any]] = defaultdict(lambda: {'count': 0, 'languages': set()})
for file_path in files:
language = file_path.parent.name
with open(file_path, "r") as file:
text = file.read()
matches = pattern.findall(text)
for match in matches:
instances[match]['count'] += 1
instances[match]['languages'].add(language)
return instances
def print_instances(instances, verbose=False):
for item, details in sorted(instances.items(), key=lambda x: x[0]):
languages = ', '.join(sorted(details['languages']))
if verbose:
print(f"{item} ({details['count']}) - [{languages}]")
else:
print(f"{item} ({details['count']})")
def main():
args = parse_arguments()
base_dir = Path(__file__).parent.parent
core_path = base_dir / 'crates/languages/src'
extension_path = base_dir / 'extensions/'
core_instances = count_instances(find_highlight_files(core_path))
extension_instances = count_instances(find_highlight_files(extension_path))
unique_extension_instances = {k: v for k, v in extension_instances.items() if k not in core_instances}
print('Shared:\n')
print_instances(core_instances, args.verbose)
if unique_extension_instances:
print('\nExtension-only:\n')
print_instances(unique_extension_instances, args.verbose)
if __name__ == '__main__':
main()
-231
View File
@@ -1,231 +0,0 @@
#!/usr/bin/env bash
#
# Remove Preview callouts from documentation for stable release.
#
# Usage:
# script/docs-strip-preview-callouts [--dry-run]
#
# This script finds and removes all Preview-related callouts from docs:
# > **Preview:** This feature is available in Zed Preview...
# > **Changed in Preview (v0.XXX).** See [release notes]...
#
# Then creates a PR with the changes.
#
# Options:
# --dry-run Show what would be changed without modifying files or creating PR
# --verbose Show detailed progress
#
# Run this as part of the stable release workflow.
set -euo pipefail
DRY_RUN=false
VERBOSE=false
# Colors
RED='\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}[strip-preview]${NC} $*" >&2
fi
}
error() {
echo -e "${RED}Error:${NC} $*" >&2
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
--verbose)
VERBOSE=true
shift
;;
-h|--help)
head -18 "$0" | tail -16
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
# Get repo root
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
DOCS_DIR="$REPO_ROOT/docs/src"
echo "Searching for Preview callouts in $DOCS_DIR..."
# Find files with either type of preview callout:
# - > **Preview:** ...
# - > **Changed in Preview ...
files_with_callouts=$(grep -rlE "> \*\*(Preview:|Changed in Preview)" "$DOCS_DIR" 2>/dev/null || true)
if [[ -z "$files_with_callouts" ]]; then
echo "No Preview callouts found. Nothing to do."
exit 0
fi
file_count=$(echo "$files_with_callouts" | wc -l | tr -d ' ')
echo "Found $file_count file(s) with Preview callouts:"
echo ""
for file in $files_with_callouts; do
relative_path="${file#$REPO_ROOT/}"
echo " $relative_path"
if [[ "$VERBOSE" == "true" ]]; then
grep -nE "> \*\*(Preview:|Changed in Preview)" "$file" | while read -r line; do
echo " $line"
done
fi
done
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
echo -e "${YELLOW}=== DRY RUN ===${NC}"
echo ""
echo "Would remove Preview callouts from the files above and create a PR."
echo ""
echo "Lines to be removed:"
echo ""
for file in $files_with_callouts; do
relative_path="${file#$REPO_ROOT/}"
echo "--- $relative_path ---"
grep -nE "> \*\*(Preview:|Changed in Preview)" "$file" || true
echo ""
done
echo -e "${YELLOW}=== END DRY RUN ===${NC}"
echo ""
echo "Run without --dry-run to apply changes and create PR."
exit 0
fi
# Check for clean working state (ignore untracked files)
if [[ -n "$(git status --porcelain docs/ | grep -v '^??' || true)" ]]; then
error "docs/ directory has uncommitted changes. Please commit or stash first."
fi
# Apply changes
echo "Removing Preview callouts..."
for file in $files_with_callouts; do
log "Processing: $file"
tmp_file=$(mktemp)
# Remove preview callout lines and their continuations
# Handles both:
# > **Preview:** This feature is available...
# > **Changed in Preview (v0.XXX).** See [release notes]...
awk '
BEGIN { in_callout = 0 }
/^> \*\*Preview:\*\*/ {
in_callout = 1
next
}
/^> \*\*Changed in Preview/ {
in_callout = 1
next
}
in_callout && /^>/ && !/^> \*\*/ {
next
}
in_callout && /^$/ {
in_callout = 0
next
}
{
in_callout = 0
print
}
' "$file" > "$tmp_file"
mv "$tmp_file" "$file"
echo " Updated: ${file#$REPO_ROOT/}"
done
echo ""
echo -e "${GREEN}Preview callouts removed from $file_count file(s).${NC}"
# Check if there are actual changes (in case callouts were in comments or something)
if [[ -z "$(git status --porcelain docs/)" ]]; then
echo ""
echo "No effective changes to commit (callouts may have been in non-rendered content)."
exit 0
fi
# Create branch and PR
echo ""
echo "Creating PR..."
BRANCH_NAME="docs/stable-release-$(date +%Y-%m-%d)"
log "Branch: $BRANCH_NAME"
git checkout -b "$BRANCH_NAME"
git add docs/src/
# Build file list for commit message
FILE_LIST=$(echo "$files_with_callouts" | sed "s|$REPO_ROOT/||" | sed 's/^/- /')
git commit -m "docs: Remove Preview callouts for stable release
Features documented with Preview callouts are now in Stable.
Files updated:
$FILE_LIST"
git push -u origin "$BRANCH_NAME"
gh pr create \
--title "docs: Remove Preview callouts for stable release" \
--body "This PR removes Preview callouts from documentation for features that are now in Stable.
## Files Updated
$(echo "$files_with_callouts" | sed "s|$REPO_ROOT/|• |")
## What This Does
Removes callouts like:
\`\`\`markdown
> **Preview:** This feature is available in Zed Preview. It will be included in the next Stable release.
\`\`\`
And:
\`\`\`markdown
> **Changed in Preview (v0.XXX).** See [release notes](/releases#0.XXX).
\`\`\`
These features are now in Stable, so the callouts are no longer needed.
Release Notes:
- N/A"
PR_URL=$(gh pr view --json url --jq '.url')
echo ""
echo -e "${GREEN}Done!${NC}"
echo ""
echo "PR created: $PR_URL"
echo ""
echo "Next steps:"
echo "1. Review the PR to ensure callouts were removed correctly"
echo "2. Merge the PR as part of the stable release"
-620
View File
@@ -1,620 +0,0 @@
#!/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
# Defaults
MODE=""
TARGET=""
DRY_RUN=false
VERBOSE=false
OUTPUT=""
MODEL="${DROID_MODEL:-claude-sonnet-4-5-20250929}"
OUTPUT_MODE="" # batch or immediate, auto-detected if not set
ADD_PREVIEW_CALLOUT="" # auto-detected if not set
# Colors for output
RED='\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 arguments
while [[ $# -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"
;;
esac
done
# Validate mode
if [[ -z "$MODE" ]]; then
error "Must specify one of: --pr, --commit, --diff, or --staged"
fi
# Get repo root
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
# Batch file location
BATCH_DIR="$REPO_ROOT/docs/.suggestions"
BATCH_FILE="$BATCH_DIR/pending.md"
# Temp directory for context assembly
TMPDIR=$(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 changes
2. Determine if any documentation updates are warranted
3. If yes, provide specific, actionable suggestions
4. 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 text
that 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, emotional
manipulation, or marketing-style superlatives).
For every docs file you suggest changing, treat the entire file as in scope for
brand review (not only the edited section). Include any additional full-file
voice 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 changes
CONVENTIONS
# 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 Stable
PREVIEW_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"
}
# ============================================================================
# Step 4: Run the analysis
# ============================================================================
run_analysis() {
if [[ "$DRY_RUN" == "true" ]]; then
echo -e "${GREEN}=== DRY RUN: Assembled Context ===${NC}"
echo ""
echo "Output mode: $OUTPUT_MODE"
echo "Preview callout: $ADD_PREVIEW_CALLOUT"
if [[ "$OUTPUT_MODE" == "batch" ]]; then
echo "Batch file: $BATCH_FILE"
fi
echo ""
cat "$TMPDIR/prompt.md"
echo ""
echo -e "${GREEN}=== End Context ===${NC}"
echo ""
echo "To run for real, remove --dry-run flag"
return
fi
# Check for droid CLI
if ! command -v droid &> /dev/null; then
error "droid CLI required. Install from: https://app.factory.ai/cli"
fi
log "Running analysis with model: $MODEL"
# Run the LLM
local suggestions
suggestions=$(droid exec -m "$MODEL" -f "$TMPDIR/prompt.md")
# Handle output based on mode
if [[ "$OUTPUT_MODE" == "batch" ]]; then
append_to_batch "$suggestions"
else
output_immediate "$suggestions"
fi
}
# ============================================================================
# Output handlers
# ============================================================================
append_to_batch() {
local suggestions="$1"
# Check if suggestions indicate no updates needed
if echo "$suggestions" | grep -q "No Documentation Updates Needed"; then
log "No documentation updates needed, skipping batch"
echo "$suggestions"
return
fi
# Create batch directory if needed
mkdir -p "$BATCH_DIR"
# Get PR info if available
local pr_number=""
local pr_title=""
if [[ -f "$TMPDIR/pr_number" ]]; then
pr_number=$(cat "$TMPDIR/pr_number")
pr_title=$(cat "$TMPDIR/pr_title")
fi
# Initialize batch file if it doesn't exist
if [[ ! -f "$BATCH_FILE" ]]; then
cat > "$BATCH_FILE" << 'BATCH_HEADER'
# Pending Documentation Suggestions
This file contains batched documentation suggestions for the next Preview release.
Run `script/docs-suggest-publish` to create a PR from these suggestions.
---
BATCH_HEADER
fi
# Append suggestions with metadata
{
echo ""
echo "## PR #$pr_number: $pr_title"
echo ""
echo "_Added: $(date -u +%Y-%m-%dT%H:%M:%SZ)_"
echo ""
echo "$suggestions"
echo ""
echo "---"
} >> "$BATCH_FILE"
echo -e "${GREEN}Suggestions batched to:${NC} $BATCH_FILE"
echo ""
echo "Batched suggestions for PR #$pr_number"
echo "Run 'script/docs-suggest-publish' when ready to create the docs PR."
}
output_immediate() {
local suggestions="$1"
if [[ -n "$OUTPUT" ]]; then
echo "$suggestions" > "$OUTPUT"
echo "Suggestions written to: $OUTPUT"
else
echo "$suggestions"
fi
}
# ============================================================================
# Main
# ============================================================================
main() {
get_diff
filter_changes
assemble_context
run_analysis
}
main
-591
View File
@@ -1,591 +0,0 @@
#!/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=false
KEEP_QUEUE=false
VERBOSE=false
SKIP_VALIDATION=false
BATCH_SIZE=10
MODEL="${DROID_MODEL:-claude-sonnet-4-5-latest}"
SUGGESTIONS_BRANCH="docs/suggestions-pending"
# Colors
RED='\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 arguments
while [[ $# -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"
;;
esac
done
# Get repo root
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
# Check if suggestions branch exists
log "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 0
fi
# Fetch the suggestions branch
log "Fetching suggestions branch..."
git fetch origin "$SUGGESTIONS_BRANCH"
# Check for manifest
if ! git show "origin/$SUGGESTIONS_BRANCH:manifest.json" > /dev/null 2>&1; then
echo "No manifest found on suggestions branch."
exit 0
fi
# Read manifest
MANIFEST=$(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 0
fi
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 0
fi
# 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)
fi
for command in "${REQUIRED_COMMANDS[@]}"; do
if ! command -v "$command" > /dev/null 2>&1; then
error "Required command not found: $command"
fi
done
# Remember current branch
ORIGINAL_BRANCH=$(git branch --show-current)
log "Current branch: $ORIGINAL_BRANCH"
# Create new branch for docs PR from latest main
git fetch origin main
DOCS_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 array
SUGGESTION_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 ""
fi
else
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)
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
fi
else
log "Feature flags file not found, skipping flag detection"
fi
# Split into batches
TOTAL=${#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..."
fi
echo ""
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 Claims
5. 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}"
done
echo ""
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 PR
if [[ "$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 suggestions
PR_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 from
recently 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 callout
4. 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 body
for 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
cat >> "$PR_BODY_FILE" << EOF
## PRs Included
EOF
echo "$MANIFEST" | jq -r '.suggestions[] | "- [PR #\(.pr)](\(.file)): \(.title)"' >> "$PR_BODY_FILE"
cat >> "$PR_BODY_FILE" << 'EOF'
Release Notes:
- N/A
EOF
git add docs/
git commit -m "docs: Auto-apply preview release suggestions
Auto-applied queued documentation suggestions from:
$(echo "$MANIFEST" | jq -r '.suggestions[] | "- PR #\(.pr)"')
Generated with script/docs-suggest-publish for human review in draft PR."
# Push and create PR
log "Pushing branch..."
git push -u origin "$DOCS_BRANCH"
log "Creating PR..."
PR_URL=$(gh pr create \
--draft \
--title "docs: Apply preview release suggestions" \
--body-file "$PR_BODY_FILE")
echo ""
echo -e "${GREEN}PR created:${NC} $PR_URL"
# Reset suggestions branch if not keeping
if [[ "$KEEP_QUEUE" != "true" ]]; then
echo ""
echo "Resetting suggestions queue..."
git checkout --orphan "${SUGGESTIONS_BRANCH}-reset"
git rm -rf . > /dev/null 2>&1 || true
cat > README.md << 'EOF'
# Documentation Suggestions Queue
This branch contains batched documentation suggestions for the next Preview release.
Each file represents suggestions from a merged PR. At preview branch cut time,
run `script/docs-suggest-publish` to create a documentation PR from these suggestions.
## Structure
- `suggestions/PR-XXXXX.md` - Suggestions for PR #XXXXX
- `manifest.json` - Index of all pending suggestions
## Workflow
1. PRs merged to main trigger documentation analysis
2. Suggestions are committed here as individual files
3. At preview release, suggestions are collected into a docs PR
4. After docs PR is created, this branch is reset
EOF
mkdir -p suggestions
echo '{"suggestions":[]}' > manifest.json
git add README.md suggestions manifest.json
git commit -m "Reset documentation suggestions queue
Previous suggestions published in: $PR_URL"
# Force push required: replacing the orphan suggestions branch with a clean slate
git push -f origin "${SUGGESTIONS_BRANCH}-reset:$SUGGESTIONS_BRANCH"
git checkout "$ORIGINAL_BRANCH"
git branch -D "${SUGGESTIONS_BRANCH}-reset"
echo "Suggestions queue reset."
else
git checkout "$ORIGINAL_BRANCH"
echo ""
echo "Suggestions queue kept (--keep-queue). Remember to reset manually after PR is merged."
fi
echo ""
echo -e "${GREEN}Done!${NC}"
echo ""
echo "Next steps:"
echo "1. Review the draft PR and verify all auto-applied docs changes"
echo "2. Confirm full-file brand voice pass for each touched docs file"
echo "3. Mark ready for review and merge"
-61
View File
@@ -1,61 +0,0 @@
#!/usr/bin/env bash
# This script manages prompt overrides for the Zed editor.
#
# It provides functionality to:
# 1. Link the current repository's prompt templates to Zed's configuration.
# 2. Create and link a separate Git worktree for prompt management.
# 3. Unlink previously linked prompt overrides.
#
# Usage:
# ./script_name.sh link # Link current repo's prompts
# ./script_name.sh link --worktree # Create and link a separate worktree
# ./script_name.sh unlink # Remove existing prompt override link
#
# The script ensures proper Git branch and worktree setup when using the
# --worktree option. It also provides informative output and error handling.
if [ "$1" = "link" ]; then
# Remove existing link (or directory)
rm -rf ~/.config/zed/prompt_overrides
if [ "$2" = "--worktree" ]; then
# Check if 'prompts' branch exists, create if not
if ! git show-ref --quiet refs/heads/prompts; then
git branch prompts
fi
# Check if 'prompts' worktree exists
if git worktree list | grep -q "../zed_prompts"; then
echo "Worktree already exists at ../zed_prompts."
else
# Create worktree if it doesn't exist
git worktree add ../zed_prompts prompts || git worktree add ../zed_prompts -b prompts
fi
ln -sf "$(realpath "$(pwd)/../zed_prompts/assets/prompts")" ~/.config/zed/prompt_overrides
echo "Linked $(realpath "$(pwd)/../zed_prompts/assets/prompts") to ~/.config/zed/prompt_overrides"
echo -e "\033[0;33mDon't forget you have it linked, or your prompts will go stale\033[0m"
else
ln -sf "$(pwd)/assets/prompts" ~/.config/zed/prompt_overrides
echo "Linked $(pwd)/assets/prompts to ~/.config/zed/prompt_overrides"
fi
elif [ "$1" = "unlink" ]; then
if [ -e ~/.config/zed/prompt_overrides ]; then
# Remove symbolic link
rm -rf ~/.config/zed/prompt_overrides
echo "Unlinked ~/.config/zed/prompt_overrides"
else
echo -e "\033[33mWarning: No file exists at ~/.config/zed/prompt_overrides\033[0m"
fi
else
echo "This script helps you manage prompt overrides for Zed."
echo "You can link this directory to have Zed use the contents of your current repo templates as your active prompts,"
echo "or store your modifications in a separate Git worktree."
echo
echo "Usage: $0 [link [--worktree]|unlink]"
echo
echo "Options:"
echo " link Create a symbolic link from ./assets/prompts to ~/.config/zed/prompt_overrides"
echo " link --worktree Create a 'prompts' Git worktree in ../prompts, then link ../prompts/assets/prompts"
echo " to ~/.config/zed/prompt_overrides"
echo " unlink Remove the symbolic link at ~/.config/zed/prompt_overrides"
exit 1
fi
-70
View File
@@ -1,70 +0,0 @@
#!/usr/bin/env node --redirect-warnings=/dev/null
const fs = require("fs");
const { randomBytes } = require("crypto");
const { execFileSync } = require("child_process");
const {
minimizeTestPlan,
buildTests,
runTests,
} = require("./randomized-test-minimize");
const { ZED_SERVER_URL } = process.env;
if (!ZED_SERVER_URL) throw new Error("Missing env var `ZED_SERVER_URL`");
main();
async function main() {
buildTests();
const seed = randomU64();
const commit = execFileSync("git", ["rev-parse", "HEAD"], {
encoding: "utf8",
}).trim();
console.log("commit:", commit);
console.log("starting seed:", seed);
const planPath = "target/test-plan.json";
const minPlanPath = "target/test-plan.min.json";
const failingSeed = runTests({
SEED: seed,
SAVE_PLAN: planPath,
ITERATIONS: 50000,
OPERATIONS: 200,
});
if (!failingSeed) {
console.log("tests passed");
return;
}
console.log("found failure at seed", failingSeed);
const minimizedSeed = minimizeTestPlan(planPath, minPlanPath);
const minimizedPlan = fs.readFileSync(minPlanPath, "utf8");
console.log("minimized plan:\n", minimizedPlan);
const url = `${ZED_SERVER_URL}/api/randomized_test_failure`;
const body = {
seed: minimizedSeed,
plan: JSON.parse(minimizedPlan),
commit: commit,
};
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
process.exit(1);
}
function randomU64() {
const bytes = randomBytes(8);
const hexString = bytes.reduce(
(string, byte) => string + byte.toString(16),
"",
);
return BigInt("0x" + hexString).toString(10);
}
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env node --redirect-warnings=/dev/null
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
const FAILING_SEED_REGEX = /failing seed: (\d+)/gi;
const CARGO_TEST_ARGS = ["--release", "--lib", "--package", "collab"];
if (require.main === module) {
if (process.argv.length < 4) {
process.stderr.write(
"usage: script/randomized-test-minimize <input-plan> <output-plan> [start-index]\n",
);
process.exit(1);
}
minimizeTestPlan(
process.argv[2],
process.argv[3],
parseInt(process.argv[4]) || 0,
);
}
function minimizeTestPlan(inputPlanPath, outputPlanPath, startIndex = 0) {
const tempPlanPath = inputPlanPath + ".try";
fs.copyFileSync(inputPlanPath, outputPlanPath);
let testPlan = JSON.parse(fs.readFileSync(outputPlanPath, "utf8"));
process.stderr.write("minimizing failing test plan...\n");
for (let ix = startIndex; ix < testPlan.length; ix++) {
// Skip 'MutateClients' entries, since they themselves are not single operations.
if (testPlan[ix].MutateClients) {
continue;
}
// Remove a row from the test plan
const newTestPlan = testPlan.slice();
newTestPlan.splice(ix, 1);
fs.writeFileSync(tempPlanPath, serializeTestPlan(newTestPlan), "utf8");
process.stderr.write(
`${ix}/${testPlan.length}: ${JSON.stringify(testPlan[ix])}`,
);
const failingSeed = runTests({
SEED: "0",
LOAD_PLAN: tempPlanPath,
SAVE_PLAN: tempPlanPath,
ITERATIONS: "500",
});
// If the test failed, keep the test plan with the removed row. Reload the test
// plan from the JSON file, since the test itself will remove any operations
// which are no longer valid before saving the test plan.
if (failingSeed != null) {
process.stderr.write(` - remove. failing seed: ${failingSeed}.\n`);
fs.copyFileSync(tempPlanPath, outputPlanPath);
testPlan = JSON.parse(fs.readFileSync(outputPlanPath, "utf8"));
ix--;
} else {
process.stderr.write(` - keep.\n`);
}
}
fs.unlinkSync(tempPlanPath);
// Re-run the final minimized plan to get the correct failing seed.
// This is a workaround for the fact that the execution order can
// slightly change when replaying a test plan after it has been
// saved and loaded.
const failingSeed = runTests({
SEED: "0",
ITERATIONS: "5000",
LOAD_PLAN: outputPlanPath,
});
process.stderr.write(`final test plan: ${outputPlanPath}\n`);
process.stderr.write(`final seed: ${failingSeed}\n`);
return failingSeed;
}
function buildTests() {
const { status } = spawnSync(
"cargo",
["test", "--no-run", ...CARGO_TEST_ARGS],
{
stdio: "inherit",
encoding: "utf8",
env: {
...process.env,
},
},
);
if (status !== 0) {
throw new Error("build failed");
}
}
function runTests(env) {
const { status, stdout } = spawnSync(
"cargo",
["test", ...CARGO_TEST_ARGS, "random_project_collaboration"],
{
stdio: "pipe",
encoding: "utf8",
env: {
...process.env,
...env,
},
},
);
if (status !== 0) {
FAILING_SEED_REGEX.lastIndex = 0;
const match = FAILING_SEED_REGEX.exec(stdout);
if (!match) {
process.stderr.write("test failed, but no failing seed found:\n");
process.stderr.write(stdout);
process.stderr.write("\n");
process.exit(1);
}
return match[1];
} else {
return null;
}
}
function serializeTestPlan(plan) {
return "[\n" + plan.map((row) => JSON.stringify(row)).join(",\n") + "\n]\n";
}
exports.buildTests = buildTests;
exports.runTests = runTests;
exports.minimizeTestPlan = minimizeTestPlan;
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -xeuo pipefail
# if root or if sudo/unavailable, define an empty variable
if [ "$(id -u)" -eq 0 ]
then maysudo=''
else maysudo="$(command -v sudo || command -v doas || true)"
fi
deps=(
clang
)
$maysudo apt-get update
$maysudo apt-get install -y "${deps[@]}"
exit 0
-264
View File
@@ -1,264 +0,0 @@
#!/usr/bin/env python3
"""Run the background crash-agent MVP pipeline locally.
Default mode is dry-run: generate branch + agent output without commit/push/PR.
"""
import argparse
import json
import os
import re
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path
CRASH_ID_PATTERN = re.compile(r"^[A-Za-z0-9]+-[A-Za-z0-9]+$")
REPO_ROOT = Path(__file__).resolve().parents[1]
def run(command: list[str], check: bool = True, capture_output: bool = False) -> subprocess.CompletedProcess:
return subprocess.run(
command,
cwd=REPO_ROOT,
text=True,
check=check,
capture_output=capture_output,
)
def validate_crash_ids(ids: list[str]) -> list[str]:
valid = []
for crash_id in ids:
if not CRASH_ID_PATTERN.match(crash_id):
print(f"WARNING: Skipping invalid crash ID format: '{crash_id}'", file=sys.stderr)
continue
valid.append(crash_id)
return valid
MAX_TOP = 100
def prefetch_crash_data(crashes: list[dict[str, str]], output_dir: str) -> None:
"""Fetch crash reports and save to output_dir/crash-{ID}.md.
Each crash item must contain:
- crash_id: short ID used by the pipeline (e.g. ZED-202)
- fetch_id: identifier passed to script/sentry-fetch (short or numeric)
"""
os.makedirs(output_dir, exist_ok=True)
for crash in crashes:
crash_id = crash["crash_id"]
fetch_id = crash["fetch_id"]
output_path = os.path.join(output_dir, f"crash-{crash_id}.md")
result = run(
["python3", "script/sentry-fetch", fetch_id],
check=False,
capture_output=True,
)
if result.returncode != 0:
print(
f"WARNING: Failed to fetch crash data for {crash_id} "
f"(fetch id: {fetch_id}): {result.stderr.strip()}",
file=sys.stderr,
)
continue
with open(output_path, "w", encoding="utf-8") as f:
f.write(result.stdout)
print(
f"Fetched crash data for {crash_id} (fetch id: {fetch_id}) -> {output_path}",
file=sys.stderr,
)
def resolve_crashes(args) -> list[dict[str, str]]:
if args.crash_ids:
raw = [item.strip() for item in args.crash_ids.split(",") if item.strip()]
crash_ids = validate_crash_ids(raw)
return [{"crash_id": crash_id, "fetch_id": crash_id} for crash_id in crash_ids]
top = min(args.top, MAX_TOP)
if args.top > MAX_TOP:
print(f"Capping --top from {args.top} to {MAX_TOP}", file=sys.stderr)
with tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False) as file:
output_path = file.name
try:
run(
[
"python3",
"script/select-sentry-crash-candidates",
"--org",
args.org,
"--top",
str(top),
"--sample-size",
str(args.sample_size),
"--output",
output_path,
],
capture_output=True,
)
with open(output_path, "r", encoding="utf-8") as file:
payload = json.load(file)
crashes = []
for item in payload.get("selected", []):
crash_id = item.get("short_id")
issue_id = item.get("issue_id")
if not crash_id:
continue
crashes.append(
{
"crash_id": crash_id,
"fetch_id": str(issue_id) if issue_id else crash_id,
}
)
valid_crash_ids = set(validate_crash_ids([item["crash_id"] for item in crashes]))
return [item for item in crashes if item["crash_id"] in valid_crash_ids]
finally:
try:
os.remove(output_path)
except OSError:
pass
def write_prompt(crash_id: str, crash_data_file: str | None = None) -> Path:
if crash_data_file:
fetch_step = (
f"The crash report has been pre-fetched and is available at: {crash_data_file}\n"
f"Read this file to get the crash data. Do not call script/sentry-fetch.\n"
f"\n"
f"1. Read the crash report from {crash_data_file}"
)
else:
fetch_step = f"1. Fetch crash data with: script/sentry-fetch {crash_id}"
prompt = f"""You are running the weekly background crash-fix MVP pipeline for crash {crash_id}.
Required workflow:
{fetch_step}
2. Read and follow `.rules`.
3. Follow .factory/prompts/crash/investigate.md and write ANALYSIS.md
4. Follow .factory/prompts/crash/link-issues.md and write LINKED_ISSUES.md
5. Follow .factory/prompts/crash/fix.md to implement a minimal fix with tests
6. Run validators required by the fix prompt for the affected code paths
7. Write PR_BODY.md with sections:
- Crash Summary
- Root Cause
- Fix
- Validation
- Potentially Related Issues (High/Medium/Low from LINKED_ISSUES.md)
- Reviewer Checklist
- Release Notes (final section, formatted as "Release Notes:", blank line, then one bullet like "- N/A" when not user-facing)
Constraints:
- Keep changes narrowly scoped to this crash.
- If the crash is not solvable with available context, write a clear blocker summary to PR_BODY.md.
"""
file_path = Path(tempfile.gettempdir()) / f"background-agent-{crash_id}.md"
file_path.write_text(prompt, encoding="utf-8")
return file_path
def run_pipeline_for_crash(args, crash_id: str) -> dict:
branch = f"background-agent/mvp-{crash_id.lower()}-{datetime.utcnow().strftime('%Y%m%d')}"
if args.reset_branch:
run(["git", "fetch", "origin", "main"], check=False)
run(["git", "checkout", "-B", branch, "origin/main"])
prompt_file = write_prompt(crash_id)
try:
droid_command = [
args.droid_bin,
"exec",
"--auto",
"medium",
"-m",
args.model,
"-f",
str(prompt_file),
]
completed = run(droid_command, check=False)
has_changes = run(["git", "diff", "--quiet"], check=False).returncode != 0
return {
"crash_id": crash_id,
"branch": branch,
"droid_exit_code": completed.returncode,
"has_changes": has_changes,
}
finally:
try:
prompt_file.unlink()
except OSError:
pass
def main() -> int:
parser = argparse.ArgumentParser(description="Run local background-agent MVP dry-run workflow")
parser.add_argument("--crash-ids", help="Comma-separated crash IDs (e.g. ZED-4VS,ZED-123)")
parser.add_argument("--top", type=int, default=3, help="Top N crashes when --crash-ids is omitted (max 100)")
parser.add_argument(
"--sample-size",
type=int,
default=100,
help="Number of unresolved issues to consider for candidate selection",
)
parser.add_argument("--org", default="zed-dev", help="Sentry org slug")
parser.add_argument(
"--select-only",
action="store_true",
help="Resolve crash IDs and print them comma-separated, then exit. No agent execution.",
)
parser.add_argument(
"--prefetch-dir",
help="When used with --select-only, also fetch crash data via script/sentry-fetch "
"and save reports to DIR/crash-{ID}.md for each resolved ID.",
)
parser.add_argument("--model", default=os.environ.get("DROID_MODEL", "claude-opus-4-5-20251101"))
parser.add_argument("--droid-bin", default=os.environ.get("DROID_BIN", "droid"))
parser.add_argument(
"--reset-branch",
action="store_true",
help="For each crash, checkout a fresh local branch from origin/main",
)
args = parser.parse_args()
crashes = resolve_crashes(args)
if not crashes:
print("No crash IDs were selected.", file=sys.stderr)
return 1
crash_ids = [item["crash_id"] for item in crashes]
if args.select_only:
if args.prefetch_dir:
prefetch_crash_data(crashes, args.prefetch_dir)
print(",".join(crash_ids))
return 0
print(f"Running local dry-run for crashes: {', '.join(crash_ids)}")
results = [run_pipeline_for_crash(args, crash_id) for crash_id in crash_ids]
print("\nRun summary:")
for result in results:
print(
f"- {result['crash_id']}: droid_exit={result['droid_exit_code']} "
f"changes={result['has_changes']} branch={result['branch']}"
)
failures = [result for result in results if result["droid_exit_code"] != 0]
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())
-139
View File
@@ -1,139 +0,0 @@
#!/usr/bin/env bash
#
# Test docs-suggest on multiple PRs and generate a summary report.
#
# Usage:
# script/test-docs-suggest-batch [--limit N] [--output FILE]
#
# This script runs docs-suggest in dry-run mode on recent merged PRs
# to validate the context assembly and help tune the prompt.
set -euo pipefail
LIMIT=50
OUTPUT="docs-suggest-batch-results.md"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
while [[ $# -gt 0 ]]; do
case $1 in
--limit)
LIMIT="$2"
shift 2
;;
--output)
OUTPUT="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
echo "Testing docs-suggest on $LIMIT recent merged PRs..."
echo "Output: $OUTPUT"
echo ""
# Get list of PRs
PRS=$(gh pr list --state merged --limit "$LIMIT" --json number,title --jq '.[] | "\(.number)|\(.title)"')
# Initialize output file
cat > "$OUTPUT" << HEADER
# docs-suggest Batch Test Results
**Date**: $(date +%Y-%m-%d)
**PRs tested**: $LIMIT
## Summary
| PR | Title | Result | Source Files | Notes |
|----|-------|--------|--------------|-------|
HEADER
# Track stats
total=0
has_source=0
no_source=0
errors=0
while IFS='|' read -r pr_num title; do
total=$((total + 1))
echo -n "[$total/$LIMIT] PR #$pr_num: "
# Run dry-run and capture output
tmpfile=$(mktemp)
if "$SCRIPT_DIR/docs-suggest" --pr "$pr_num" --dry-run 2>"$tmpfile.err" >"$tmpfile.out"; then
# Check if it found source files
if grep -q "No documentation-relevant changes" "$tmpfile.out"; then
result="No source changes"
no_source=$((no_source + 1))
source_count="0"
echo "skipped (no source)"
else
# Extract source file count from verbose output
source_count=$(grep -oE '[0-9]+ source' "$tmpfile.err" 2>/dev/null | grep -oE '[0-9]+' || echo "?")
result="Has source changes"
has_source=$((has_source + 1))
echo "has $source_count source files"
fi
notes=""
else
result="Error"
errors=$((errors + 1))
source_count="-"
notes=$(head -1 "$tmpfile.err" 2>/dev/null || echo "unknown error")
echo "error"
fi
# Escape title for markdown table
title_escaped=$(echo "$title" | sed 's/|/\\|/g' | cut -c1-60)
# Add row to table
echo "| [#$pr_num](https://github.com/zed-industries/zed/pull/$pr_num) | $title_escaped | $result | $source_count | $notes |" >> "$OUTPUT"
rm -f "$tmpfile" "$tmpfile.out" "$tmpfile.err"
done <<< "$PRS"
# Add summary stats
cat >> "$OUTPUT" << STATS
## Statistics
- **Total PRs**: $total
- **With source changes**: $has_source ($(( has_source * 100 / total ))%)
- **No source changes**: $no_source ($(( no_source * 100 / total ))%)
- **Errors**: $errors
## Observations
_Add manual observations here after reviewing results._
## Sample Contexts
STATS
# Add 3 sample contexts from PRs with source changes
echo "" >> "$OUTPUT"
echo "### Sample 1: PR with source changes" >> "$OUTPUT"
echo "" >> "$OUTPUT"
sample_pr=$(gh pr list --state merged --limit 20 --json number --jq '.[].number' | while read pr; do
if "$SCRIPT_DIR/docs-suggest" --pr "$pr" --dry-run 2>/dev/null | grep -q "## Code Diff"; then
echo "$pr"
break
fi
done)
if [[ -n "$sample_pr" ]]; then
echo "PR #$sample_pr:" >> "$OUTPUT"
echo "" >> "$OUTPUT"
echo '```' >> "$OUTPUT"
"$SCRIPT_DIR/docs-suggest" --pr "$sample_pr" --dry-run 2>/dev/null | head -100 >> "$OUTPUT"
echo '```' >> "$OUTPUT"
fi
echo ""
echo "Done! Results written to: $OUTPUT"
echo ""
echo "Stats: $has_source with source changes, $no_source without, $errors errors"
-38
View File
@@ -1,38 +0,0 @@
## Triage Watcher v0.1
# This is a small script to watch for new issues on the Zed repository and open them in a new browser tab interactively.
#
## Installing Julia
#
# You need Julia installed on your system:
# curl -fsSL https://install.julialang.org | sh
#
## Running this script:
# 1. It only works on Macos/Linux
# Open a new Julia repl with `julia` inside the `zed` repo
# 2. Paste the following code
# 3. Whenever you close your computer, just type the Up arrow on the REPL + enter to rerun the loop again to resume
function get_issues()
entries = filter(x -> occursin("state:needs triage", x), split(read(`gh issue list -L 10`, String), '\n'))
top = findfirst.('\t', entries) .- 1
[entries[i][begin:top[i]] for i in eachindex(entries)]
end
nums = get_issues();
while true
new_nums = get_issues()
# Open each new issue in a new browser tab
for issue_num in setdiff(new_nums, nums)
url = "https://github.com/zed-industries/zed/issues/" * issue_num
println("\nOpening $url")
open_tab = `/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome $url`
try
sound_file = "/Users/mrg/Downloads/mario_coin_sound.mp3"
run(`afplay -v 0.02 $sound_file`)
finally
end
run(open_tab)
end
nums = new_nums
print("🧘🏼")
sleep(60)
end
@@ -16,7 +16,11 @@ pub(crate) fn deploy_collab() -> Workflow {
named::workflow()
.with_minimal_permissions()
.on(Event::default().push(Push::default().add_tag("collab-production")))
.on(Event::default().push(
Push::default()
.add_tag("collab-production")
.add_tag("collab-staging"),
))
.add_env(("DOCKER_BUILDKIT", "1"))
.add_job(style.name, style.job)
.add_job(tests.name, tests.job)