Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a233591abd | |||
| 51df125f6b | |||
| 06dd555020 | |||
| 5beb53cdbb | |||
| 89cfc5e427 | |||
| 58be1b373c | |||
| bfbe7d8dfd | |||
| 61af44bf1f | |||
| 5e4cc71133 | |||
| b0b3decb1b | |||
| 2ff5fd713d | |||
| c20d661613 | |||
| 26204a6b5d |
@@ -0,0 +1,310 @@
|
||||
name: AI Documentation Updates Automation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: "Pull Request number to process"
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
documentation-check:
|
||||
name: Check Documentation Updates with Claude Code
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
CODER_URL: ${{ secrets.TRAIAGE_CODER_URL }}
|
||||
CODER_SESSION_TOKEN: ${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- name: Determine Inputs
|
||||
id: determine-inputs
|
||||
if: always()
|
||||
env:
|
||||
GITHUB_ACTOR: ${{ github.actor }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
GITHUB_EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
GITHUB_EVENT_USER_ID: ${{ github.event.sender.id }}
|
||||
GITHUB_EVENT_USER_LOGIN: ${{ github.event.sender.login }}
|
||||
INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# For workflow_dispatch, use the actor who triggered it
|
||||
# For pull_request events, use the PR author
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
if ! GITHUB_USER_ID=$(gh api "users/${GITHUB_ACTOR}" --jq '.id'); then
|
||||
echo "::error::Failed to get GitHub user ID for actor ${GITHUB_ACTOR}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using workflow_dispatch actor: ${GITHUB_ACTOR} (ID: ${GITHUB_USER_ID})"
|
||||
echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}"
|
||||
echo "github_username=${GITHUB_ACTOR}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
echo "Using PR number: ${INPUTS_PR_NUMBER}"
|
||||
echo "pr_number=${INPUTS_PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
exit 0
|
||||
elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
|
||||
GITHUB_USER_ID=${GITHUB_EVENT_USER_ID}
|
||||
echo "Using PR author: ${GITHUB_EVENT_USER_LOGIN} (ID: ${GITHUB_USER_ID})"
|
||||
echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}"
|
||||
echo "github_username=${GITHUB_EVENT_USER_LOGIN}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
echo "Using PR number: ${GITHUB_EVENT_PR_NUMBER}"
|
||||
echo "pr_number=${GITHUB_EVENT_PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
exit 0
|
||||
else
|
||||
echo "::error::Unsupported event type: ${GITHUB_EVENT_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify push access
|
||||
env:
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_USERNAME: ${{ steps.determine-inputs.outputs.github_username }}
|
||||
GITHUB_USER_ID: ${{ steps.determine-inputs.outputs.github_user_id }}
|
||||
run: |
|
||||
# Query the actor's permission on this repo
|
||||
can_push="$(gh api "/repos/${GITHUB_REPOSITORY}/collaborators/${GITHUB_USERNAME}/permission" --jq '.user.permissions.push')"
|
||||
if [[ "${can_push}" != "true" ]]; then
|
||||
echo "::error title=Access Denied::${GITHUB_USERNAME} does not have push access to ${GITHUB_REPOSITORY}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Post initial comment
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ steps.determine-inputs.outputs.pr_number }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
COMMENT_BODY=$(cat <<EOF
|
||||
🤖 **Documentation Check Started**
|
||||
|
||||
Analyzing PR changes to determine if documentation updates are needed...
|
||||
|
||||
[View workflow run](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID})
|
||||
EOF
|
||||
)
|
||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" --repo "${GITHUB_REPOSITORY}" --create-if-none --edit-last
|
||||
|
||||
- name: Download and install Coder binary
|
||||
shell: bash
|
||||
env:
|
||||
CODER_URL: ${{ secrets.TRAIAGE_CODER_URL }}
|
||||
run: |
|
||||
if [ "${{ runner.arch }}" == "ARM64" ]; then
|
||||
ARCH="arm64"
|
||||
else
|
||||
ARCH="amd64"
|
||||
fi
|
||||
mkdir -p "${HOME}/.local/bin"
|
||||
curl -fsSL --compressed "$CODER_URL/bin/coder-linux-${ARCH}" -o "${HOME}/.local/bin/coder"
|
||||
chmod +x "${HOME}/.local/bin/coder"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
coder version
|
||||
coder whoami
|
||||
echo "$HOME/.local/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: Get Coder username from GitHub actor
|
||||
id: get-coder-username
|
||||
env:
|
||||
CODER_SESSION_TOKEN: ${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_USER_ID: ${{ steps.determine-inputs.outputs.github_user_id }}
|
||||
run: |
|
||||
user_json=$(
|
||||
coder users list --github-user-id="${GITHUB_USER_ID}" --output=json
|
||||
)
|
||||
coder_username=$(jq -r 'first | .username' <<< "$user_json")
|
||||
[[ -z "${coder_username}" || "${coder_username}" == "null" ]] && echo "No Coder user with GitHub user ID ${GITHUB_USER_ID} found" && exit 1
|
||||
echo "coder_username=${coder_username}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create Coder task for documentation analysis
|
||||
id: create-task
|
||||
env:
|
||||
CODER_USERNAME: ${{ steps.get-coder-username.outputs.coder_username }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ steps.determine-inputs.outputs.pr_number }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
TEMPLATE_NAME: "traiage"
|
||||
TEMPLATE_PRESET: "Default"
|
||||
run: |
|
||||
# Fetch PR details using `gh` CLI
|
||||
pr_json=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json 'url')
|
||||
pr_url=$(echo "${pr_json}" | jq -r '.url')
|
||||
|
||||
# Build comprehensive prompt
|
||||
PROMPT=$(cat <<'EOF'
|
||||
Analyze PR ${pr_url} for documentation updates.
|
||||
|
||||
## Task Description
|
||||
You are tasked with analyzing code changes in a pull request and determining what documentation updates are needed. Please:
|
||||
|
||||
1. Use the GitHub CLI or API to fetch the PR details, including:
|
||||
- PR title and description
|
||||
- List of files changed
|
||||
- The actual code diff
|
||||
2. Review the PR description and code changes to understand what was changed
|
||||
3. Examine the existing documentation in the docs/ directory
|
||||
4. Identify any of the following needs:
|
||||
- Updates required to existing documentation
|
||||
- Documentation that needs to be deprecated
|
||||
- New documentation that should be added
|
||||
5. Provide a clear, actionable list of documentation changes needed
|
||||
|
||||
## PR to Analyze
|
||||
${pr_url}
|
||||
|
||||
## IMPORTANT: Final Output Format
|
||||
After completing your analysis, you MUST end your response with ONLY the following formatted output between the markers:
|
||||
|
||||
---DOCUMENTATION-ANALYSIS-START---
|
||||
[Your concise bulleted list of recommendations OR "No documentation changes needed"]
|
||||
---DOCUMENTATION-ANALYSIS-END---
|
||||
|
||||
The content between these markers should be:
|
||||
- A simple bulleted list with links to docs that need updating
|
||||
- OR a single sentence: "No documentation changes needed - [brief reason]"
|
||||
- Maximum 10-15 lines
|
||||
- Use markdown formatting
|
||||
- Include file paths as links when referencing docs
|
||||
|
||||
Example format:
|
||||
---DOCUMENTATION-ANALYSIS-START---
|
||||
### Documentation Updates Needed
|
||||
- **Update** [docs/admin/templates.md](docs/admin/templates.md) - Add new parameter documentation
|
||||
- **Create** docs/guides/new-feature.md - Document the new feature workflow
|
||||
- **Deprecate** docs/old-feature.md - Feature has been removed
|
||||
---DOCUMENTATION-ANALYSIS-END---
|
||||
|
||||
OR:
|
||||
|
||||
---DOCUMENTATION-ANALYSIS-START---
|
||||
No documentation changes needed - This PR only contains internal refactoring with no user-facing changes.
|
||||
---DOCUMENTATION-ANALYSIS-END---
|
||||
EOF
|
||||
)
|
||||
# Expand variables in the prompt
|
||||
PROMPT=$(eval "cat <<EOF
|
||||
${PROMPT}
|
||||
EOF
|
||||
")
|
||||
export PROMPT
|
||||
|
||||
export TASK_NAME="doccheck-pr-${PR_NUMBER}"
|
||||
export CONTEXT_KEY="gh-pr-${PR_NUMBER}"
|
||||
echo "Creating task: ${CODER_USERNAME}/${TASK_NAME}"
|
||||
|
||||
./scripts/documentation-check.sh create
|
||||
|
||||
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_OUTPUT}"
|
||||
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Update comment with task link
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ steps.determine-inputs.outputs.pr_number }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
TASK_NAME: ${{ steps.create-task.outputs.TASK_NAME }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
COMMENT_BODY=$(cat <<EOF
|
||||
🤖 **Documentation Check Started**
|
||||
|
||||
Analyzing PR changes to determine if documentation updates are needed...
|
||||
|
||||
[View workflow run](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID})
|
||||
|
||||
---
|
||||
|
||||
🔄 **Analysis in progress...**
|
||||
|
||||
📋 Task: https://dev.coder.com/tasks/${TASK_NAME}
|
||||
EOF
|
||||
)
|
||||
|
||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" --repo "${GITHUB_REPOSITORY}" --edit-last
|
||||
|
||||
- name: Wait for task completion and get results
|
||||
id: get-results
|
||||
env:
|
||||
TASK_NAME: ${{ steps.create-task.outputs.TASK_NAME }}
|
||||
run: |
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
echo "Waiting for task to complete..."
|
||||
./scripts/documentation-check.sh wait
|
||||
|
||||
echo "Getting task results..."
|
||||
TASK_OUTPUT=$(./scripts/documentation-check.sh summary)
|
||||
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
|
||||
# Convert to minutes and seconds
|
||||
MINUTES=$((DURATION / 60))
|
||||
SECONDS=$((DURATION % 60))
|
||||
|
||||
if [ $MINUTES -gt 0 ]; then
|
||||
DURATION_STR="${MINUTES}m ${SECONDS}s"
|
||||
else
|
||||
DURATION_STR="${SECONDS}s"
|
||||
fi
|
||||
|
||||
# Save output to file for next step
|
||||
echo "${TASK_OUTPUT}" > /tmp/task_output.txt
|
||||
echo "${DURATION_STR}" > /tmp/task_duration.txt
|
||||
|
||||
echo "Task completed successfully in ${DURATION_STR}"
|
||||
|
||||
- name: Update PR comment with results
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ steps.determine-inputs.outputs.pr_number }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
TASK_NAME: ${{ steps.create-task.outputs.TASK_NAME }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
TASK_OUTPUT=$(cat /tmp/task_output.txt)
|
||||
DURATION=$(cat /tmp/task_duration.txt)
|
||||
|
||||
COMMENT_BODY=$(cat <<EOF
|
||||
🤖 **Documentation Check Started**
|
||||
|
||||
Analyzing PR changes to determine if documentation updates are needed...
|
||||
|
||||
[View workflow run](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID})
|
||||
|
||||
---
|
||||
|
||||
${TASK_OUTPUT}
|
||||
|
||||
---
|
||||
⏱️ Execution time: **${DURATION}**
|
||||
📋 Task: https://dev.coder.com/tasks/${TASK_NAME}
|
||||
🔗 [View workflow run](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID})
|
||||
EOF
|
||||
)
|
||||
|
||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" --repo "${GITHUB_REPOSITORY}" --edit-last
|
||||
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
# shellcheck source=scripts/lib.sh
|
||||
source "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
CODER_BIN=${CODER_BIN:-"$(which coder)"}
|
||||
|
||||
TEMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "${TEMPDIR}"' EXIT
|
||||
|
||||
[[ -n ${VERBOSE:-} ]] && set -x
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <options>"
|
||||
echo "Commands:"
|
||||
echo " create - Create a new documentation check task"
|
||||
echo " wait - Wait for task to complete"
|
||||
echo " summary - Get task output summary"
|
||||
echo " delete - Delete the task"
|
||||
exit 1
|
||||
}
|
||||
|
||||
create() {
|
||||
requiredenvs CODER_URL CODER_SESSION_TOKEN CODER_USERNAME TASK_NAME TEMPLATE_NAME TEMPLATE_PRESET PROMPT
|
||||
|
||||
# Check if a task already exists
|
||||
set +e
|
||||
task_json=$("${CODER_BIN}" \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks status "${CODER_USERNAME}/${TASK_NAME}" \
|
||||
--output json 2>/dev/null)
|
||||
set -e
|
||||
|
||||
if [[ "${TASK_NAME}" == $(jq -r '.name' <<<"${task_json}" 2>/dev/null) ]]; then
|
||||
echo "Task \"${CODER_USERNAME}/${TASK_NAME}\" already exists. Sending prompt to existing task."
|
||||
prompt
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"${CODER_BIN}" \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks create \
|
||||
--name "${TASK_NAME}" \
|
||||
--template "${TEMPLATE_NAME}" \
|
||||
--preset "${TEMPLATE_PRESET}" \
|
||||
--org coder \
|
||||
--owner "${CODER_USERNAME}" \
|
||||
--stdin <<<"${PROMPT}"
|
||||
exit 0
|
||||
}
|
||||
|
||||
prompt() {
|
||||
requiredenvs CODER_URL CODER_SESSION_TOKEN TASK_NAME PROMPT
|
||||
|
||||
${CODER_BIN} \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks status "${TASK_NAME}" \
|
||||
--watch >/dev/null
|
||||
|
||||
${CODER_BIN} \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks send "${TASK_NAME}" \
|
||||
--stdin \
|
||||
<<<"${PROMPT}"
|
||||
}
|
||||
|
||||
wait_for_completion() {
|
||||
requiredenvs CODER_URL CODER_SESSION_TOKEN TASK_NAME
|
||||
|
||||
${CODER_BIN} \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks status "${TASK_NAME}" \
|
||||
--watch >/dev/null
|
||||
}
|
||||
|
||||
summary() {
|
||||
requiredenvs CODER_URL CODER_SESSION_TOKEN TASK_NAME
|
||||
|
||||
last_msg_json=$(
|
||||
${CODER_BIN} \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
exp tasks logs "${TASK_NAME}" \
|
||||
--output json
|
||||
)
|
||||
|
||||
# Extract the last output message from the task
|
||||
last_output_msg=$(jq -r 'last(.[] | select(.type=="output")) | .content' <<<"${last_msg_json}")
|
||||
|
||||
# Extract only the content between the documentation analysis markers
|
||||
if echo "${last_output_msg}" | grep -q "---DOCUMENTATION-ANALYSIS-START---"; then
|
||||
# Extract content between markers
|
||||
summary=$(echo "${last_output_msg}" | sed -n '/---DOCUMENTATION-ANALYSIS-START---/,/---DOCUMENTATION-ANALYSIS-END---/p' | sed '1d;$d')
|
||||
echo "${summary}"
|
||||
else
|
||||
# Fallback: if markers not found, return a message
|
||||
echo "⚠️ Unable to extract documentation analysis. Please check the [task logs](https://dev.coder.com/tasks/${TASK_NAME})."
|
||||
fi
|
||||
}
|
||||
|
||||
delete() {
|
||||
requiredenvs CODER_URL CODER_SESSION_TOKEN TASK_NAME
|
||||
|
||||
"${CODER_BIN}" \
|
||||
--url "${CODER_URL}" \
|
||||
--token "${CODER_SESSION_TOKEN}" \
|
||||
delete \
|
||||
"${TASK_NAME}" \
|
||||
--yes
|
||||
exit 0
|
||||
}
|
||||
|
||||
main() {
|
||||
dependencies coder
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
create)
|
||||
create
|
||||
;;
|
||||
wait)
|
||||
wait_for_completion
|
||||
;;
|
||||
summary)
|
||||
summary
|
||||
;;
|
||||
delete)
|
||||
delete
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user