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
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://cli.factory.ai/install.sh | bash
43 echo "${HOME}/.factory/bin" >> "$GITHUB_PATH"
44 echo "DROID_BIN=${HOME}/.factory/bin/droid" >> "$GITHUB_ENV"
45 # Verify installation
46 "${HOME}/.factory/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 0: Guardrails are loaded via AGENTS.md in each phase
87
88 # Phase 2: Explore Repository (Read-Only)
89 - name: "Phase 2: Explore Repository"
90 id: phase2
91 run: |
92 "$DROID_BIN" exec \
93 --model "$DROID_MODEL" \
94 --autonomy read-only \
95 --prompt-file .factory/prompts/docs-automation/phase2-explore.md \
96 --output /tmp/phase2-output.json \
97 --format json
98 echo "Repository exploration complete"
99 cat /tmp/phase2-output.json
100
101 # Phase 3: Analyze Changes (Read-Only)
102 - name: "Phase 3: Analyze Changes"
103 id: phase3
104 run: |
105 CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
106 "$DROID_BIN" exec \
107 --model "$DROID_MODEL" \
108 --autonomy read-only \
109 --prompt-file .factory/prompts/docs-automation/phase3-analyze.md \
110 --context "Changed files: $CHANGED_FILES" \
111 --context-file /tmp/phase2-output.json \
112 --output /tmp/phase3-output.md \
113 --format markdown
114 echo "Change analysis complete"
115 cat /tmp/phase3-output.md
116
117 # Phase 4: Plan Documentation Impact (Read-Only)
118 - name: "Phase 4: Plan Documentation Impact"
119 id: phase4
120 run: |
121 "$DROID_BIN" exec \
122 --model "$DROID_MODEL" \
123 --autonomy read-only \
124 --prompt-file .factory/prompts/docs-automation/phase4-plan.md \
125 --context-file /tmp/phase3-output.md \
126 --context-file docs/AGENTS.md \
127 --output /tmp/phase4-plan.md \
128 --format markdown
129 echo "Documentation plan complete"
130 cat /tmp/phase4-plan.md
131
132 # Check if updates are required
133 if grep -q "Documentation Updates Required: No" /tmp/phase4-plan.md; then
134 echo "updates_required=false" >> "$GITHUB_OUTPUT"
135 else
136 echo "updates_required=true" >> "$GITHUB_OUTPUT"
137 fi
138
139 # Phase 5: Apply Plan (Write-Enabled)
140 - name: "Phase 5: Apply Documentation Plan"
141 id: phase5
142 if: steps.phase4.outputs.updates_required == 'true'
143 run: |
144 "$DROID_BIN" exec \
145 --model "$DROID_MODEL" \
146 --autonomy medium \
147 --prompt-file .factory/prompts/docs-automation/phase5-apply.md \
148 --context-file /tmp/phase4-plan.md \
149 --context-file docs/AGENTS.md \
150 --context-file docs/.rules \
151 --output /tmp/phase5-report.md \
152 --format markdown
153 echo "Documentation updates applied"
154 cat /tmp/phase5-report.md
155
156 # Phase 5b: Format with Prettier
157 - name: "Phase 5b: Format with Prettier"
158 id: phase5b
159 if: steps.phase4.outputs.updates_required == 'true'
160 run: |
161 echo "Formatting documentation with Prettier..."
162 cd docs && prettier --write src/
163
164 echo "Verifying Prettier formatting passes..."
165 cd docs && prettier --check src/
166
167 echo "Prettier formatting complete"
168
169 # Phase 6: Summarize Changes
170 - name: "Phase 6: Summarize Changes"
171 id: phase6
172 if: steps.phase4.outputs.updates_required == 'true'
173 run: |
174 # Get git diff of docs
175 git diff docs/src/ > /tmp/docs-diff.txt || true
176
177 "$DROID_BIN" exec \
178 --model "$DROID_MODEL" \
179 --autonomy read-only \
180 --prompt-file .factory/prompts/docs-automation/phase6-summarize.md \
181 --context-file /tmp/phase5-report.md \
182 --context-file /tmp/phase3-output.md \
183 --context "Trigger SHA: ${{ steps.changed.outputs.sha }}" \
184 --output /tmp/phase6-summary.md \
185 --format markdown
186 echo "Summary generated"
187 cat /tmp/phase6-summary.md
188
189 # Phase 7: Commit and Open PR
190 - name: "Phase 7: Create PR"
191 id: phase7
192 if: steps.phase4.outputs.updates_required == 'true'
193 run: |
194 # Check if there are actual changes
195 if git diff --quiet docs/src/; then
196 echo "No documentation changes detected"
197 exit 0
198 fi
199
200 # Configure git
201 git config user.name "factory-droid[bot]"
202 git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
203
204 # Daily batch branch - one branch per day, multiple commits accumulate
205 BRANCH_NAME="docs/auto-update-$(date +%Y-%m-%d)"
206
207 # Check if branch already exists on remote
208 if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
209 echo "Branch $BRANCH_NAME exists, checking out and updating..."
210 git fetch origin "$BRANCH_NAME"
211 git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
212 else
213 echo "Creating new branch $BRANCH_NAME..."
214 git checkout -b "$BRANCH_NAME"
215 fi
216
217 # Stage and commit
218 git add docs/src/
219 SUMMARY=$(head -50 < /tmp/phase6-summary.md)
220 git commit -m "docs: auto-update documentation
221
222 ${SUMMARY}
223
224 Triggered by: ${{ steps.changed.outputs.source }} ${{ steps.changed.outputs.ref }}
225
226 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"
227
228 # Push
229 git push -u origin "$BRANCH_NAME"
230
231 # Check if PR already exists for this branch
232 EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number' || echo "")
233
234 if [ -n "$EXISTING_PR" ]; then
235 echo "PR #$EXISTING_PR already exists for branch $BRANCH_NAME, updated with new commit"
236 else
237 # Create new PR
238 gh pr create \
239 --title "docs: automated documentation update ($(date +%Y-%m-%d))" \
240 --body-file /tmp/phase6-summary.md \
241 --base main || true
242 echo "PR created on branch: $BRANCH_NAME"
243 fi
244 env:
245 GH_TOKEN: ${{ github.token }}
246
247 # Summary output
248 - name: "Summary"
249 if: always()
250 run: |
251 echo "## Documentation Automation Summary" >> "$GITHUB_STEP_SUMMARY"
252 echo "" >> "$GITHUB_STEP_SUMMARY"
253
254 if [ "${{ steps.phase4.outputs.updates_required }}" == "false" ]; then
255 echo "No documentation updates required for this change." >> "$GITHUB_STEP_SUMMARY"
256 elif [ -f /tmp/phase6-summary.md ]; then
257 cat /tmp/phase6-summary.md >> "$GITHUB_STEP_SUMMARY"
258 else
259 echo "Workflow completed. Check individual phase outputs for details." >> "$GITHUB_STEP_SUMMARY"
260 fi