Skip to content
Merged
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
260 changes: 228 additions & 32 deletions .github/workflows/opencode-review.yml

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: PR Review Merge Scheduler

on:
schedule:
- cron: "17 */2 * * *"
workflow_dispatch:
inputs:
dry_run:
description: Print planned actions without mutating PRs
required: false
default: false
type: boolean
max_prs:
description: Maximum open PRs to inspect
required: false
default: "100"
trigger_reviews:
description: Dispatch OpenCode Review for PR heads without current approval
required: false
default: true
type: boolean
enable_auto_merge:
description: Enable auto-merge for current-head approved PRs
required: false
default: true
type: boolean
update_branches:
description: Update outdated PR branches after OpenCode approval
required: false
default: true
type: boolean

concurrency:
group: pr-review-merge-scheduler
cancel-in-progress: false

jobs:
scan-pr-queue:
runs-on: ubuntu-latest
permissions:
actions: write
checks: read
contents: write
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == true }}
MAX_PRS: ${{ inputs.max_prs || '100' }}
PROJECT_FLOW: ${{ vars.PROJECT_FLOW }}
TRIGGER_REVIEWS: ${{ github.event_name != 'workflow_dispatch' || inputs.trigger_reviews == true }}
ENABLE_AUTO_MERGE: ${{ github.event_name != 'workflow_dispatch' || inputs.enable_auto_merge == true }}
UPDATE_BRANCHES: ${{ github.event_name != 'workflow_dispatch' || inputs.update_branches == true }}
steps:
- name: Download central scheduler
env:
CENTRAL_REPOSITORY: ContextualWisdomLab/.github
CENTRAL_REF: 7be2d9949831d94cfb96cd7eaadae9513e7d325d
CENTRAL_SCHEDULER_SHA256: f954b62efa4ad60964a65501d777cb4ba26f1ac5746c2d11406d610a5ab695f6
run: |
set -euo pipefail
scheduler_dir="$RUNNER_TEMP/pr-governance"
mkdir -p "$scheduler_dir"
gh api "repos/${CENTRAL_REPOSITORY}/contents/scripts/ci/pr_review_merge_scheduler.py?ref=${CENTRAL_REF}" \
--jq .content | base64 -d > "$scheduler_dir/pr_review_merge_scheduler.py"
actual_sha256="$(sha256sum "$scheduler_dir/pr_review_merge_scheduler.py" | awk '{print $1}')"
if [ "$actual_sha256" != "$CENTRAL_SCHEDULER_SHA256" ]; then
echo "::error::Central scheduler checksum mismatch: expected $CENTRAL_SCHEDULER_SHA256, got $actual_sha256"
exit 1
fi
python3 "$scheduler_dir/pr_review_merge_scheduler.py" --self-test

- name: Inspect PR review and merge queue
run: |
set -euo pipefail
project_flow="$PROJECT_FLOW"
if [ -z "$project_flow" ]; then
project_flow=github-flow
if [ "$DEFAULT_BRANCH" = develop ]; then
project_flow=git-flow
fi
fi
args=(
--repo "$GITHUB_REPOSITORY"
--base-branch "$DEFAULT_BRANCH"
--max-prs "$MAX_PRS"
--project-flow "$project_flow"
--review-workflow "OpenCode Review"
)
if [ "$DRY_RUN" = true ]; then args+=(--dry-run); fi
if [ "$TRIGGER_REVIEWS" = true ]; then args+=(--trigger-reviews); else args+=(--no-trigger-reviews); fi
if [ "$ENABLE_AUTO_MERGE" = true ]; then args+=(--enable-auto-merge); else args+=(--no-enable-auto-merge); fi
if [ "$UPDATE_BRANCHES" = true ]; then args+=(--update-branches); else args+=(--no-update-branches); fi
python3 "$RUNNER_TEMP/pr-governance/pr_review_merge_scheduler.py" "${args[@]}"
88 changes: 83 additions & 5 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ jobs:
# refs/pull/<n>/head has already advanced before this queued run starts.
if git -C "$TRUSTED_WORKSPACE" fetch --no-tags --depth=1 origin "$PR_HEAD_SHA"; then
git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA^{commit}"
if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then
git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc"
fi
if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then
git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py"
fi
git -C "$TRUSTED_WORKSPACE" update-ref "refs/remotes/pull/${PR_NUMBER}/head" "$PR_HEAD_SHA"
exit 0
fi
Expand All @@ -118,6 +124,12 @@ jobs:
fetched_head_sha="$(git -C "$TRUSTED_WORKSPACE" rev-parse "refs/remotes/pull/${PR_NUMBER}/head")"
if [ "$fetched_head_sha" = "$PR_HEAD_SHA" ]; then
git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA^{commit}"
if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then
git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc"
fi
if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then
git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py"
fi
exit 0
fi
if [ "$pr_head_fetch_attempt" -lt 6 ]; then
Expand All @@ -130,7 +142,9 @@ jobs:

- name: Self-test Strix gate script
working-directory: ${{ runner.temp }}/trusted-workspace
run: bash "$TRUSTED_STRIX_GATE_TEST"
env:
STRIX_GATE_SELF_TEST_MODE: smoke
run: timeout 180 bash "$TRUSTED_STRIX_GATE_TEST"

- name: Gate Strix secrets
id: gate
Expand Down Expand Up @@ -360,8 +374,8 @@ jobs:
STRIX_SOURCE_DIRS: ". backend frontend"
STRIX_REASONING_EFFORT: low
STRIX_LLM_MAX_RETRIES: 1
STRIX_TRANSIENT_RETRY_PER_MODEL: 5
STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 60
STRIX_TRANSIENT_RETRY_PER_MODEL: 1
STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 15
STRIX_FALLBACK_MODELS: ${{ steps.gate.outputs.provider_mode == 'github_models' && 'github_models/deepseek/deepseek-r1-0528 github_models/deepseek/deepseek-v3-0324' || '' }}
STRIX_FAIL_ON_PROVIDER_SIGNAL: "1"
STRIX_VERTEX_FALLBACK_MODELS: ""
Expand All @@ -378,13 +392,36 @@ jobs:
IS_PR_EVIDENCE_RUN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && 'true' || 'false' }}
run: |
budget_suffix="TIME""OUT"
process_budget_seconds="3600"
process_budget_seconds="900"
export "LLM_${budget_suffix}=120"
export "STRIX_MEMORY_COMPRESSOR_${budget_suffix}=10"
export "STRIX_PROCESS_${budget_suffix}_SECONDS=$process_budget_seconds"
export "STRIX_TOTAL_${budget_suffix}_SECONDS=7200"
export "STRIX_TOTAL_${budget_suffix}_SECONDS=1800"
bash "$TRUSTED_STRIX_GATE"

- name: Remove Strix secret temp files
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
run: |
set -euo pipefail
for secret_file in \
"${LLM_API_KEY_FILE:-}" \
"${LLM_API_BASE_FILE:-}" \
"${STRIX_LLM_FILE:-}" \
"${GOOGLE_APPLICATION_CREDENTIALS:-}" \
"${CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE:-}"; do
if [ -z "$secret_file" ]; then
continue
fi
case "$secret_file" in
"$RUNNER_TEMP"/*)
rm -f -- "$secret_file"
;;
*)
echo "::warning::Refusing to remove temp secret path outside RUNNER_TEMP."
;;
esac
done

- name: Collect Strix reports for artifact upload
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
env:
Expand Down Expand Up @@ -419,3 +456,44 @@ jobs:
path: strix_runs/
if-no-files-found: error
retention-days: 5

publish-manual-pr-evidence-status:
name: publish-manual-pr-evidence-status
needs: strix
if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr_head_sha != '' }}
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- name: Publish same-head manual Strix status
env:
GH_TOKEN: ${{ github.token }}
PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }}
STRIX_RESULT: ${{ needs.strix.result }}
run: |
set -euo pipefail
if ! [[ "$PR_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::PR head SHA must be a 40-character git SHA."
exit 1
fi

case "$STRIX_RESULT" in
success)
state="success"
description="Manual workflow_dispatch Strix evidence passed"
;;
failure|cancelled|skipped)
state="failure"
description="Manual workflow_dispatch Strix evidence failed"
;;
*)
state="error"
description="Manual workflow_dispatch Strix evidence inconclusive"
;;
esac

gh api -X POST "repos/${GITHUB_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
148 changes: 148 additions & 0 deletions opencode.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"$schema": "https://opencode.ai/config.json",
"model": "github-models/openai/gpt-5",
"small_model": "github-models/deepseek/deepseek-v3-0324",
"enabled_providers": ["github-models"],
"lsp": true,
"mcp": {
"codegraph": {
"type": "local",
"command": ["npx", "-y", "@colbymchenry/codegraph@0.9.9", "serve", "--mcp"],
"enabled": true
},
"deepwiki": {
"type": "remote",
"url": "https://mcp.deepwiki.com/mcp",
"enabled": true,
"timeout": 10000
},
"context7": {
"type": "local",
"command": ["npx", "-y", "@upstash/context7-mcp@3.1.0", "--transport", "stdio"],
"enabled": true,
"timeout": 10000,
"environment": {
"NPM_CONFIG_IGNORE_SCRIPTS": "true",
"NPM_CONFIG_LOGLEVEL": "error"
}
},
"web_search": {
"type": "local",
"command": ["npx", "-y", "@guhcostan/web-search-mcp@1.0.5"],
"enabled": true,
"timeout": 10000,
"environment": {
"NPM_CONFIG_IGNORE_SCRIPTS": "true",
"NPM_CONFIG_LOGLEVEL": "error"
}
}
},
"permission": {
"edit": "deny",
"bash": "allow",
"read": "allow",
"grep": "allow",
"glob": "allow",
"list": "allow",
"task": "allow",
"webfetch": "allow",
"websearch": "allow",
"lsp": "allow",
"external_directory": "allow"
},
"agent": {
"ci-review": {
"description": "Compact read-only CI pull request reviewer",
"mode": "primary",
"prompt": "{file:./ci-review-prompt.md}",
"steps": 4,
"permission": {
"edit": "deny",
"bash": "allow",
"read": "allow",
"grep": "allow",
"glob": "allow",
"list": "allow",
"task": "allow",
"webfetch": "allow",
"websearch": "allow",
"lsp": "allow",
"external_directory": "allow"
}
},
"ci-review-fallback": {
"description": "Expanded read-only CI pull request reviewer fallback",
"mode": "primary",
"prompt": "{file:./ci-review-prompt.md}",
"steps": 12,
"permission": {
"edit": "deny",
"bash": "allow",
"read": "allow",
"grep": "allow",
"glob": "allow",
"list": "allow",
"task": "allow",
"webfetch": "allow",
"websearch": "allow",
"lsp": "allow",
"external_directory": "allow"
}
}
},
"provider": {
"github-models": {
"npm": "@ai-sdk/openai-compatible",
"name": "GitHub Models",
"options": {
"baseURL": "https://models.github.ai/inference",
"apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}"
},
"models": {
"openai/gpt-5": {
"name": "OpenAI GPT-5",
"tool_call": true,
"limit": {
"context": 200000,
"output": 100000
}
},
"deepseek/deepseek-r1-0528": {
"name": "DeepSeek R1 0528",
"tool_call": true,
"reasoning": true,
"limit": {
"context": 128000,
"output": 4096
}
},
"deepseek/deepseek-v3-0324": {
"name": "DeepSeek V3 0324",
"tool_call": true,
"limit": {
"context": 128000,
"output": 4096
}
},
"openai/o3": {
"name": "OpenAI o3",
"tool_call": true,
"reasoning": true,
"limit": {
"context": 200000,
"output": 100000
}
},
"openai/o4-mini": {
"name": "OpenAI o4-mini",
"tool_call": true,
"reasoning": true,
"limit": {
"context": 200000,
"output": 100000
}
}
}
}
}
}
Loading
Loading