Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions .github/workflows/agent-source-fix-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
name: Source Fix Dispatch
run-name: >-
Source Fix ${{ github.event.client_payload.target_repository }}#${{
github.event.client_payload.pr_number }}@${{ github.event.client_payload.pr_head_sha }}

on:
repository_dispatch:
types: [agent-source-fix]

concurrency:
group: >-
source-fix-${{ github.event.client_payload.target_repository }}-${{
github.event.client_payload.pr_number || github.run_id }}
cancel-in-progress: false

permissions:
actions: read
contents: read
id-token: write

jobs:
source-fix:
if: github.repository == 'ContextualWisdomLab/.github'
runs-on: ubuntu-24.04
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || '' }}
PR_NUMBER: ${{ github.event.client_payload.pr_number || '' }}
PR_HEAD_SHA: ${{ github.event.client_payload.pr_head_sha || '' }}
PR_HEAD_REF: ${{ github.event.client_payload.pr_head_ref || '' }}
PR_BASE_SHA: ${{ github.event.client_payload.pr_base_sha || '' }}
PR_BASE_REF: ${{ github.event.client_payload.pr_base_ref || '' }}
REQUESTED_BY: ${{ github.event.client_payload.requested_by || '' }}
SOURCE_COMMENT_ID: ${{ github.event.client_payload.source_comment_id || '' }}
INSTRUCTION_SHA256: ${{ github.event.client_payload.instruction_sha256 || '' }}
INVOCATION_KEY: ${{ github.event.client_payload.invocation_key || '' }}
steps:
- name: Harden runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
with:
egress-policy: audit

- name: Check out trusted source-fix implementation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ContextualWisdomLab/.github
ref: ${{ github.sha }}
fetch-depth: 1
persist-credentials: false
path: trusted-source-fix

- name: Validate immutable invocation claim
run: python3 trusted-source-fix/scripts/ci/agent_source_fix_worker.py --validate-only

- name: Inspect exact invocation ledger
id: ledger
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ledger_name="cwl-source-fix-invocation-${INVOCATION_KEY}"
response="${RUNNER_TEMP}/source-fix-artifacts.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts" \
-X GET -f "name=${ledger_name}" -f per_page=100 >"$response"
python3 - "$response" "$ledger_name" <<'PY'
import json
import os
import sys
from pathlib import Path

response_path = Path(sys.argv[1])
expected_name = sys.argv[2]
payload = json.loads(response_path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise SystemExit("artifact response must be an object")
total_count = payload.get("total_count")
artifacts = payload.get("artifacts")
if type(total_count) is not int or total_count < 0 or not isinstance(artifacts, list):
raise SystemExit("artifact response is malformed")
if total_count != len(artifacts):
raise SystemExit("artifact response is truncated or inconsistent")
live = False
for artifact in artifacts:
if not isinstance(artifact, dict):
raise SystemExit("artifact response contains a non-object record")
if artifact.get("name") != expected_name or type(artifact.get("expired")) is not bool:
raise SystemExit("artifact response contains a mismatched record")
live = live or not artifact["expired"]
output = Path(os.environ["GITHUB_OUTPUT"])
with output.open("a", encoding="utf-8") as handle:
handle.write(f"claim={'false' if live else 'true'}\n")
if not live:
claim_dir = Path(os.environ["RUNNER_TEMP"]) / "source-fix-claim"
claim_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
claim = {
"repository": os.environ["TARGET_REPOSITORY"],
"pr_number": int(os.environ["PR_NUMBER"]),
"head_sha": os.environ["PR_HEAD_SHA"],
"base_sha": os.environ["PR_BASE_SHA"],
"requested_by": os.environ["REQUESTED_BY"],
"source_comment_id": int(os.environ["SOURCE_COMMENT_ID"]),
"instruction_sha256": os.environ["INSTRUCTION_SHA256"],
"invocation_key": os.environ["INVOCATION_KEY"],
}
(claim_dir / "claim.json").write_text(
json.dumps(claim, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
PY

- name: Claim invocation in durable artifact ledger
if: steps.ledger.outputs.claim == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cwl-source-fix-invocation-${{ env.INVOCATION_KEY }}
path: ${{ runner.temp }}/source-fix-claim/claim.json
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false

- name: Exchange OpenCode app token for target repository writes
if: steps.ledger.outputs.claim == 'true'
id: target_app_token
env:
OIDC_AUDIENCE: opencode-github-action
OPENCODE_API_BASE_URL: https://api.opencode.ai
run: |
set -euo pipefail
mark_unavailable() { echo "available=false" >>"$GITHUB_OUTPUT"; }
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
mark_unavailable
exit 0
fi
request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}"
separator="&"; case "$request_url" in *\?*) ;; *) separator="?" ;; esac
if ! oidc_response="$(curl -fsS --connect-timeout 10 --max-time 30 \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${request_url}${separator}audience=${OIDC_AUDIENCE}")"; then
mark_unavailable
exit 0
fi
oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")"
if [ -z "$oidc_token" ]; then mark_unavailable; exit 0; fi
if ! token_response="$(curl -fsS --connect-timeout 10 --max-time 30 -X POST \
-H "Authorization: Bearer ${oidc_token}" \
"${OPENCODE_API_BASE_URL}/exchange_github_app_token")"; then
mark_unavailable
exit 0
fi
app_token="$(jq -r '.token // empty' <<<"$token_response")"
if [ -z "$app_token" ]; then mark_unavailable; exit 0; fi
echo "::add-mask::$app_token"
echo "available=true" >>"$GITHUB_OUTPUT"
echo "token=$app_token" >>"$GITHUB_OUTPUT"

- name: Require a mutation credential
if: steps.ledger.outputs.claim == 'true'
env:
USER_TOKEN_AVAILABLE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '' }}
APP_TOKEN_AVAILABLE: ${{ steps.target_app_token.outputs.available == 'true' }}
run: |
set -euo pipefail
if [ "$USER_TOKEN_AVAILABLE" != "true" ] && [ "$APP_TOKEN_AVAILABLE" != "true" ]; then
echo "::error::Source-fix requires PR_REVIEW_MERGE_TOKEN, OPENCODE_APPROVE_TOKEN, or an exchanged OpenCode app token."
exit 1
fi

- name: Install OpenCode CLI
if: steps.ledger.outputs.claim == 'true'
env:
OPENCODE_VERSION: "1.17.13"
OPENCODE_SHA256: 157afa289d1a8d9372de0ce19ac726119b937a1f6b201808d46f06e4e59bb348
run: |
set -euo pipefail
archive="${RUNNER_TEMP}/opencode-linux-x64.tar.gz"
install_dir="${HOME}/.opencode/bin"
mkdir -p "$install_dir"
curl -fsSL -o "$archive" \
"https://github.com/anomalyco/opencode/releases/download/v${OPENCODE_VERSION}/opencode-linux-x64.tar.gz"
printf '%s %s\n' "$OPENCODE_SHA256" "$archive" | sha256sum -c -
tar -xzf "$archive" -C "$RUNNER_TEMP"
install -m 0755 "${RUNNER_TEMP}/opencode" "${install_dir}/opencode"
echo "$install_dir" >>"$GITHUB_PATH"

- name: Provision contextual-orchestrator sidecar
if: steps.ledger.outputs.claim == 'true'
env:
BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }}
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
set -euo pipefail
bash "$GITHUB_WORKSPACE/trusted-source-fix/scripts/ci/contextual_orchestrator_review_sidecar.sh"

- name: Execute bounded source repair
if: steps.ledger.outputs.claim == 'true'
env:
GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token }}
run: |
set -euo pipefail
source "$GITHUB_WORKSPACE/trusted-source-fix/scripts/ci/load_contextual_orchestrator_token.sh"
python3 "$GITHUB_WORKSPACE/trusted-source-fix/scripts/ci/agent_source_fix_worker.py"
154 changes: 154 additions & 0 deletions .github/workflows/agent-source-fix-router.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: Source Fix Comment Router

on:
issue_comment:
types: [created]
schedule:
- cron: "*/5 * * * *"

permissions:
contents: read

jobs:
route-local-source-fix:
if: >-
github.repository == 'ContextualWisdomLab/.github'
&& github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& github.event.comment.user.type != 'Bot'
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
&& contains(github.event.comment.body, '@cwl-source-fix')
concurrency:
group: source-fix-router-local-${{ github.repository }}-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: true
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: read
contents: write
issues: write
pull-requests: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GH_TOKEN: ${{ github.token }}
TARGET_REPOSITORY_TOKEN: ${{ github.token }}
AGENT_DISPATCH_TOKEN: ${{ github.token }}
SOURCE_FIX_REPOSITORY_TARGETS: ${{ vars.SOURCE_FIX_REPOSITORY_TARGETS || vars.OPENCODE_REPOSITORY_DISPATCH_TARGETS }}
OPENCODE_REPOSITORY_DISPATCH_TARGETS: ${{ vars.OPENCODE_REPOSITORY_DISPATCH_TARGETS }}
steps:
- name: Check out trusted default-branch router
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false

- name: Resolve immutable pull-request head
env:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
SOURCE_EVENT_PATH: ${{ github.event_path }}
run: |
set -euo pipefail
pr_json="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")"
jq --argjson pull_request "$pr_json" '. + {pull_request: $pull_request}' \
"$SOURCE_EVENT_PATH" >"${RUNNER_TEMP}/source-fix-event.json"

- name: Route trusted local source-fix command
run: >-
python3 -u scripts/ci/agent_source_fix_router.py
--event-path "${RUNNER_TEMP}/source-fix-event.json"

sweep-organization-source-fixes:
if: >-
github.repository == 'ContextualWisdomLab/.github'
&& github.event_name == 'schedule'
concurrency:
group: source-fix-router-sweep-${{ github.repository }}
cancel-in-progress: false
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
actions: read
contents: write
id-token: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
SOURCE_FIX_REPOSITORY_TARGETS: ${{ vars.SOURCE_FIX_REPOSITORY_TARGETS || vars.OPENCODE_REPOSITORY_DISPATCH_TARGETS }}
OPENCODE_REPOSITORY_DISPATCH_TARGETS: ${{ vars.OPENCODE_REPOSITORY_DISPATCH_TARGETS }}
LOOKBACK_HOURS: ${{ vars.AGENT_MENTION_LOOKBACK_HOURS || '168' }}
MAX_DISPATCHES: ${{ vars.AGENT_MENTION_MAX_DISPATCHES || '20' }}
TIME_BUDGET_SECONDS: ${{ vars.AGENT_MENTION_TIME_BUDGET_SECONDS || '480' }}
steps:
- name: Exchange OpenCode app token for sibling-repository access
id: source_fix_app_token
env:
OIDC_AUDIENCE: opencode-github-action
OPENCODE_API_BASE_URL: https://api.opencode.ai
USER_TOKEN_CONFIGURED: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '' }}
run: |
set -euo pipefail
mark_unavailable() { echo "available=false" >>"$GITHUB_OUTPUT"; }
if [ "$USER_TOKEN_CONFIGURED" = "true" ]; then
mark_unavailable
exit 0
fi
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
mark_unavailable
exit 0
fi
request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}"
separator="&"; case "$request_url" in *\?*) ;; *) separator="?" ;; esac
if ! oidc_response="$(curl -fsS --connect-timeout 10 --max-time 30 \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${request_url}${separator}audience=${OIDC_AUDIENCE}")"; then
mark_unavailable
exit 0
fi
oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")"
if [ -z "$oidc_token" ]; then mark_unavailable; exit 0; fi
if ! token_response="$(curl -fsS --connect-timeout 10 --max-time 30 -X POST \
-H "Authorization: Bearer ${oidc_token}" \
"${OPENCODE_API_BASE_URL}/exchange_github_app_token")"; then
mark_unavailable
exit 0
fi
app_token="$(jq -r '.token // empty' <<<"$token_response")"
if [ -z "$app_token" ]; then mark_unavailable; exit 0; fi
echo "::add-mask::$app_token"
echo "available=true" >>"$GITHUB_OUTPUT"
echo "SOURCE_FIX_APP_TOKEN=$app_token" >>"$GITHUB_ENV"

- name: Check out trusted source-fix router
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false

- name: Sweep recent organization source-fix commands
env:
PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}
OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }}
AGENT_DISPATCH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ -n "$PR_REVIEW_MERGE_TOKEN" ]; then
TARGET_REPOSITORY_TOKEN="$PR_REVIEW_MERGE_TOKEN"
repository_source="organization"
elif [ -n "$OPENCODE_APPROVE_TOKEN" ]; then
TARGET_REPOSITORY_TOKEN="$OPENCODE_APPROVE_TOKEN"
repository_source="organization"
else
TARGET_REPOSITORY_TOKEN="${SOURCE_FIX_APP_TOKEN:-}"
repository_source="installation"
fi
if [ -z "$TARGET_REPOSITORY_TOKEN" ]; then
echo "::error::Source-fix sweep requires an organization token or OpenCode app token."
exit 1
fi
export TARGET_REPOSITORY_TOKEN
python3 -u scripts/ci/agent_source_fix_sweep.py \
--organization ContextualWisdomLab \
--repository-source "$repository_source" \
--lookback-hours "$LOOKBACK_HOURS" \
--max-dispatches "$MAX_DISPATCHES" \
--time-budget-seconds "$TIME_BUDGET_SECONDS"
Loading
Loading