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 DROID_MODEL: claude-opus-4-5-20251101
27
28jobs:
29 docs-automation:
30 runs-on: ubuntu-latest
31 timeout-minutes: 30
32
33 steps:
34 - name: Checkout repository
35 uses: actions/checkout@v4
36 with:
37 fetch-depth: 0
38
39 - name: Install Droid CLI
40 id: install-droid
41 run: |
42 curl -fsSL https://app.factory.ai/cli | sh
43 echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
44 echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
45 # Verify installation
46 "${HOME}/.local/bin/droid" --version
47
48 - name: Setup Node.js (for Prettier)
49 uses: actions/setup-node@v4
50 with:
51 node-version: '20'
52
53 - name: Install Prettier
54 run: npm install -g prettier
55
56 - name: Get changed files
57 id: changed
58 run: |
59 if [ -n "${{ inputs.pr_number }}" ]; then
60 # Get full PR diff
61 echo "Analyzing PR #${{ inputs.pr_number }}"
62 echo "source=pr" >> "$GITHUB_OUTPUT"
63 echo "ref=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
64 gh pr diff "${{ inputs.pr_number }}" --name-only > /tmp/changed_files.txt
65 elif [ -n "${{ inputs.trigger_sha }}" ]; then
66 # Get single commit diff
67 SHA="${{ inputs.trigger_sha }}"
68 echo "Analyzing commit $SHA"
69 echo "source=commit" >> "$GITHUB_OUTPUT"
70 echo "ref=$SHA" >> "$GITHUB_OUTPUT"
71 git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt
72 else
73 # Default to current commit
74 SHA="${{ github.sha }}"
75 echo "Analyzing commit $SHA"
76 echo "source=commit" >> "$GITHUB_OUTPUT"
77 echo "ref=$SHA" >> "$GITHUB_OUTPUT"
78 git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt || git diff --name-only HEAD~1 HEAD > /tmp/changed_files.txt
79 fi
80
81 echo "Changed files:"
82 cat /tmp/changed_files.txt
83 env:
84 GH_TOKEN: ${{ github.token }}
85
86 # Phase 3: Analyze Changes (Read-Only - default)
87 - name: "Phase 3: Analyze Changes"
88 id: phase3
89 run: |
90 CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
91 echo "Analyzing changes in: $CHANGED_FILES"
92
93 "$DROID_BIN" exec \
94 -m "$DROID_MODEL" \
95 "$(cat .factory/prompts/docs-automation/phase3-analyze.md)
96
97 Changed files: $CHANGED_FILES" \
98 > /tmp/phase3-output.md 2>&1 || true
99 echo "Change analysis complete"
100 cat /tmp/phase3-output.md
101
102 # Phase 4: Plan Documentation Impact (Read-Only - default)
103 - name: "Phase 4: Plan Documentation Impact"
104 id: phase4
105 run: |
106 CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
107 PHASE3_OUTPUT=$(cat /tmp/phase3-output.md)
108
109 "$DROID_BIN" exec \
110 -m "$DROID_MODEL" \
111 "$(cat .factory/prompts/docs-automation/phase4-plan.md)
112
113 ## Context from Phase 3
114
115 ### Changed Files
116 $CHANGED_FILES
117
118 ### Phase 3 Analysis
119 $PHASE3_OUTPUT" \
120 > /tmp/phase4-plan.md 2>&1 || true
121 echo "Documentation plan complete"
122 cat /tmp/phase4-plan.md
123
124 # Check if updates are required
125 if grep -q "NO_UPDATES_REQUIRED" /tmp/phase4-plan.md; then
126 echo "updates_required=false" >> "$GITHUB_OUTPUT"
127 else
128 echo "updates_required=true" >> "$GITHUB_OUTPUT"
129 fi
130
131 # Phase 5: Apply Plan (Write-Enabled with --auto medium)
132 - name: "Phase 5: Apply Documentation Plan"
133 id: phase5
134 if: steps.phase4.outputs.updates_required == 'true'
135 run: |
136 PHASE4_PLAN=$(cat /tmp/phase4-plan.md)
137
138 "$DROID_BIN" exec \
139 -m "$DROID_MODEL" \
140 --auto medium \
141 "$(cat .factory/prompts/docs-automation/phase5-apply.md)
142
143 ## Documentation Plan from Phase 4
144 $PHASE4_PLAN" \
145 > /tmp/phase5-report.md 2>&1 || true
146 echo "Documentation updates applied"
147 cat /tmp/phase5-report.md
148
149 # Phase 5b: Format with Prettier
150 - name: "Phase 5b: Format with Prettier"
151 id: phase5b
152 if: steps.phase4.outputs.updates_required == 'true'
153 run: |
154 echo "Formatting documentation with Prettier..."
155 cd docs && prettier --write src/
156
157 echo "Verifying Prettier formatting passes..."
158 cd docs && prettier --check src/
159
160 echo "Prettier formatting complete"
161
162 # Phase 6: Summarize Changes (Read-Only - default)
163 - name: "Phase 6: Summarize Changes"
164 id: phase6
165 if: steps.phase4.outputs.updates_required == 'true'
166 run: |
167 # Get git diff of docs
168 git diff docs/src/ > /tmp/docs-diff.txt || true
169
170 PHASE5_REPORT=$(cat /tmp/phase5-report.md)
171 DOCS_DIFF=$(cat /tmp/docs-diff.txt)
172
173 "$DROID_BIN" exec \
174 -m "$DROID_MODEL" \
175 "$(cat .factory/prompts/docs-automation/phase6-summarize.md)
176
177 ## Context
178
179 ### Phase 5 Report
180 $PHASE5_REPORT
181
182 ### Documentation Diff
183 \`\`\`diff
184 $DOCS_DIFF
185 \`\`\`" \
186 > /tmp/phase6-summary.md 2>&1 || true
187 echo "Summary generated"
188 cat /tmp/phase6-summary.md
189
190 # Phase 7: Commit and Open PR
191 - name: "Phase 7: Create PR"
192 id: phase7
193 if: steps.phase4.outputs.updates_required == 'true'
194 run: |
195 # Check if there are actual changes
196 if git diff --quiet docs/src/; then
197 echo "No documentation changes detected"
198 exit 0
199 fi
200
201 # Configure git
202 git config user.name "factory-droid[bot]"
203 git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
204
205 # Daily batch branch - one branch per day, multiple commits accumulate
206 BRANCH_NAME="docs/auto-update-$(date +%Y-%m-%d)"
207
208 # Get source PR info for attribution
209 SOURCE_PR_INFO=""
210 if [ "${{ steps.changed.outputs.source }}" == "pr" ]; then
211 PR_NUM="${{ steps.changed.outputs.ref }}"
212 PR_DETAILS=$(gh pr view "$PR_NUM" --json title,author,url 2>/dev/null || echo "{}")
213 SOURCE_TITLE=$(echo "$PR_DETAILS" | jq -r '.title // "Unknown"')
214 SOURCE_AUTHOR=$(echo "$PR_DETAILS" | jq -r '.author.login // "Unknown"')
215 SOURCE_URL=$(echo "$PR_DETAILS" | jq -r '.url // ""')
216 SOURCE_PR_INFO="
217 ---
218 **Source**: [#$PR_NUM]($SOURCE_URL) - $SOURCE_TITLE
219 **Author**: @$SOURCE_AUTHOR
220 "
221 fi
222
223 # Stash local changes from phase 5
224 git stash push -m "docs-automation-changes" -- docs/src/
225
226 # Check if branch already exists on remote
227 if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
228 echo "Branch $BRANCH_NAME exists, checking out and updating..."
229 git fetch origin "$BRANCH_NAME"
230 git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
231 else
232 echo "Creating new branch $BRANCH_NAME..."
233 git checkout -b "$BRANCH_NAME"
234 fi
235
236 # Apply stashed changes
237 git stash pop || true
238
239 # Stage and commit
240 git add docs/src/
241 SUMMARY=$(head -50 < /tmp/phase6-summary.md)
242 git commit -m "docs: auto-update documentation
243
244 ${SUMMARY}
245
246 Triggered by: ${{ steps.changed.outputs.source }} ${{ steps.changed.outputs.ref }}
247
248 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"
249
250 # Push
251 git push -u origin "$BRANCH_NAME"
252
253 # Build the PR body section for this update
254 PR_BODY_SECTION="## Update from $(date '+%Y-%m-%d %H:%M')
255 $SOURCE_PR_INFO
256 $(cat /tmp/phase6-summary.md)
257 "
258
259 # Check if PR already exists for this branch
260 EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number,url,body --jq '.[0]' || echo "")
261
262 if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
263 PR_NUM=$(echo "$EXISTING_PR" | jq -r '.number')
264 PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
265 EXISTING_BODY=$(echo "$EXISTING_PR" | jq -r '.body // ""')
266
267 # Append new summary to existing PR body
268 NEW_BODY="${EXISTING_BODY}
269
270 ---
271
272 ${PR_BODY_SECTION}"
273
274 echo "$NEW_BODY" > /tmp/updated-pr-body.md
275 gh pr edit "$PR_NUM" --body-file /tmp/updated-pr-body.md
276
277 echo "PR #$PR_NUM updated: $PR_URL"
278 else
279 # Create new PR
280 echo "$PR_BODY_SECTION" > /tmp/new-pr-body.md
281 gh pr create \
282 --title "docs: automated documentation update ($(date +%Y-%m-%d))" \
283 --body-file /tmp/new-pr-body.md \
284 --base main || true
285 echo "PR created on branch: $BRANCH_NAME"
286 fi
287 env:
288 GH_TOKEN: ${{ github.token }}
289
290 # Summary output
291 - name: "Summary"
292 if: always()
293 run: |
294 echo "## Documentation Automation Summary" >> "$GITHUB_STEP_SUMMARY"
295 echo "" >> "$GITHUB_STEP_SUMMARY"
296
297 if [ "${{ steps.phase4.outputs.updates_required }}" == "false" ]; then
298 echo "No documentation updates required for this change." >> "$GITHUB_STEP_SUMMARY"
299 elif [ -f /tmp/phase6-summary.md ]; then
300 cat /tmp/phase6-summary.md >> "$GITHUB_STEP_SUMMARY"
301 else
302 echo "Workflow completed. Check individual phase outputs for details." >> "$GITHUB_STEP_SUMMARY"
303 fi