From bc459efbd8d2435406d0cab60f063956ac2e426e Mon Sep 17 00:00:00 2001 From: Carmel Soceanu Date: Tue, 19 Aug 2025 13:03:30 +0300 Subject: [PATCH 1/4] Update suggest_docs.yml --- .github/workflows/suggest_docs.yml | 47 +++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/.github/workflows/suggest_docs.yml b/.github/workflows/suggest_docs.yml index 7ac6e011b..eec23b58c 100644 --- a/.github/workflows/suggest_docs.yml +++ b/.github/workflows/suggest_docs.yml @@ -1,25 +1,51 @@ -name: Suggest Documentation Update +name: Suggest Documentation Update (Enhanced) on: - pull_request: - types: [opened, synchronize] + issue_comment: + types: [created] + workflow_dispatch: + inputs: + force_run: + description: 'Force run even if filters would normally skip' + required: false + type: boolean + default: false jobs: suggest-docs: runs-on: ubuntu-latest + if: | + github.event.inputs.force_run == 'true' || + (github.event.issue.pull_request && contains(github.event.comment.body, '[update-docs]')) steps: + - name: Get PR information + id: pr_info + if: github.event.issue.pull_request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.issue.number }} + PR_DATA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER) + echo "head_ref=$(echo "$PR_DATA" | jq -r '.head.ref')" >> $GITHUB_OUTPUT + echo "head_repo=$(echo "$PR_DATA" | jq -r '.head.repo.full_name')" >> $GITHUB_OUTPUT + - name: Checkout PR Code uses: actions/checkout@v3 + with: + repository: ${{ steps.pr_info.outputs.head_repo || github.repository }} + ref: ${{ steps.pr_info.outputs.head_ref || github.ref }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: '3.10' - name: Install Python Dependencies run: | - pip install google-generativeai + pip install -U google-genai - name: Configure Git run: | @@ -28,18 +54,13 @@ jobs: - name: Set up GitHub CLI run: | - sudo apt-get install gh - - - name: Authenticate GitHub CLI - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "${GH_TOKEN}" | gh auth login --with-token + sudo apt-get update + sudo apt-get install -y gh - name: Run Documentation Suggester env: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} DOCS_REPO_URL: ${{ secrets.DOCS_REPO_URL }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT }} run: | python scripts/suggest_docs.py From 473cd44576ca90f7a13265cd74cd8b76457acf46 Mon Sep 17 00:00:00 2001 From: Carmel Soceanu Date: Tue, 19 Aug 2025 13:04:41 +0300 Subject: [PATCH 2/4] Update suggest_docs.py --- scripts/suggest_docs.py | 163 +++++++++++++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 28 deletions(-) diff --git a/scripts/suggest_docs.py b/scripts/suggest_docs.py index dd90daf79..e9919d287 100644 --- a/scripts/suggest_docs.py +++ b/scripts/suggest_docs.py @@ -1,26 +1,68 @@ import os import subprocess +import argparse from pathlib import Path from google import genai from google.genai import types # === CONFIG === -genai.configure(api_key=os.environ["GEMINI_API_KEY"]) -client = genai.Client() - +client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) DOCS_REPO_URL = os.environ["DOCS_REPO_URL"] BRANCH_NAME = "doc-update-from-pr" - def get_diff(): result = subprocess.run(["git", "diff", "origin/main...HEAD"], capture_output=True, text=True) return result.stdout.strip() +def get_commit_info(): + """Get commit information for the documentation PR reference""" + try: + # Get the HEAD commit - this is what GitHub Actions checked out for the PR + current_commit_result = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True) + if current_commit_result.returncode != 0: + return None + commit_hash = current_commit_result.stdout.strip() + + # Get remote origin URL to construct proper commit links + remote_url = subprocess.run(["git", "config", "--get", "remote.origin.url"], capture_output=True, text=True) + if remote_url.returncode != 0: + return None + + # Convert SSH URL to HTTPS if needed + repo_url = remote_url.stdout.strip() + if repo_url.startswith("git@github.com:"): + repo_url = repo_url.replace("git@github.com:", "https://github.com/").replace(".git", "") + elif repo_url.endswith(".git"): + repo_url = repo_url.replace(".git", "") + + # Get commit details + short_hash = commit_hash[:7] + + # Just get the current commit that triggered the pipeline + return { + 'repo_url': repo_url, + 'current_commit': commit_hash, + 'short_hash': short_hash + } + + except Exception as e: + print(f"Warning: Could not get commit info: {e}") + return None def clone_docs_repo(): subprocess.run(["git", "clone", DOCS_REPO_URL, "docs_repo"]) os.chdir("docs_repo") - subprocess.run(["git", "checkout", "-b", BRANCH_NAME]) + + # Try to check out the branch if it already exists + result = subprocess.run(["git", "ls-remote", "--heads", "origin", BRANCH_NAME], capture_output=True, text=True) + if result.stdout.strip(): + print(f"Reusing existing branch: {BRANCH_NAME}") + subprocess.run(["git", "fetch", "origin", BRANCH_NAME]) + subprocess.run(["git", "checkout", BRANCH_NAME]) + subprocess.run(["git", "pull", "origin", BRANCH_NAME]) + else: + print(f"Creating new branch: {BRANCH_NAME}") + subprocess.run(["git", "checkout", "-b", BRANCH_NAME]) def get_file_previews(): @@ -29,13 +71,13 @@ def get_file_previews(): for path in adoc_files: try: with open(path, encoding="utf-8") as f: - first_lines = "".join([next(f) for _ in range(10)]) + lines = f.readlines()[:10] # Get first 10 lines (or fewer if file is short) + first_lines = "".join(lines) previews.append((str(path), first_lines.strip())) except Exception as e: print(f"Skipping file {path}: {e}") return previews - def ask_gemini_for_relevant_files(diff, file_previews): context = "\n\n".join( [f"File: {fname}\nPreview:\n{preview}" for fname, preview in file_previews] @@ -63,7 +105,6 @@ def ask_gemini_for_relevant_files(diff, file_previews): ) return [line.strip() for line in response.text.strip().splitlines() if line.strip()] - def load_full_content(file_path): try: return Path(file_path).read_text(encoding="utf-8") @@ -71,11 +112,19 @@ def load_full_content(file_path): print(f"Failed to read {file_path}: {e}") return "" - def ask_gemini_for_updated_content(diff, file_path, current_content): prompt = f""" You are a documentation assistant. +CRITICAL FORMATTING REQUIREMENTS FOR ASCIIDOC FILES: +- NEVER use markdown code fences like ```adoc or ``` anywhere in the file +- AsciiDoc files start directly with content (comments, headers, or text) +- Use ONLY AsciiDoc syntax: ==== for headers, |=== for tables, ---- for code blocks +- Do NOT mix markdown and AsciiDoc syntax +- Maintain proper table structures with matching |=== opening and closing +- Keep all cross-references (xref) intact and properly formatted +- Ensure consistent indentation and spacing + A developer made the following code changes: {diff} @@ -87,13 +136,21 @@ def ask_gemini_for_updated_content(diff, file_path, current_content): Analyze the diff and check whether **new, important information** is introduced that is not already covered in this file. - If the file already includes everything important, return exactly: `NO_UPDATE_NEEDED` -- If the file is missing key information, return the **full updated file content** in valid .adoc format, with your additions inserted in the correct section. +- If the file is missing key information, return the **full updated file content**, modifying only what is necessary. in valid AsciiDoc format + +VALIDATION CHECKLIST - Before responding, verify: +1. No markdown code fences (```) anywhere in the content +2. All tables have matching |=== opening and closing +3. All section headers use correct ==== syntax +4. All cross-references are properly formatted +5. No broken formatting or incomplete structures Do not explain or summarize — only return either: - `NO_UPDATE_NEEDED` (if nothing is missing), or -- The full updated .adoc file content +- The full updated AsciiDoc file content with perfect syntax (NO markdown!) """ + response = client.models.generate_content( model="gemini-2.5-flash", contents=prompt, @@ -103,8 +160,6 @@ def ask_gemini_for_updated_content(diff, file_path, current_content): ) return response.text.strip() - - def overwrite_file(file_path, new_content): try: Path(file_path).write_text(new_content, encoding="utf-8") @@ -113,29 +168,60 @@ def overwrite_file(file_path, new_content): print(f"Failed to write {file_path}: {e}") return False - -def push_and_open_pr(modified_files): +def push_and_open_pr(modified_files, commit_info=None): subprocess.run(["git", "add"] + modified_files) + + # Build commit message with source commit references + commit_msg = "Auto-generated doc updates from code PR" + + if commit_info: + repo_name = commit_info['repo_url'].split('/')[-1] + commit_url = f"{commit_info['repo_url']}/commit/{commit_info['current_commit']}" + commit_msg += f"\n\nSource commit: {commit_info['short_hash']} from {repo_name}" + commit_msg += f"\nLink: {commit_url}" + + commit_msg += "\n\nAssisted-by: Gemini" + subprocess.run([ - "git", "commit", - "-m", "Auto-generated doc updates from code PR\n\nAssisted-by: Gemini" - ]) subprocess.run(["git", "push", "--set-upstream", "origin", BRANCH_NAME]) + "git", "commit", + "-m", commit_msg + ]) + # Add remote with token auth + gh_token = os.environ["GH_TOKEN"] + docs_repo_url = DOCS_REPO_URL.replace("https://", f"https://{gh_token}@") + + subprocess.run(["git", "remote", "set-url", "origin", docs_repo_url]) + subprocess.run(["git", "push", "--set-upstream", "origin", BRANCH_NAME, "--force"]) + + # Build PR body (simple, without commit references) + pr_body = "This PR updates the following documentation files based on code changes:\n\n" + pr_body += "\n".join([f"- `{f}`" for f in modified_files]) + pr_body += "\n\n*Note: Each commit in this PR contains references to the specific source code commits that triggered the documentation updates.*" + subprocess.run([ "gh", "pr", "create", "--title", "Auto-Generated Doc Updates from Code PR", - "--body", f"This PR updates the following documentation files based on the code changes:\n\n" + - "\n".join([f"- `{f}`" for f in modified_files]), + "--body", pr_body, "--base", "main", "--head", BRANCH_NAME ]) - def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--dry-run", action="store_true", help="Simulate changes without writing files or pushing PR") + args = parser.parse_args() + diff = get_diff() if not diff: print("No changes detected.") return + # Get commit info before switching to docs repo + commit_info = get_commit_info() + if commit_info: + print(f"Source repository: {commit_info['repo_url']}") + print(f"Latest commit: {commit_info['short_hash']}") + clone_docs_repo() previews = get_file_previews() @@ -160,17 +246,38 @@ def main(): print(f"No update needed for {file_path}") continue - print(f"Updating {file_path}...") - if overwrite_file(file_path, updated): - modified_files.append(file_path) + if args.dry_run: + print(f"[Dry Run] Would update {file_path} with:\n{updated}\n") + else: + print(f"Updating {file_path}...") + if overwrite_file(file_path, updated): + modified_files.append(file_path) if modified_files: - push_and_open_pr(modified_files) + if args.dry_run: + print("[Dry Run] Would push and open PR for the following files:") + for f in modified_files: + print(f"- {f}") + + if commit_info: + # Show what the commit message would look like + repo_name = commit_info['repo_url'].split('/')[-1] + commit_url = f"{commit_info['repo_url']}/commit/{commit_info['current_commit']}" + commit_msg = "Auto-generated doc updates from code PR" + commit_msg += f"\n\nSource commit: {commit_info['short_hash']} from {repo_name}" + commit_msg += f"\nLink: {commit_url}" + commit_msg += "\n\nAssisted-by: Gemini" + + print(f"\n[Dry Run] Commit message would be:") + print("=" * 50) + print(commit_msg) + print("=" * 50) + + print(f"\n[Dry Run] PR body would be simple (commit reference is in commit message only)") + else: + push_and_open_pr(modified_files, commit_info) else: print("All documentation is already up to date — no PR created.") - - if __name__ == "__main__": main() - From 651a58aa5fb23fa079488f2d1a6d0215e0b861b9 Mon Sep 17 00:00:00 2001 From: csoceanu Date: Tue, 19 Aug 2025 13:51:13 +0300 Subject: [PATCH 3/4] Add debug logging to workflow Assisted-by: Cursor --- .github/workflows/suggest_docs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/suggest_docs.yml b/.github/workflows/suggest_docs.yml index eec23b58c..ecd72f10a 100644 --- a/.github/workflows/suggest_docs.yml +++ b/.github/workflows/suggest_docs.yml @@ -19,6 +19,13 @@ jobs: (github.event.issue.pull_request && contains(github.event.comment.body, '[update-docs]')) steps: + - name: Debug Event Information + run: | + echo "Event name: ${{ github.event_name }}" + echo "Has PR: ${{ github.event.issue.pull_request != null }}" + echo "Comment body: ${{ github.event.comment.body }}" + echo "Force run: ${{ github.event.inputs.force_run }}" + echo "Repository: ${{ github.repository }}" - name: Get PR information id: pr_info if: github.event.issue.pull_request From f0f2eca26d0c22f353d0b89d75825f7673a65c72 Mon Sep 17 00:00:00 2001 From: Carmel Soceanu Date: Tue, 19 Aug 2025 14:32:48 +0300 Subject: [PATCH 4/4] Update suggest_docs.yml --- .github/workflows/suggest_docs.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/suggest_docs.yml b/.github/workflows/suggest_docs.yml index ecd72f10a..fc8f37c4b 100644 --- a/.github/workflows/suggest_docs.yml +++ b/.github/workflows/suggest_docs.yml @@ -11,6 +11,11 @@ on: type: boolean default: false +permissions: + contents: read + issues: read + pull-requests: read + jobs: suggest-docs: runs-on: ubuntu-latest @@ -19,13 +24,6 @@ jobs: (github.event.issue.pull_request && contains(github.event.comment.body, '[update-docs]')) steps: - - name: Debug Event Information - run: | - echo "Event name: ${{ github.event_name }}" - echo "Has PR: ${{ github.event.issue.pull_request != null }}" - echo "Comment body: ${{ github.event.comment.body }}" - echo "Force run: ${{ github.event.inputs.force_run }}" - echo "Repository: ${{ github.repository }}" - name: Get PR information id: pr_info if: github.event.issue.pull_request