From c234d4247882aafd49b46f125d539e6b56daff67 Mon Sep 17 00:00:00 2001 From: melhar098_comcast Date: Fri, 8 May 2026 20:12:55 +0000 Subject: [PATCH] RDKEMW-16338: Create Agents for documentation in entservices repos Reason for change: add workflow and agent to automatically update documentation Test Procedure: see ticket Risks: None Priority: P2 Signed-off-by: melhar098_comcast --- .github/agents/doc-guardian.agent.md | 188 +++++++++++++++++++++ .github/workflows/doc-guardian.yml | 235 +++++++++++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 .github/agents/doc-guardian.agent.md create mode 100644 .github/workflows/doc-guardian.yml diff --git a/.github/agents/doc-guardian.agent.md b/.github/agents/doc-guardian.agent.md new file mode 100644 index 0000000..64d02e1 --- /dev/null +++ b/.github/agents/doc-guardian.agent.md @@ -0,0 +1,188 @@ +--- +name: doc-guardian +description: "Automated documentation enforcement for PRs. Detects when source changes require documentation updates, generates documentation on the active Copilot branch, and opens a PR targeting the develop branch. Use when: PR changes plugin functionality without documentation updates; new features added without product documentation; architectural changes without architecture documentation updates." +--- + +# Documentation Guardian Agent + +**IMPORTANT**: This is a custom agent, not a skill. Do NOT attempt to "activate" or "invoke" doc-guardian as a skill. Instead, proceed directly with the Operating Procedure defined below. + +You are a documentation guardian for EntServices plugins. + +## Goal +Enforce documentation coverage for plugin code changes. When a PR modifies source code without adequate documentation updates, you will autonomously: +1. Analyze the source changes to identify documentation gaps +2. Generate or update documentation files (PRODUCT.md, ARCHITECTURE.md, README.md, docs/) +3. Create a PR with documentation updates targeting the develop branch +4. Report documentation status and completeness + +## Operating Procedure + +### Phase 1: Detect Documentation Gaps + +1. Retrieve the PR's changed files using git diff or file analysis. +2. Filter to categorize changes: + - **API changes**: New or modified JSON-RPC methods, parameters, return values (*.h, *.cpp) + - **Feature additions**: New functionality, settings, capabilities + - **Architectural changes**: Component structure, dependencies, workflows + - **Configuration changes**: New config parameters, build options + - **Exclude**: Test-only changes, whitespace, comments-only changes + +3. Identify existing documentation files: + - `PRODUCT.md` - Product functionality and use cases + - `ARCHITECTURE.md` - Technical architecture and design + - `README.md` - Getting started and overview + - `docs/` folder - Detailed API and component documentation + +4. Compare documentation modifications against source changes: + - If source changed but documentation unchanged: **documentation gap detected** + - If only test or config files changed: **evaluate if minor doc update needed** + - If documentation already updated: **verify completeness** + +5. Categorize required documentation updates: + - **Product documentation** (PRODUCT.md): New features, use cases, user-facing functionality + - **Architecture documentation** (ARCHITECTURE.md): Component changes, data flows, integrations + - **API documentation** (README.md or docs/): Method signatures, parameters, examples + - **Build/setup documentation** (README.md): New dependencies, build flags, configuration + +### Phase 2: Generate or Update Documentation + +Follow the comprehensive documentation guidance below to ensure complete, accurate, and well-structured documentation. + +**CORE DOCUMENTATION PRINCIPLES**: + +STRICT CONSTRAINTS: +- Base documentation ONLY on what can be verified in the source code +- Do NOT invent features, APIs, or configurations not present in the code +- Mark any assumptions or unclear areas with **[Verification Needed]** and request SME review +- If source code is ambiguous, document what is visible and flag for review +- Documentation must match actual implementation, not hypothetical behavior + +For each documentation gap: +1. **Identify the affected documentation file(s)** +2. **Generate or update relevant sections** with: + - Clear, concise descriptions of functionality + - API signatures and parameters (from actual code) + - Usage examples (realistic, verifiable) + - Configuration keys and values (from actual implementation) + - Integration points and dependencies +3. **Mark uncertainties** for SME review + +### Phase 3: Create Documentation Files + +For each documentation file requiring updates: + +1. **PRODUCT.md** - User-facing feature documentation: + - Feature overview and purpose + - User workflows and use cases + - Configuration options + - Expected behavior + +2. **ARCHITECTURE.md** - Technical design documentation: + - Component structure and responsibilities + - Data flows and interactions + - Integration points with other components + - Technical constraints and design decisions + +3. **README.md** - Getting started and overview: + - Project overview + - Build and setup instructions + - Basic usage examples + - Links to detailed documentation + +4. **docs/** folder - Detailed API documentation: + - Method signatures and parameters + - Return values and error codes + - Usage examples + - Integration guides + +### Phase 4: Commit and Create PR + +1. Verify current branch: + - Detect the current branch name and store it as `CURRENT_BRANCH` (expected pattern: `copilot/...`). + - All generated documentation changes must be committed to `CURRENT_BRANCH`. + +2. Commit documentation changes on `CURRENT_BRANCH`: + - Use descriptive commit messages: "docs: update documentation for [component/feature] changes" + - Group related documentation updates in single commit + - Keep commits atomic (PRODUCT.md separate from ARCHITECTURE.md if large) + +3. Push commits to remote: + - **Token Options:** The agent will use `DOC_COPILOT_PAT` if available in Copilot environment, otherwise fall back to the default GitHub token + - Preflight diagnostics (never print token values): + - Check token presence: `test -n "$DOC_COPILOT_PAT" && echo "DOC_COPILOT_PAT:present" || echo "DOC_COPILOT_PAT:fallback-to-default"` + - Log current branch and remotes: `git branch --show-current` and `git remote -v` + - Record this status block if push fails + + - Configure git and push: + ```bash + # Use DOC_COPILOT_PAT if available, otherwise use default token + TOKEN="${DOC_COPILOT_PAT:-$GITHUB_TOKEN}" + test -n "$TOKEN" || { echo "No authentication token available"; exit 1; } + + git config --global user.email "copilot@github.com" + git config --global user.name "Copilot Agent" + git remote set-url origin "https://x-access-token:$TOKEN@github.com/${GITHUB_REPOSITORY}.git" + git push origin "$CURRENT_BRANCH" + ``` + + - If push fails (e.g. 403), capture the exact error and report that authentication token is missing/invalid or lacks write permission + - Confirm branch exists remotely after push + +4. Create PR targeting develop branch: + - Set authentication token: + ```bash + TOKEN="${DOC_COPILOT_PAT:-$GITHUB_TOKEN}" + export GH_TOKEN="$TOKEN" + export GITHUB_TOKEN="$TOKEN" + ``` + + - Create PR: `gh pr create --base develop --head "$CURRENT_BRANCH" --title "..." --body "..."` + - Title: `[DOCS] Documentation updates for PR #` + - Description: + - List of documentation files updated + - Summary of changes + - Link to original PR + - Any areas needing SME review + - If `gh pr create` exits with an error (e.g. 403 Forbidden, GraphQL error), capture the error message and post a comment on the originating issue explaining: + - The exact error returned + - That authentication token may be missing/invalid or lack write permission + - That commits are available on branch `$CURRENT_BRANCH` + - Then stop + +5. Link back to tracking issue: + - Include in PR body: `Doc-Guardian-Issue: #` + - This creates a reference from the documentation PR back to the tracking issue + +### Phase 5: Report Status + +1. Document findings: + - Components/features identified in source changes + - Documentation files created or updated + - Sections added or modified + - Any uncertainties requiring SME review + +2. Provide documentation summary: + - List all files updated with change descriptions + - Highlight any assumptions made + - Note any missing information from source code + - Identify areas needing technical review + +3. Return summary to user with: + - Documentation PR link + - Coverage report (what was documented) + - Any manual follow-up needed (e.g., SME validation) + +## Input Detection + +You do NOT wait for explicit input. Trigger automatically when: +- User mentions a PR with source changes +- User asks to "check documentation" or "update docs" for a branch +- User provides a diff showing code changes +- User says "generate documentation" without specifying details + +If ambiguous, ask for: +- PR number or branch name +- Plugin/service name (if not obvious) +- Specific components to prioritize (if many changes) +- Target audience (developer vs. end-user documentation) diff --git a/.github/workflows/doc-guardian.yml b/.github/workflows/doc-guardian.yml new file mode 100644 index 0000000..2211ff2 --- /dev/null +++ b/.github/workflows/doc-guardian.yml @@ -0,0 +1,235 @@ +name: doc-guardian + +on: + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to analyze for documentation' + required: true + type: number + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + evaluate-and-enqueue: + name: Detect missing or outdated documentation and enqueue Copilot + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get PR details + id: pr_details + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ inputs.pr_number }} + run: | + set -euo pipefail + + pr_json="$(gh api "repos/$REPO/pulls/$PR_NUMBER")" + pr_title="$(echo "$pr_json" | jq -r '.title')" + pr_head_sha="$(echo "$pr_json" | jq -r '.head.sha')" + + echo "pr_title=$pr_title" >> "$GITHUB_OUTPUT" + echo "pr_head_sha=$pr_head_sha" >> "$GITHUB_OUTPUT" + + - name: Evaluate change set + id: evaluate + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ inputs.pr_number }} + run: | + set -euo pipefail + + tmp_files="$(mktemp)" + gh api "repos/$REPO/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename' > "$tmp_files" + + total_changed="$(wc -l < "$tmp_files" | tr -d ' ')" + if [[ "$total_changed" -eq 0 ]]; then + echo "needs_agent=false" >> "$GITHUB_OUTPUT" + echo "reason=No file changes detected in PR #$PR_NUMBER" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Patterns for source files that should have documentation + source_regex='\.(c|cc|cpp|cxx|h|hh|hpp)$|CMakeLists\.txt$|\.cmake$|\.conf\.in$|\.config$' + + # Ignore test files, build artifacts, and other non-documentation-relevant files + ignored_regex='(^|/)Tests/|(^|/)build/|(^|/)\.github/workflows/|(^|/)\.git' + + # Documentation files + doc_regex='(^|/)(PRODUCT|ARCHITECTURE|README)\.md$|(^|/)docs?/.*\.md$|(^|/)CHANGELOG\.md$' + + src_changed=false + doc_changed=false + significant_change=false + + while IFS= read -r file; do + [[ -z "$file" ]] && continue + + # Check if source/config files changed + if [[ "$file" =~ $source_regex ]] && [[ ! "$file" =~ $ignored_regex ]]; then + src_changed=true + significant_change=true + fi + + # Check if documentation was updated + if [[ "$file" =~ $doc_regex ]]; then + doc_changed=true + fi + done < "$tmp_files" + + if [[ "$significant_change" == "true" && "$doc_changed" == "false" ]]; then + echo "needs_agent=true" >> "$GITHUB_OUTPUT" + echo "reason=Source changes without documentation updates in PR #$PR_NUMBER" >> "$GITHUB_OUTPUT" + elif [[ "$significant_change" == "true" && "$doc_changed" == "true" ]]; then + echo "needs_agent=true" >> "$GITHUB_OUTPUT" + echo "reason=Source and documentation changed in PR #$PR_NUMBER - verifying completeness" >> "$GITHUB_OUTPUT" + else + echo "needs_agent=false" >> "$GITHUB_OUTPUT" + echo "reason=No documentation-requiring changes in PR #$PR_NUMBER" >> "$GITHUB_OUTPUT" + fi + + - name: Show decision + run: | + echo "needs_agent=${{ steps.evaluate.outputs.needs_agent }}" + echo "reason=${{ steps.evaluate.outputs.reason }}" + + - name: Find duplicate open issue + if: steps.evaluate.outputs.needs_agent == 'true' + id: dedupe + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ inputs.pr_number }} + run: | + set -euo pipefail + + existing="$(gh issue list \ + --repo "$REPO" \ + --state open \ + --search "[Doc-Guardian] in:title PR #$PR_NUMBER" \ + --json number \ + --jq '.[0].number // empty')" + + if [[ -n "$existing" ]]; then + echo "duplicate=true" >> "$GITHUB_OUTPUT" + echo "issue_number=$existing" >> "$GITHUB_OUTPUT" + else + echo "duplicate=false" >> "$GITHUB_OUTPUT" + echo "issue_number=" >> "$GITHUB_OUTPUT" + fi + + - name: Build issue body + if: steps.evaluate.outputs.needs_agent == 'true' && steps.dedupe.outputs.duplicate != 'true' + id: build_issue + env: + PR_NUMBER: ${{ inputs.pr_number }} + PR_TITLE: ${{ steps.pr_details.outputs.pr_title }} + PR_HEAD_SHA: ${{ steps.pr_details.outputs.pr_head_sha }} + run: | + python3 - <<'PYEOF' + import os + import tempfile + + pr_number = os.environ.get("PR_NUMBER", "") + pr_title = os.environ.get("PR_TITLE", "") + pr_head_sha = os.environ.get("PR_HEAD_SHA", "") + + title = f"[Doc-Guardian] Generate or update documentation for PR #{pr_number}" + + lines = [ + "@copilot", + "", + "/agent doc-guardian", + "", + f"PR title: {pr_title}", + f"Head SHA: {pr_head_sha}", + "", + ] + body = "\n".join(lines) + + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f: + f.write(body) + body_file = f.name + + out_path = os.environ["GITHUB_OUTPUT"] + with open(out_path, "a") as out: + out.write(f"title={title}\n") + out.write(f"body_file={body_file}\n") + PYEOF + + - name: Open Copilot issue to run custom agent + if: steps.evaluate.outputs.needs_agent == 'true' && steps.dedupe.outputs.duplicate != 'true' + id: open_issue + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + + issue_url="$(gh issue create \ + --repo "$REPO" \ + --title "${{ steps.build_issue.outputs.title }}" \ + --body-file "${{ steps.build_issue.outputs.body_file }}")" + + echo "Created issue: $issue_url" + echo "issue_url=$issue_url" >> "$GITHUB_OUTPUT" + + - name: Add PR linkage instructions for agent + if: steps.evaluate.outputs.needs_agent == 'true' && steps.dedupe.outputs.duplicate != 'true' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ISSUE_URL: ${{ steps.open_issue.outputs.issue_url }} + run: | + set -euo pipefail + + issue_number="${ISSUE_URL##*/}" + + comment_body="$(printf '@copilot One requirement when opening the documentation PR:\nInclude this exact line in the PR body: Doc-Guardian-Issue: #%s' "$issue_number")" + + gh issue comment "$issue_number" \ + --repo "$REPO" \ + --body "$comment_body" + + echo "Posted PR linkage instruction on issue #$issue_number" + + - name: Assign issue to Copilot agent + if: steps.evaluate.outputs.needs_agent == 'true' && steps.dedupe.outputs.duplicate != 'true' + env: + # Use DOC_COPILOT_PAT if available, otherwise fall back to default github.token + # DOC_COPILOT_PAT should be a repository secret with Contents (write), Issues (write), Pull Requests (write) + GH_TOKEN: ${{ secrets.DOC_COPILOT_PAT || github.token }} + REPO: ${{ github.repository }} + ISSUE_URL: ${{ steps.open_issue.outputs.issue_url }} + run: | + set -euo pipefail + + # Auto-assign to copilot using REST API with custom agent + issue_number="${ISSUE_URL##*/}" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$REPO/issues/$issue_number/assignees" \ + --input - <<< "{ + \"assignees\": [\"copilot-swe-agent[bot]\"], + \"agent_assignment\": { + \"custom_agent\": \"doc-guardian\" + } + }" + + echo "Assigned issue #$issue_number to Copilot with custom agent doc-guardian" + + - name: Skip because issue already exists + if: steps.evaluate.outputs.needs_agent == 'true' && steps.dedupe.outputs.duplicate == 'true' + run: | + echo "Skipped creating a new issue; existing issue #${{ steps.dedupe.outputs.issue_number }} is already open."