1name: Documentation Automation
2
3on:
4 # push:
5 # branches: [main]
6 # paths:
7 # - 'crates/**'
8 # - 'extensions/**'
9 workflow_dispatch:
10 inputs:
11 pr_number:
12 description: 'PR number to analyze (gets full PR diff)'
13 required: false
14 type: string
15 trigger_sha:
16 description: 'Commit SHA to analyze (ignored if pr_number is set)'
17 required: false
18 type: string
19
20permissions:
21 contents: write
22 pull-requests: write
23
24env:
25 FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
26 ANALYSIS_MODEL: gemini-3-flash-preview
27 WRITING_MODEL: claude-opus-4-5-20251101
28
29jobs:
30 docs-automation:
31 runs-on: ubuntu-latest
32 timeout-minutes: 30
33
34 steps:
35 - name: Checkout repository
36 uses: actions/checkout@v4
37 with:
38 fetch-depth: 0
39
40 - name: Install Droid CLI
41 id: install-droid
42 run: |
43 curl -fsSL https://app.factory.ai/cli | sh
44 echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
45 echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
46 # Verify installation
47 "${HOME}/.local/bin/droid" --version
48
49 - name: Setup Node.js (for Prettier)
50 uses: actions/setup-node@v4
51 with:
52 node-version: '20'
53
54 - name: Install Prettier
55 run: npm install -g prettier
56
57 - name: Get changed files
58 id: changed
59 run: |
60 if [ -n "${{ inputs.pr_number }}" ]; then
61 # Get full PR diff
62 echo "Analyzing PR #${{ inputs.pr_number }}"
63 echo "source=pr" >> "$GITHUB_OUTPUT"
64 echo "ref=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
65 gh pr diff "${{ inputs.pr_number }}" --name-only > /tmp/changed_files.txt
66 elif [ -n "${{ inputs.trigger_sha }}" ]; then
67 # Get single commit diff
68 SHA="${{ inputs.trigger_sha }}"
69 echo "Analyzing commit $SHA"
70 echo "source=commit" >> "$GITHUB_OUTPUT"
71 echo "ref=$SHA" >> "$GITHUB_OUTPUT"
72 git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt
73 else
74 # Default to current commit
75 SHA="${{ github.sha }}"
76 echo "Analyzing commit $SHA"
77 echo "source=commit" >> "$GITHUB_OUTPUT"
78 echo "ref=$SHA" >> "$GITHUB_OUTPUT"
79 git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt || git diff --name-only HEAD~1 HEAD > /tmp/changed_files.txt
80 fi
81
82 echo "Changed files:"
83 cat /tmp/changed_files.txt
84 env:
85 GH_TOKEN: ${{ github.token }}
86
87 # Filter for docs-relevant files
88 - name: "Filter docs-relevant files"
89 id: filter
90 run: |
91 # Patterns for files that could affect documentation
92 PATTERNS="crates/.*/src/.*\.rs|assets/settings/.*|assets/keymaps/.*|extensions/.*|docs/.*"
93
94 RELEVANT=$(grep -E "$PATTERNS" /tmp/changed_files.txt || true)
95 if [ -z "$RELEVANT" ]; then
96 echo "No docs-relevant files changed"
97 echo "has_relevant=false" >> "$GITHUB_OUTPUT"
98 else
99 echo "Docs-relevant files found:"
100 echo "$RELEVANT"
101 echo "has_relevant=true" >> "$GITHUB_OUTPUT"
102 fi
103
104 # Combined: Analyze + Plan (using fast model)
105 - name: "Analyze & Plan"
106 id: analyze
107 if: steps.filter.outputs.has_relevant == 'true'
108 run: |
109 CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
110
111 GUIDELINES='## Documentation Guidelines
112 ### Requires Update: New features, changed keybindings, modified settings, deprecated functionality
113 ### No Update: Internal refactoring, performance fixes, bug fixes, test/CI changes
114 ### Output JSON: {"updates_required": bool, "summary": str, "planned_changes": [{file, section, change_type, description}]}'
115
116 "$DROID_BIN" exec \
117 -m "$ANALYSIS_MODEL" \
118 --auto low \
119 "Analyze code changes for documentation impact.
120
121 $GUIDELINES
122
123 Changed files: $CHANGED_FILES
124
125 Output the JSON structure. Be conservative - only flag user-visible changes." \
126 > /tmp/analysis.json 2>&1 || true
127
128 echo "Analysis complete:"
129 cat /tmp/analysis.json
130
131 # Check if updates required
132 if grep -q '"updates_required":\s*true' /tmp/analysis.json; then
133 echo "updates_required=true" >> "$GITHUB_OUTPUT"
134 else
135 echo "updates_required=false" >> "$GITHUB_OUTPUT"
136 fi
137
138 # Combined: Apply + Summarize (using writing model)
139 - name: "Apply Documentation Changes"
140 id: apply
141 if: steps.analyze.outputs.updates_required == 'true'
142 run: |
143 ANALYSIS=$(cat /tmp/analysis.json)
144
145 "$DROID_BIN" exec \
146 -m "$WRITING_MODEL" \
147 --auto medium \
148 "Apply documentation changes from this analysis:
149
150 $ANALYSIS
151
152 Instructions:
153 1. Edit each specified file
154 2. Follow mdBook format, use {#kb action::Name} for keybindings
155 3. Output summary:
156
157 ## Changes Applied
158 - [file]: [change]
159
160 ## Summary for PR
161 [2-3 sentences]" \
162 > /tmp/apply-report.md 2>&1 || true
163
164 echo "Changes applied:"
165 cat /tmp/apply-report.md
166 cp /tmp/apply-report.md /tmp/phase6-summary.md
167
168 # Phase 5b: Format with Prettier
169 # Format with Prettier (only changed files)
170 - name: "Format with Prettier"
171 id: format
172 if: steps.analyze.outputs.updates_required == 'true'
173 run: |
174 CHANGED_DOCS=$(git diff --name-only docs/src/ | sed 's|^docs/||' | tr '\n' ' ')
175 if [ -n "$CHANGED_DOCS" ]; then
176 echo "Formatting: $CHANGED_DOCS"
177 cd docs && prettier --write "$CHANGED_DOCS"
178 fi
179
180 # Create PR
181 - name: "Create PR"
182 id: create_pr
183 if: steps.analyze.outputs.updates_required == 'true'
184 run: |
185 # Check if there are actual changes
186 if git diff --quiet docs/src/; then
187 echo "No documentation changes detected"
188 exit 0
189 fi
190
191 # Configure git
192 git config user.name "factory-droid[bot]"
193 git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
194
195 # Daily batch branch - one branch per day, multiple commits accumulate
196 BRANCH_NAME="docs/auto-update-$(date +%Y-%m-%d)"
197
198 # Get source PR info for attribution
199 SOURCE_PR_INFO=""
200 if [ "${{ steps.changed.outputs.source }}" == "pr" ]; then
201 PR_NUM="${{ steps.changed.outputs.ref }}"
202 PR_DETAILS=$(gh pr view "$PR_NUM" --json title,author,url 2>/dev/null || echo "{}")
203 SOURCE_TITLE=$(echo "$PR_DETAILS" | jq -r '.title // "Unknown"')
204 SOURCE_AUTHOR=$(echo "$PR_DETAILS" | jq -r '.author.login // "Unknown"')
205 SOURCE_URL=$(echo "$PR_DETAILS" | jq -r '.url // ""')
206 SOURCE_PR_INFO="
207 ---
208 **Source**: [#$PR_NUM]($SOURCE_URL) - $SOURCE_TITLE
209 **Author**: @$SOURCE_AUTHOR
210 "
211 fi
212
213 # Stash local changes from phase 5
214 git stash push -m "docs-automation-changes" -- docs/src/
215
216 # Check if branch already exists on remote
217 if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
218 echo "Branch $BRANCH_NAME exists, checking out and updating..."
219 git fetch origin "$BRANCH_NAME"
220 git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
221 else
222 echo "Creating new branch $BRANCH_NAME..."
223 git checkout -b "$BRANCH_NAME"
224 fi
225
226 # Apply stashed changes
227 git stash pop || true
228
229 # Stage and commit
230 git add docs/src/
231 SUMMARY=$(head -50 < /tmp/phase6-summary.md)
232 git commit -m "docs: auto-update documentation
233
234 ${SUMMARY}
235
236 Triggered by: ${{ steps.changed.outputs.source }} ${{ steps.changed.outputs.ref }}
237
238 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"
239
240 # Push
241 git push -u origin "$BRANCH_NAME"
242
243 # Build the PR body section for this update
244 PR_BODY_SECTION="## Update from $(date '+%Y-%m-%d %H:%M')
245 $SOURCE_PR_INFO
246 $(cat /tmp/phase6-summary.md)
247 "
248
249 # Check if PR already exists for this branch
250 EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number,url,body --jq '.[0]' || echo "")
251
252 if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
253 PR_NUM=$(echo "$EXISTING_PR" | jq -r '.number')
254 PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
255 EXISTING_BODY=$(echo "$EXISTING_PR" | jq -r '.body // ""')
256
257 # Append new summary to existing PR body
258 NEW_BODY="${EXISTING_BODY}
259
260 ---
261
262 ${PR_BODY_SECTION}"
263
264 echo "$NEW_BODY" > /tmp/updated-pr-body.md
265 gh pr edit "$PR_NUM" --body-file /tmp/updated-pr-body.md
266
267 echo "PR #$PR_NUM updated: $PR_URL"
268 else
269 # Create new PR
270 echo "$PR_BODY_SECTION" > /tmp/new-pr-body.md
271 gh pr create \
272 --title "docs: automated documentation update ($(date +%Y-%m-%d))" \
273 --body-file /tmp/new-pr-body.md \
274 --base main || true
275 echo "PR created on branch: $BRANCH_NAME"
276 fi
277 env:
278 GH_TOKEN: ${{ github.token }}
279
280 # Summary output
281 - name: "Summary"
282 if: always()
283 run: |
284 echo "## Documentation Automation Summary" >> "$GITHUB_STEP_SUMMARY"
285 echo "" >> "$GITHUB_STEP_SUMMARY"
286
287 if [ "${{ steps.filter.outputs.has_relevant }}" == "false" ]; then
288 echo "No docs-relevant files changed. Skipped analysis." >> "$GITHUB_STEP_SUMMARY"
289 elif [ "${{ steps.analyze.outputs.updates_required }}" == "false" ]; then
290 echo "No documentation updates required for this change." >> "$GITHUB_STEP_SUMMARY"
291 elif [ -f /tmp/phase6-summary.md ]; then
292 cat /tmp/phase6-summary.md >> "$GITHUB_STEP_SUMMARY"
293 else
294 echo "Workflow completed. Check individual step outputs for details." >> "$GITHUB_STEP_SUMMARY"
295 fi