Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T00:30:58.936Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches ยท 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

docs_suggestions.yml

490 lines ยท 18.6 KB ยท yaml
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
Served at tenant.openagents/omega Member data and write actions are omitted.