Skip to repository content490 lines ยท 18.6 KB ยท yaml
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T00:30:58.936Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches ยท 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
docs_suggestions.yml
1name: Documentation Suggestions
2
3# Stable release callout stripping plan (not wired yet):
4# 1. Add a separate stable-only workflow trigger on `release.published`
5# with `github.event.release.prerelease == false`.
6# 2. In that workflow, run `script/docs-strip-preview-callouts` on `main`.
7# 3. Open a PR with stripped preview callouts for human review.
8# 4. Fail loudly on script errors or when no callout changes are produced.
9# 5. Keep this workflow focused on suggestions only until that stable workflow is added.
10
11on:
12 # Analyze merged PRs on main and cherry-picks to release branches. Each job
13 # further restricts the event actions and target branch that it handles.
14 pull_request:
15 types: [closed, opened, synchronize]
16 branches:
17 - main
18 - "v0.*"
19 paths:
20 - "crates/**/*.rs"
21
22 # Manual trigger for testing
23 workflow_dispatch:
24 inputs:
25 pr_number:
26 description: "PR number to analyze"
27 required: true
28 type: string
29 mode:
30 description: "Output mode"
31 required: true
32 type: choice
33 options:
34 - batch
35 - immediate
36 default: batch
37
38# Both jobs below declare their own `permissions:` blocks, so no job uses this
39# top-level default. Least privilege therefore grants nothing here.
40permissions: {}
41
42env:
43 DROID_MODEL: claude-sonnet-4-5-20250929
44 SUGGESTIONS_BRANCH: docs/suggestions-pending
45
46jobs:
47 # Job for PRs merged to main - batch suggestions to branch
48 # Only runs for PRs from the same repo (not forks) since secrets aren't available for fork PRs
49 batch-suggestions:
50 runs-on: ubuntu-latest
51 timeout-minutes: 10
52 permissions:
53 contents: write
54 pull-requests: read
55 if: |
56 (github.event_name == 'pull_request' &&
57 github.event.pull_request.merged == true &&
58 github.event.pull_request.base.ref == 'main' &&
59 github.event.pull_request.head.repo.full_name == github.repository) ||
60 (github.event_name == 'workflow_dispatch' && inputs.mode == 'batch')
61
62 steps:
63 - name: Checkout repository
64 uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
65 with:
66 fetch-depth: 0
67 token: ${{ secrets.GITHUB_TOKEN }}
68
69 - name: Install Droid CLI
70 run: |
71 # Retry with exponential backoff for transient network/auth issues
72 MAX_RETRIES=3
73 for i in $(seq 1 "$MAX_RETRIES"); do
74 echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
75 if curl -fsSL https://app.factory.ai/cli | sh; then
76 echo "Droid CLI installed successfully"
77 break
78 fi
79 if [ "$i" -eq "$MAX_RETRIES" ]; then
80 echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
81 exit 1
82 fi
83 sleep $((i * 5))
84 done
85 env:
86 FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
87
88 - name: Get PR info
89 id: pr
90 env:
91 INPUT_PR_NUMBER: ${{ inputs.pr_number }}
92 EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
93 GH_TOKEN: ${{ github.token }}
94 run: |
95 if [ -n "$INPUT_PR_NUMBER" ]; then
96 PR_NUM="$INPUT_PR_NUMBER"
97 else
98 PR_NUM="$EVENT_PR_NUMBER"
99 fi
100 if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
101 echo "::error::Invalid PR number: $PR_NUM"
102 exit 1
103 fi
104 echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
105 PR_TITLE=$(gh pr view "$PR_NUM" --json title --jq '.title' | tr -d '\n\r' | head -c 200)
106 EOF_MARKER="EOF_$(openssl rand -hex 8)"
107 {
108 echo "title<<$EOF_MARKER"
109 echo "$PR_TITLE"
110 echo "$EOF_MARKER"
111 } >> "$GITHUB_OUTPUT"
112
113 - name: Analyze PR for documentation needs
114 id: analyze
115 env:
116 GH_TOKEN: ${{ github.token }}
117 FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
118 PR_NUMBER: ${{ steps.pr.outputs.number }}
119 run: |
120 export PATH="${HOME}/.local/bin:${PATH}"
121
122 # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
123 # Unset GH_TOKEN first to allow gh auth login to store credentials
124 echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)
125
126 OUTPUT_FILE=$(mktemp)
127
128 # Retry with exponential backoff for transient Factory API failures
129 MAX_RETRIES=3
130 for i in $(seq 1 "$MAX_RETRIES"); do
131 echo "Attempt $i of $MAX_RETRIES to analyze PR..."
132 if ./script/docs-suggest \
133 --pr "$PR_NUMBER" \
134 --immediate \
135 --preview \
136 --output "$OUTPUT_FILE" \
137 --verbose; then
138 echo "Analysis completed successfully"
139 break
140 fi
141 if [ "$i" -eq "$MAX_RETRIES" ]; then
142 echo "Analysis failed after $MAX_RETRIES attempts"
143 exit 1
144 fi
145 echo "Retrying in $((i * 5)) seconds..."
146 sleep $((i * 5))
147 done
148
149 # Check if we got actionable suggestions (not "no updates needed")
150 if grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
151 ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
152 echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
153 echo "output_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
154 else
155 echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
156 echo "No actionable documentation suggestions for this PR"
157 cat "$OUTPUT_FILE"
158 fi
159
160 - name: Commit suggestions to queue branch
161 if: steps.analyze.outputs.has_suggestions == 'true'
162 env:
163 PR_NUM: ${{ steps.pr.outputs.number }}
164 PR_TITLE: ${{ steps.pr.outputs.title }}
165 OUTPUT_FILE: ${{ steps.analyze.outputs.output_file }}
166 REPO: ${{ github.repository }}
167 run: |
168 set -euo pipefail
169
170 # Configure git
171 git config user.name "github-actions[bot]"
172 git config user.email "github-actions[bot]@users.noreply.github.com"
173
174 # Retry loop for handling concurrent pushes
175 MAX_RETRIES=3
176 for i in $(seq 1 "$MAX_RETRIES"); do
177 echo "Attempt $i of $MAX_RETRIES"
178
179 # Fetch and checkout suggestions branch (create if doesn't exist)
180 if git ls-remote --exit-code --heads origin "$SUGGESTIONS_BRANCH" > /dev/null 2>&1; then
181 git fetch origin "$SUGGESTIONS_BRANCH"
182 git checkout -B "$SUGGESTIONS_BRANCH" "origin/$SUGGESTIONS_BRANCH"
183 else
184 # Create orphan branch for clean history
185 git checkout --orphan "$SUGGESTIONS_BRANCH"
186 git rm -rf . > /dev/null 2>&1 || true
187
188 # Initialize with README
189 cat > README.md << 'EOF'
190 # Documentation Suggestions Queue
191
192 This branch contains batched documentation suggestions for the next Preview release.
193
194 Each file represents suggestions from a merged PR. At preview branch cut time,
195 run `script/docs-suggest-publish` to create a documentation PR from these suggestions.
196
197 ## Structure
198
199 - `suggestions/PR-XXXXX.md` - Suggestions for PR #XXXXX
200 - `manifest.json` - Index of all pending suggestions
201
202 ## Workflow
203
204 1. PRs merged to main trigger documentation analysis
205 2. Suggestions are committed here as individual files
206 3. At preview release, suggestions are collected into a docs PR
207 4. After docs PR is created, this branch is reset
208 EOF
209
210 mkdir -p suggestions
211 echo '{"suggestions":[]}' > manifest.json
212 git add README.md suggestions manifest.json
213 git commit -m "Initialize documentation suggestions queue"
214 fi
215
216 # Create suggestion file
217 SUGGESTION_FILE="suggestions/PR-${PR_NUM}.md"
218
219 {
220 echo "# PR #${PR_NUM}: ${PR_TITLE}"
221 echo ""
222 echo "_Merged: $(date -u +%Y-%m-%dT%H:%M:%SZ)_"
223 echo "_PR: https://github.com/${REPO}/pull/${PR_NUM}_"
224 echo ""
225 cat "$OUTPUT_FILE"
226 } > "$SUGGESTION_FILE"
227
228 # Update manifest
229 MANIFEST=$(cat manifest.json)
230 NEW_ENTRY="{\"pr\":${PR_NUM},\"title\":$(echo "$PR_TITLE" | jq -R .),\"file\":\"$SUGGESTION_FILE\",\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
231
232 # Add to manifest if not already present
233 if ! echo "$MANIFEST" | jq -e ".suggestions[] | select(.pr == $PR_NUM)" > /dev/null 2>&1; then
234 echo "$MANIFEST" | jq ".suggestions += [$NEW_ENTRY]" > manifest.json
235 fi
236
237 # Commit
238 git add "$SUGGESTION_FILE" manifest.json
239 git commit -m "docs: Add suggestions for PR #${PR_NUM}
240
241 ${PR_TITLE}
242
243 Auto-generated documentation suggestions for review at next preview release."
244
245 # Try to push
246 if git push origin "$SUGGESTIONS_BRANCH"; then
247 echo "Successfully pushed suggestions"
248 break
249 else
250 echo "Push failed, retrying..."
251 if [ "$i" -eq "$MAX_RETRIES" ]; then
252 echo "Failed after $MAX_RETRIES attempts"
253 exit 1
254 fi
255 sleep $((i * 2))
256 fi
257 done
258
259 - name: Summary
260 if: always()
261 env:
262 HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
263 PR_NUM: ${{ steps.pr.outputs.number }}
264 REPO: ${{ github.repository }}
265 run: |
266 {
267 echo "## Documentation Suggestions"
268 echo ""
269 if [ "$HAS_SUGGESTIONS" == "true" ]; then
270 echo "โ
Suggestions queued for PR #${PR_NUM}"
271 echo ""
272 echo "View pending suggestions: [docs/suggestions-pending branch](https://github.com/${REPO}/tree/${SUGGESTIONS_BRANCH})"
273 else
274 echo "No documentation updates needed for this PR."
275 fi
276 } >> "$GITHUB_STEP_SUMMARY"
277
278 # Job for cherry-picks to release branches - immediate output as PR comment
279 cherry-pick-suggestions:
280 runs-on: ubuntu-latest
281 timeout-minutes: 10
282 permissions:
283 contents: read
284 pull-requests: write
285 concurrency:
286 group: docs-suggestions-${{ github.event.pull_request.number || inputs.pr_number || 'manual' }}
287 cancel-in-progress: true
288 if: |
289 (github.event_name == 'pull_request' &&
290 contains(fromJSON('["opened","synchronize"]'), github.event.action) &&
291 startsWith(github.event.pull_request.base.ref, 'v0.') &&
292 github.event.pull_request.head.repo.full_name == github.repository &&
293 contains(fromJSON('["MEMBER","OWNER"]'),
294 github.event.pull_request.author_association)) ||
295 (github.event_name == 'workflow_dispatch' && inputs.mode == 'immediate')
296
297 steps:
298 - name: Checkout repository
299 uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
300 with:
301 fetch-depth: 0
302 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || '' }}
303 persist-credentials: false
304
305 - name: Install Droid CLI
306 run: |
307 # Retry with exponential backoff for transient network/auth issues
308 MAX_RETRIES=3
309 for i in $(seq 1 "$MAX_RETRIES"); do
310 echo "Attempt $i of $MAX_RETRIES to install Droid CLI..."
311 if curl -fsSL https://app.factory.ai/cli | sh; then
312 echo "Droid CLI installed successfully"
313 break
314 fi
315 if [ "$i" -eq "$MAX_RETRIES" ]; then
316 echo "Failed to install Droid CLI after $MAX_RETRIES attempts"
317 exit 1
318 fi
319 sleep $((i * 5))
320 done
321 env:
322 FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
323
324 - name: Get PR number
325 id: pr
326 env:
327 INPUT_PR_NUMBER: ${{ inputs.pr_number }}
328 EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
329 run: |
330 if [ -n "$INPUT_PR_NUMBER" ]; then
331 PR_NUM="$INPUT_PR_NUMBER"
332 else
333 PR_NUM="$EVENT_PR_NUMBER"
334 fi
335 if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then
336 echo "::error::Invalid PR number: $PR_NUM"
337 exit 1
338 fi
339 echo "number=$PR_NUM" >> "$GITHUB_OUTPUT"
340
341 - name: Analyze PR for documentation needs
342 id: analyze
343 env:
344 GH_TOKEN: ${{ github.token }}
345 FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
346 PR_NUMBER: ${{ steps.pr.outputs.number }}
347 run: |
348 export PATH="${HOME}/.local/bin:${PATH}"
349
350 # Ensure gh CLI is authenticated (GH_TOKEN may not be auto-detected)
351 # Unset GH_TOKEN first to allow gh auth login to store credentials
352 echo "$GH_TOKEN" | (unset GH_TOKEN && gh auth login --with-token)
353
354 OUTPUT_FILE="${RUNNER_TEMP}/suggestions.md"
355
356 # Cherry-picks don't get preview callout
357 # Retry with exponential backoff for transient Factory API failures
358 MAX_RETRIES=3
359 for i in $(seq 1 "$MAX_RETRIES"); do
360 echo "Attempt $i of $MAX_RETRIES to analyze PR..."
361 if ./script/docs-suggest \
362 --pr "$PR_NUMBER" \
363 --immediate \
364 --no-preview \
365 --output "$OUTPUT_FILE" \
366 --verbose; then
367 echo "Analysis completed successfully"
368 break
369 fi
370 if [ "$i" -eq "$MAX_RETRIES" ]; then
371 echo "Analysis failed after $MAX_RETRIES attempts"
372 exit 1
373 fi
374 echo "Retrying in $((i * 5)) seconds..."
375 sleep $((i * 5))
376 done
377
378 # Check if we got actionable suggestions
379 if [ -s "$OUTPUT_FILE" ] && \
380 grep -q "Documentation Suggestions" "$OUTPUT_FILE" && \
381 ! grep -q "No Documentation Updates Needed" "$OUTPUT_FILE"; then
382 echo "has_suggestions=true" >> "$GITHUB_OUTPUT"
383 echo "suggestions_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
384 else
385 echo "has_suggestions=false" >> "$GITHUB_OUTPUT"
386 fi
387
388 - name: Post suggestions as PR comment
389 if: steps.analyze.outputs.has_suggestions == 'true'
390 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
391 env:
392 SUGGESTIONS_FILE: ${{ steps.analyze.outputs.suggestions_file }}
393 PR_NUMBER: ${{ steps.pr.outputs.number }}
394 with:
395 script: |
396 const fs = require('fs');
397
398 // Read suggestions from file
399 const suggestionsRaw = fs.readFileSync(process.env.SUGGESTIONS_FILE, 'utf8');
400
401 // Sanitize AI-generated content
402 let sanitized = suggestionsRaw
403 // Strip HTML tags
404 .replace(/<[^>]*>/g, '')
405 // Strip markdown links but keep display text
406 .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
407 // Strip raw URLs
408 .replace(/https?:\/\/[^\s)>\]]+/g, '[link removed]')
409 // Strip protocol-relative URLs
410 .replace(/\/\/[^\s)>\]]+\.[^\s)>\]]+/g, '[link removed]')
411 // Neutralize @-mentions (preserve JSDoc-style annotations)
412 .replace(/@(?!param\b|returns?\b|throws?\b|typedef\b|type\b|see\b|example\b|since\b|deprecated\b|default\b)(\w+)/g, '`@$1`')
413 // Strip cross-repo references that could be confused with real links
414 .replace(/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#\d+/g, '[ref removed]');
415
416 // Truncate to 20,000 characters
417 if (sanitized.length > 20000) {
418 sanitized = sanitized.substring(0, 20000) + '\n\nโฆ(truncated)';
419 }
420
421 // Parse and validate PR number
422 const prNumber = parseInt(process.env.PR_NUMBER, 10);
423 if (isNaN(prNumber) || prNumber <= 0) {
424 core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`);
425 return;
426 }
427
428 const body = `## ๐ Documentation Suggestions
429
430 This cherry-pick contains changes that may need documentation updates.
431
432 ${sanitized}
433
434 ---
435 > **Note:** This comment was generated automatically by an AI model analyzing
436 > code changes. Suggestions may contain inaccuracies โ please verify before acting.
437
438 <details>
439 <summary>About this comment</summary>
440
441 This comment was generated automatically by analyzing code changes in this cherry-pick.
442 Cherry-picks typically don't need new documentation since the feature was already
443 documented when merged to main, but please verify.
444
445 </details>`;
446
447 // Find existing comment to update (avoid spam)
448 const { data: comments } = await github.rest.issues.listComments({
449 owner: context.repo.owner,
450 repo: context.repo.repo,
451 issue_number: prNumber
452 });
453
454 const botComment = comments.find(c =>
455 c.user.type === 'Bot' &&
456 c.body.includes('Documentation Suggestions')
457 );
458
459 if (botComment) {
460 await github.rest.issues.updateComment({
461 owner: context.repo.owner,
462 repo: context.repo.repo,
463 comment_id: botComment.id,
464 body: body
465 });
466 } else {
467 await github.rest.issues.createComment({
468 owner: context.repo.owner,
469 repo: context.repo.repo,
470 issue_number: prNumber,
471 body: body
472 });
473 }
474
475 - name: Summary
476 if: always()
477 env:
478 HAS_SUGGESTIONS: ${{ steps.analyze.outputs.has_suggestions }}
479 PR_NUM: ${{ steps.pr.outputs.number }}
480 run: |
481 {
482 echo "## ๐ Documentation Suggestions (Cherry-pick)"
483 echo ""
484 if [ "$HAS_SUGGESTIONS" == "true" ]; then
485 echo "Suggestions posted as PR comment on #${PR_NUM}."
486 else
487 echo "No documentation suggestions for this cherry-pick."
488 fi
489 } >> "$GITHUB_STEP_SUMMARY"
490