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