cpflow 5.2.0 → 6.0.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.agents/agent-workflow.yml +26 -2
- data/.agents/bin/README.md +16 -4
- data/.agents/bin/docs +1 -1
- data/.agents/bin/lint +1 -1
- data/.agents/bin/setup +1 -1
- data/.agents/bin/test +1 -1
- data/.agents/bin/validate +2 -1
- data/.coderabbit.yaml +13 -0
- data/.github/actions/cpflow-delete-control-plane-app/action.yml +2 -1
- data/.github/actions/cpflow-setup-environment/action.yml +9 -7
- data/.github/actions/cpflow-wait-for-health/action.yml +87 -15
- data/.github/dependabot.yml +10 -0
- data/.github/pull_request_template.md +18 -0
- data/.github/workflows/check_cpln_links.yml +6 -1
- data/.github/workflows/claude-code-review.yml +5 -2
- data/.github/workflows/claude.yml +97 -4
- data/.github/workflows/coderabbit-lifecycle-review.yml +29 -0
- data/.github/workflows/command_docs.yml +7 -2
- data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +12 -5
- data/.github/workflows/cpflow-delete-review-app.yml +638 -43
- data/.github/workflows/cpflow-deploy-review-app.yml +665 -36
- data/.github/workflows/cpflow-deploy-staging.yml +16 -10
- data/.github/workflows/cpflow-help-command.yml +3 -3
- data/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
- data/.github/workflows/cpflow-review-app-help.yml +6 -14
- data/.github/workflows/rspec-shared.yml +34 -26
- data/.github/workflows/rspec-specific.yml +10 -2
- data/.github/workflows/rspec.yml +71 -4
- data/.github/workflows/rubocop.yml +9 -2
- data/.github/workflows/trigger-docs-site.yml +2 -0
- data/.rubocop.yml +6 -1
- data/AGENTS.md +6 -49
- data/CHANGELOG.md +56 -1
- data/CONTRIBUTING.md +48 -3
- data/Gemfile.lock +1 -1
- data/README.md +6 -1
- data/cpflow.gemspec +3 -15
- data/docs/ai-github-flow-prompt.md +2 -2
- data/docs/ci-automation.md +216 -99
- data/docs/commands.md +36 -9
- data/docs/rds-private-networking.md +12 -12
- data/docs/releasing.md +15 -4
- data/docs/secrets-and-env-values.md +8 -0
- data/docs/tips.md +18 -0
- data/examples/controlplane.yml +5 -0
- data/lib/command/ai_github_flow_prompt.rb +1 -1
- data/lib/command/apply_template.rb +104 -2
- data/lib/command/base.rb +52 -3
- data/lib/command/cleanup_stale_apps.rb +28 -4
- data/lib/command/copy_image_from_upstream.rb +3 -0
- data/lib/command/deploy_image.rb +54 -3
- data/lib/command/generate_github_actions.rb +36 -12
- data/lib/command/run.rb +357 -44
- data/lib/command/setup_app.rb +10 -5
- data/lib/command/update_github_actions.rb +7 -6
- data/lib/core/controlplane.rb +179 -79
- data/lib/core/controlplane_api.rb +8 -0
- data/lib/core/controlplane_api_direct.rb +257 -63
- data/lib/core/shell.rb +39 -7
- data/lib/core/timed_command.rb +111 -0
- data/lib/cpflow/version.rb +1 -1
- data/lib/cpflow.rb +1 -1
- data/lib/github_flow_templates/.github/cpflow-help.md +24 -10
- data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +10 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +9 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
- data/lib/github_flow_templates/bin/test-cpflow-github-flow +63 -3
- data/lib/patches/hash.rb +2 -2
- data/rakelib/create_release.rake +14 -14
- data/script/check_shell_scripts +66 -0
- metadata +12 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73c78e337b3728ba8273db59f8f7d9540e855e986ba7aed509c3091a7cf806dd
|
|
4
|
+
data.tar.gz: d399af718dd0ab5a103d13b2eb7bc3515d2839077280254c29563fa88f14559d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad64de2a50dac54f6c9ee541ec8a2e80f13a36b81cedc2c58924c881ed80df8ff5f6c231255cae7328fbeeda80992e98fbab613f406f149478e7be2711a5a958
|
|
7
|
+
data.tar.gz: 927713a8b5e829ec6c89aef86ba9020d686bf6b1647473efbb356f31f9ca590185b8c582c061afd555ad32efa6f6caf38c834fba03cf56a52b2a7970f239f2db
|
data/.agents/agent-workflow.yml
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Non-command agent-workflow configuration for portable shared skills.
|
|
2
|
+
# This file is the canonical source for the non-command policy values below.
|
|
3
|
+
# AGENTS.md directs portable skills here; it does not duplicate or override them.
|
|
2
4
|
# Commands live as scripts in .agents/bin/ (see .agents/bin/README.md).
|
|
3
|
-
# Compatibility summary for older workflow copies; canonical values live in
|
|
4
|
-
# AGENTS.md.
|
|
5
5
|
base_branch: main
|
|
6
|
+
merge_submission:
|
|
7
|
+
mode: direct
|
|
6
8
|
changelog: "CHANGELOG.md — Keep-a-Changelog; user-visible changes only"
|
|
7
9
|
follow_up_prefix: "Follow-up:"
|
|
8
10
|
review_gate: "AI reviewers are advisory unless they confirm a blocker; merge gate is the full `gh pr checks` list green (not --required) + all threads resolved + mergeable clean"
|
|
@@ -11,5 +13,27 @@ coordination_backend: "private shakacode/agent-coordination (claims/heartbeats n
|
|
|
11
13
|
benchmark_labels: n/a
|
|
12
14
|
merge_ledger: n/a
|
|
13
15
|
ci_parity_environment: "n/a — reproduce CI-only failures from the matching job in .github/workflows/**"
|
|
16
|
+
release_qa_runbook: ".agents/workflows/ai-rollout-e2e-test.md — use after publishing a cpflow gem that changes GitHub Actions, AI rollout prompts, readiness checks, generator output, or React on Rails deployment behavior"
|
|
14
17
|
hosted_ci_trigger: "n/a — CI runs on every PR"
|
|
15
18
|
ci_change_detector: n/a
|
|
19
|
+
|
|
20
|
+
# Consumed by the portable untrusted-contributor-intake skill. These values must
|
|
21
|
+
# be read from the trusted base ref, never from contributor-controlled PR content.
|
|
22
|
+
untrusted_contributor_intake:
|
|
23
|
+
trusted_github_host: github.com
|
|
24
|
+
trusted_github_scheme: https
|
|
25
|
+
trusted_github_repo: shakacode/control-plane-flow
|
|
26
|
+
repo_prefix: CPF
|
|
27
|
+
|
|
28
|
+
# External actions already used by this repository and reviewed for the closed
|
|
29
|
+
# allowlist enforced by the GitHub Actions security scanner. Every use still
|
|
30
|
+
# requires an immutable commit SHA and a same-line release-version comment.
|
|
31
|
+
trusted_actions:
|
|
32
|
+
- actions/checkout
|
|
33
|
+
- actions/create-github-app-token
|
|
34
|
+
- actions/github-script
|
|
35
|
+
- actions/upload-artifact
|
|
36
|
+
- anthropics/claude-code-action
|
|
37
|
+
- docker/setup-buildx-action
|
|
38
|
+
- peter-evans/repository-dispatch
|
|
39
|
+
- ruby/setup-ruby
|
data/.agents/bin/README.md
CHANGED
|
@@ -8,12 +8,24 @@ means that capability is n/a here.
|
|
|
8
8
|
| Script | Purpose | This repo runs |
|
|
9
9
|
| --- | --- | --- |
|
|
10
10
|
| `setup` | Install dependencies | `bundle install` |
|
|
11
|
-
| `validate` | Pre-push gate (run before pushing) | `bundle exec rake` (rspec + rubocop) |
|
|
11
|
+
| `validate` | Pre-push gate (run before pushing) | ShellCheck, then `bundle exec rake` (rspec + rubocop) |
|
|
12
12
|
| `test` | Run tests | `bundle exec rspec` |
|
|
13
13
|
| `lint` | Lint / format (pass `-A` to fix) | `bundle exec rubocop` |
|
|
14
14
|
| `docs` | Check generated command docs | `bundle exec rake check_command_docs` |
|
|
15
15
|
| `build` | Build / type-check | n/a (gem) |
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
`validate` requires ShellCheck to be installed and available as `shellcheck` on
|
|
18
|
+
`PATH`.
|
|
19
|
+
|
|
20
|
+
`script/check_shell_scripts` checks every Git-tracked `.sh` and `.bash` file,
|
|
21
|
+
plus the extensionless shell entrypoints declared in its
|
|
22
|
+
`extensionless_shell_files` array. It does not infer script languages from
|
|
23
|
+
shebangs. When adding, renaming, or removing an extensionless shell script,
|
|
24
|
+
update that array and its inventory regression test; use a `.sh` or `.bash`
|
|
25
|
+
suffix for automatic inclusion. Declared paths must remain tracked and readable.
|
|
26
|
+
Unlisted extensionless files and non-shell files are outside this check.
|
|
27
|
+
|
|
28
|
+
Canonical non-command policy, including the release-QA runbook reference, lives
|
|
29
|
+
in [`../agent-workflow.yml`](../agent-workflow.yml). [`../../AGENTS.md`](../../AGENTS.md)
|
|
30
|
+
is the thin discovery pointer for portable shared skills and does not duplicate
|
|
31
|
+
or override that policy.
|
data/.agents/bin/docs
CHANGED
data/.agents/bin/lint
CHANGED
data/.agents/bin/setup
CHANGED
data/.agents/bin/test
CHANGED
data/.agents/bin/validate
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Pre-push gate: full local validation (run before pushing).
|
|
3
3
|
set -euo pipefail
|
|
4
|
-
cd "$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
4
|
+
cd "$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
5
|
+
./script/check_shell_scripts
|
|
5
6
|
exec bundle exec rake
|
data/.coderabbit.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
reviews:
|
|
2
|
+
profile: chill
|
|
3
|
+
review_status: false
|
|
4
|
+
review_progress: false
|
|
5
|
+
commit_status: false
|
|
6
|
+
in_progress_fortune: false
|
|
7
|
+
collapse_walkthrough: true
|
|
8
|
+
auto_review:
|
|
9
|
+
enabled: false
|
|
10
|
+
auto_incremental_review: false
|
|
11
|
+
labels:
|
|
12
|
+
- coderabbit:first-pass
|
|
13
|
+
- coderabbit:ready
|
|
@@ -22,8 +22,9 @@ runs:
|
|
|
22
22
|
- name: Delete application
|
|
23
23
|
shell: bash
|
|
24
24
|
working-directory: ${{ inputs.working_directory }}
|
|
25
|
-
run: ${
|
|
25
|
+
run: "${ACTION_PATH}/delete-app.sh"
|
|
26
26
|
env:
|
|
27
|
+
ACTION_PATH: ${{ github.action_path }}
|
|
27
28
|
APP_NAME: ${{ inputs.app_name }}
|
|
28
29
|
CPLN_ORG: ${{ inputs.cpln_org }}
|
|
29
30
|
REVIEW_APP_PREFIX: ${{ inputs.review_app_prefix }}
|
|
@@ -19,6 +19,10 @@ inputs:
|
|
|
19
19
|
description: Directory where ruby/setup-ruby should detect Ruby version files.
|
|
20
20
|
required: false
|
|
21
21
|
default: "."
|
|
22
|
+
cpflow_source_directory:
|
|
23
|
+
description: Directory containing the checked-out control-plane-flow source.
|
|
24
|
+
required: false
|
|
25
|
+
default: ".cpflow"
|
|
22
26
|
# GitHub parses double-brace expression snippets inside action metadata (including
|
|
23
27
|
# `description:`) while loading the composite action, and the `vars` context is not
|
|
24
28
|
# available in that phase. Keep these descriptions in plain prose - reference repo
|
|
@@ -47,8 +51,8 @@ inputs:
|
|
|
47
51
|
|
|
48
52
|
runs:
|
|
49
53
|
using: composite
|
|
50
|
-
#
|
|
51
|
-
#
|
|
54
|
+
# Dependabot proposes action updates, but each reviewed dependency remains
|
|
55
|
+
# pinned to the release commit recorded beside its exact version comment.
|
|
52
56
|
steps:
|
|
53
57
|
- name: Resolve Ruby setup version
|
|
54
58
|
id: ruby-version
|
|
@@ -80,9 +84,7 @@ runs:
|
|
|
80
84
|
echo "ruby_version=${ruby_version}" >> "$GITHUB_OUTPUT"
|
|
81
85
|
|
|
82
86
|
- name: Set up Ruby
|
|
83
|
-
|
|
84
|
-
# roll out automatically; Dependabot/Renovate can manage this non-GitHub action.
|
|
85
|
-
uses: ruby/setup-ruby@v1
|
|
87
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
86
88
|
with:
|
|
87
89
|
ruby-version: ${{ steps.ruby-version.outputs.ruby_version }}
|
|
88
90
|
working-directory: ${{ inputs.working_directory }}
|
|
@@ -93,13 +95,13 @@ runs:
|
|
|
93
95
|
CONTROL_PLANE_FLOW_REF: ${{ inputs.control_plane_flow_ref }}
|
|
94
96
|
CPLN_CLI_VERSION: ${{ inputs.cpln_cli_version }}
|
|
95
97
|
CPFLOW_VERSION: ${{ inputs.cpflow_version }}
|
|
96
|
-
CPFLOW_SOURCE_DIR: ${{
|
|
98
|
+
CPFLOW_SOURCE_DIR: ${{ inputs.cpflow_source_directory }}
|
|
97
99
|
run: |
|
|
98
100
|
set -euo pipefail
|
|
99
101
|
|
|
100
102
|
# Bump this default when a new Control Plane CLI release should roll out by default.
|
|
101
103
|
# Override per-repo by setting the `CPLN_CLI_VERSION` repo variable.
|
|
102
|
-
default_cpln_cli_version="3.
|
|
104
|
+
default_cpln_cli_version="3.11.0"
|
|
103
105
|
|
|
104
106
|
CPLN_CLI_VERSION="${CPLN_CLI_VERSION:-${default_cpln_cli_version}}"
|
|
105
107
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name: Wait for Control Plane workload health
|
|
2
2
|
description: >-
|
|
3
3
|
Polls Control Plane until the latest workload version is ready, then checks
|
|
4
|
-
the workload endpoint with curl. Exits
|
|
5
|
-
is in the accepted list. Fails non-zero
|
|
6
|
-
retries are exhausted.
|
|
4
|
+
the standard workload endpoint and ready location endpoints with curl. Exits
|
|
5
|
+
success when the HTTP response status is in the accepted list. Fails non-zero
|
|
6
|
+
(and reports `healthy=false`) once retries are exhausted.
|
|
7
7
|
|
|
8
8
|
inputs:
|
|
9
9
|
workload_name:
|
|
@@ -39,6 +39,9 @@ outputs:
|
|
|
39
39
|
healthy:
|
|
40
40
|
description: '"true" once a healthy response was observed; "false" otherwise.'
|
|
41
41
|
value: ${{ steps.poll.outputs.healthy }}
|
|
42
|
+
endpoint:
|
|
43
|
+
description: The workload or location endpoint that returned an accepted status.
|
|
44
|
+
value: ${{ steps.poll.outputs.endpoint }}
|
|
42
45
|
|
|
43
46
|
runs:
|
|
44
47
|
using: composite
|
|
@@ -59,6 +62,40 @@ runs:
|
|
|
59
62
|
|
|
60
63
|
read -r -a accepted_statuses <<< "${CPFLOW_ACCEPTED_STATUSES}"
|
|
61
64
|
|
|
65
|
+
check_endpoint() {
|
|
66
|
+
local endpoint="$1"
|
|
67
|
+
local endpoint_type="$2"
|
|
68
|
+
local http_status
|
|
69
|
+
|
|
70
|
+
http_status="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${CPFLOW_CURL_MAX_TIME}" "${endpoint}" 2>/dev/null || true)"
|
|
71
|
+
http_status="${http_status:-000}"
|
|
72
|
+
echo "${endpoint_type}: ${endpoint}, HTTP status: ${http_status}"
|
|
73
|
+
|
|
74
|
+
for accepted in "${accepted_statuses[@]}"; do
|
|
75
|
+
if [[ "${http_status}" == "${accepted}" ]]; then
|
|
76
|
+
{
|
|
77
|
+
echo "healthy=true"
|
|
78
|
+
echo "endpoint=${endpoint}"
|
|
79
|
+
} >> "$GITHUB_OUTPUT"
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
return 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
summarize_cpln_error() {
|
|
88
|
+
local error_text="$1"
|
|
89
|
+
|
|
90
|
+
case "${error_text}" in
|
|
91
|
+
*401*|*403*|*[Uu]nauthorized*|*[Ff]orbidden*) echo "authorization failure" ;;
|
|
92
|
+
*404*|*"not found"*|*"Not Found"*) echo "resource not found" ;;
|
|
93
|
+
*429*|*"rate limit"*|*"Rate Limit"*) echo "rate limited" ;;
|
|
94
|
+
*"timed out"*|*[Tt]imeout*) echo "request timeout" ;;
|
|
95
|
+
*) echo "CLI error (details suppressed; ${#error_text} stderr bytes captured)" ;;
|
|
96
|
+
esac
|
|
97
|
+
}
|
|
98
|
+
|
|
62
99
|
for attempt in $(seq 1 "${CPFLOW_MAX_RETRIES}"); do
|
|
63
100
|
echo "Health check attempt ${attempt}/${CPFLOW_MAX_RETRIES}"
|
|
64
101
|
|
|
@@ -72,22 +109,57 @@ runs:
|
|
|
72
109
|
workload_ready="$(echo "${workload_json}" | jq -r '.status.ready // false')"
|
|
73
110
|
latest_ready="$(echo "${workload_json}" | jq -r '.status.readyLatest // false')"
|
|
74
111
|
readiness_status="$(echo "${workload_json}" | jq -r '.health.readiness // "unknown"')"
|
|
75
|
-
|
|
112
|
+
workload_endpoint="$(echo "${workload_json}" | jq -r '.status.endpoint // empty')"
|
|
76
113
|
|
|
77
114
|
if [[ "${workload_ready}" != "true" || "${latest_ready}" != "true" ]]; then
|
|
78
115
|
echo "Workload status: ready=${workload_ready}, readyLatest=${latest_ready}, readiness=${readiness_status}; waiting for latest deployment."
|
|
79
|
-
elif [[ -n "${endpoint}" ]]; then
|
|
80
|
-
http_status="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${CPFLOW_CURL_MAX_TIME}" "${endpoint}" 2>/dev/null || echo 000)"
|
|
81
|
-
echo "Endpoint: ${endpoint}, HTTP status: ${http_status}"
|
|
82
|
-
|
|
83
|
-
for accepted in "${accepted_statuses[@]}"; do
|
|
84
|
-
if [[ "${http_status}" == "${accepted}" ]]; then
|
|
85
|
-
echo "healthy=true" >> "$GITHUB_OUTPUT"
|
|
86
|
-
exit 0
|
|
87
|
-
fi
|
|
88
|
-
done
|
|
89
116
|
else
|
|
90
|
-
|
|
117
|
+
if [[ -n "${workload_endpoint}" ]] && check_endpoint "${workload_endpoint}" "Workload endpoint"; then
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
location_endpoint_count=0
|
|
122
|
+
deployments_listed=false
|
|
123
|
+
deployments_settled=false
|
|
124
|
+
deployments_stderr="$(mktemp)"
|
|
125
|
+
if deployments_json="$(cpln workload get-deployments "${CPFLOW_WORKLOAD_NAME}" --gvc "${CPFLOW_APP_NAME}" --org "${CPFLOW_ORG}" -o json 2>"${deployments_stderr}")"; then
|
|
126
|
+
deployments_listed=true
|
|
127
|
+
unsettled_location_count="$(
|
|
128
|
+
printf '%s\n' "${deployments_json}" |
|
|
129
|
+
jq '[.items[]? | select(.status.ready != true or (.status.deploying // false) == true)] | length'
|
|
130
|
+
)"
|
|
131
|
+
|
|
132
|
+
if [[ "${unsettled_location_count}" -gt 0 ]]; then
|
|
133
|
+
echo "Deployment locations are not all ready (${unsettled_location_count} unsettled); waiting before endpoint fallback."
|
|
134
|
+
else
|
|
135
|
+
deployments_settled=true
|
|
136
|
+
while IFS= read -r location_endpoint; do
|
|
137
|
+
location_endpoint_count=$((location_endpoint_count + 1))
|
|
138
|
+
if [[ -n "${location_endpoint}" &&
|
|
139
|
+
"${location_endpoint}" != "${workload_endpoint}" ]] &&
|
|
140
|
+
check_endpoint "${location_endpoint}" "Location endpoint"; then
|
|
141
|
+
rm -f "${deployments_stderr}"
|
|
142
|
+
exit 0
|
|
143
|
+
fi
|
|
144
|
+
done < <(
|
|
145
|
+
printf '%s\n' "${deployments_json}" |
|
|
146
|
+
jq -r '.items[]? | .status.endpoint // empty' |
|
|
147
|
+
awk '!seen[$0]++'
|
|
148
|
+
)
|
|
149
|
+
fi
|
|
150
|
+
else
|
|
151
|
+
deployments_error="$(<"${deployments_stderr}")"
|
|
152
|
+
deployments_error_summary="$(summarize_cpln_error "${deployments_error}")"
|
|
153
|
+
echo "::warning::Could not list location endpoints for workload '${CPFLOW_WORKLOAD_NAME}' (${deployments_error_summary}); retrying."
|
|
154
|
+
fi
|
|
155
|
+
rm -f "${deployments_stderr}"
|
|
156
|
+
|
|
157
|
+
if [[ -z "${workload_endpoint}" &&
|
|
158
|
+
"${deployments_listed}" == "true" &&
|
|
159
|
+
"${deployments_settled}" == "true" &&
|
|
160
|
+
"${location_endpoint_count}" -eq 0 ]]; then
|
|
161
|
+
echo "Workload '${CPFLOW_WORKLOAD_NAME}' has no endpoint yet; waiting for one to be assigned."
|
|
162
|
+
fi
|
|
91
163
|
fi
|
|
92
164
|
|
|
93
165
|
if [[ "${attempt}" -lt "${CPFLOW_MAX_RETRIES}" ]]; then
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What changes, and why is it useful? -->
|
|
4
|
+
|
|
5
|
+
## Related issue
|
|
6
|
+
|
|
7
|
+
<!-- Use "Closes #123" for an accepted issue, or link the maintainer discussion. -->
|
|
8
|
+
|
|
9
|
+
## Validation
|
|
10
|
+
|
|
11
|
+
<!-- List the exact commands or manual checks you ran. -->
|
|
12
|
+
|
|
13
|
+
## Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] This PR addresses an accepted issue or maintainer request, or is a small typo fix.
|
|
16
|
+
- [ ] The change is focused and contains no unrelated cleanup or generated churn.
|
|
17
|
+
- [ ] I added or updated tests, documentation, and `CHANGELOG.md` where applicable.
|
|
18
|
+
- [ ] I reviewed and understand all submitted content, including AI-assisted changes.
|
|
@@ -6,13 +6,18 @@ on:
|
|
|
6
6
|
- main
|
|
7
7
|
pull_request:
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
jobs:
|
|
10
13
|
check_cpln_links:
|
|
11
14
|
runs-on: ubuntu-latest
|
|
12
15
|
name: Check Links
|
|
13
16
|
steps:
|
|
14
17
|
- name: Checkout repository
|
|
15
|
-
uses: actions/checkout@
|
|
18
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
16
21
|
- name: Validate outgoing links to Control Plane
|
|
17
22
|
run: |
|
|
18
23
|
TERM=xterm-color ./script/check_cpln_links
|
|
@@ -4,6 +4,8 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
types: [opened, synchronize, ready_for_review, reopened]
|
|
6
6
|
|
|
7
|
+
permissions: {}
|
|
8
|
+
|
|
7
9
|
jobs:
|
|
8
10
|
claude-review:
|
|
9
11
|
runs-on: ubuntu-latest
|
|
@@ -15,13 +17,14 @@ jobs:
|
|
|
15
17
|
|
|
16
18
|
steps:
|
|
17
19
|
- name: Checkout repository
|
|
18
|
-
uses: actions/checkout@
|
|
20
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
19
21
|
with:
|
|
20
22
|
fetch-depth: 1
|
|
23
|
+
persist-credentials: false
|
|
21
24
|
|
|
22
25
|
- name: Run Claude Code Review
|
|
23
26
|
id: claude-review
|
|
24
|
-
uses: anthropics/claude-code-action@v1
|
|
27
|
+
uses: anthropics/claude-code-action@8251c103ac8c1d761882c86aba1412c7f583c844 # v1.0.213
|
|
25
28
|
env:
|
|
26
29
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
27
30
|
with:
|
|
@@ -10,14 +10,107 @@ on:
|
|
|
10
10
|
pull_request_review:
|
|
11
11
|
types: [submitted]
|
|
12
12
|
|
|
13
|
+
permissions: {}
|
|
14
|
+
|
|
13
15
|
jobs:
|
|
14
|
-
|
|
16
|
+
authorize_claude_actor:
|
|
15
17
|
if: |
|
|
16
18
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
19
|
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
20
|
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
21
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
22
|
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
outputs:
|
|
26
|
+
authorized: ${{ steps.permission-gate.outputs.authorized }}
|
|
27
|
+
steps:
|
|
28
|
+
- name: Verify actor can run Claude
|
|
29
|
+
id: permission-gate
|
|
30
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
31
|
+
with:
|
|
32
|
+
script: |
|
|
33
|
+
const owner = context.repo.owner;
|
|
34
|
+
const repo = context.repo.repo;
|
|
35
|
+
const permissionByLogin = new Map();
|
|
36
|
+
|
|
37
|
+
function claudeRequester() {
|
|
38
|
+
if (
|
|
39
|
+
context.eventName === 'issue_comment' ||
|
|
40
|
+
context.eventName === 'pull_request_review_comment'
|
|
41
|
+
) {
|
|
42
|
+
return context.payload.comment?.user?.login;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (context.eventName === 'pull_request_review') {
|
|
46
|
+
return context.payload.review?.user?.login;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (context.eventName === 'issues') {
|
|
50
|
+
return context.payload.issue?.user?.login;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function hasWriteAccessFor(username) {
|
|
57
|
+
if (!username) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (permissionByLogin.has(username)) {
|
|
62
|
+
return permissionByLogin.get(username);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let hasWriteAccess = false;
|
|
66
|
+
try {
|
|
67
|
+
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
68
|
+
owner,
|
|
69
|
+
repo,
|
|
70
|
+
username
|
|
71
|
+
});
|
|
72
|
+
hasWriteAccess = ['admin', 'write'].includes(permission.permission);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
core.warning(
|
|
75
|
+
`Unable to verify ${username} write permission for Claude; ` +
|
|
76
|
+
`treating the Claude request as untrusted: ${error.message || error}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
permissionByLogin.set(username, hasWriteAccess);
|
|
81
|
+
return hasWriteAccess;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const actor = context.actor;
|
|
85
|
+
const requester = claudeRequester();
|
|
86
|
+
|
|
87
|
+
if (!requester) {
|
|
88
|
+
core.setOutput('authorized', 'false');
|
|
89
|
+
core.setFailed('Unable to run Claude because the requester could not be determined.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!(await hasWriteAccessFor(actor))) {
|
|
94
|
+
core.setOutput('authorized', 'false');
|
|
95
|
+
core.setFailed(`Unable to run Claude because ${actor} does not have write/admin repository permission.`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!(await hasWriteAccessFor(requester))) {
|
|
100
|
+
core.setOutput('authorized', 'false');
|
|
101
|
+
core.setFailed(
|
|
102
|
+
`Unable to run Claude because requester ${requester} does not have write/admin repository permission.`
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
core.setOutput('authorized', 'true');
|
|
108
|
+
core.info(`Authorized ${actor} and Claude requester ${requester} to run Claude.`);
|
|
109
|
+
|
|
110
|
+
claude:
|
|
111
|
+
needs: authorize_claude_actor
|
|
112
|
+
if: needs.authorize_claude_actor.outputs.authorized == 'true'
|
|
113
|
+
runs-on: ubuntu-latest
|
|
21
114
|
permissions:
|
|
22
115
|
contents: read
|
|
23
116
|
pull-requests: read
|
|
@@ -28,13 +121,14 @@ jobs:
|
|
|
28
121
|
statuses: read
|
|
29
122
|
steps:
|
|
30
123
|
- name: Checkout repository
|
|
31
|
-
uses: actions/checkout@
|
|
124
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
125
|
with:
|
|
33
126
|
fetch-depth: 1
|
|
127
|
+
persist-credentials: false
|
|
34
128
|
|
|
35
129
|
- name: Run Claude Code
|
|
36
130
|
id: claude
|
|
37
|
-
uses: anthropics/claude-code-action@v1
|
|
131
|
+
uses: anthropics/claude-code-action@8251c103ac8c1d761882c86aba1412c7f583c844 # v1.0.213
|
|
38
132
|
env:
|
|
39
133
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
40
134
|
with:
|
|
@@ -54,4 +148,3 @@ jobs:
|
|
|
54
148
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
55
149
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
56
150
|
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
|
57
|
-
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CodeRabbit Lifecycle Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, ready_for_review]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
request-review:
|
|
12
|
+
if: github.event.pull_request.head.repo.fork == false
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Add CodeRabbit lifecycle label
|
|
16
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
17
|
+
with:
|
|
18
|
+
# Both labels must already exist in the repo (addLabels does not create them); created 2026-09-06.
|
|
19
|
+
script: |
|
|
20
|
+
const label = context.payload.action === 'opened'
|
|
21
|
+
? 'coderabbit:first-pass'
|
|
22
|
+
: 'coderabbit:ready';
|
|
23
|
+
|
|
24
|
+
await github.rest.issues.addLabels({
|
|
25
|
+
owner: context.repo.owner,
|
|
26
|
+
repo: context.repo.repo,
|
|
27
|
+
issue_number: context.issue.number,
|
|
28
|
+
labels: [label]
|
|
29
|
+
});
|
|
@@ -6,15 +6,20 @@ on:
|
|
|
6
6
|
- main
|
|
7
7
|
pull_request:
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
jobs:
|
|
10
13
|
rspec:
|
|
11
14
|
runs-on: ubuntu-latest
|
|
12
15
|
name: Command Docs
|
|
13
16
|
steps:
|
|
14
17
|
- name: Checkout code
|
|
15
|
-
uses: actions/checkout@
|
|
18
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
16
21
|
- name: Set up Ruby
|
|
17
|
-
uses: ruby/setup-ruby@v1
|
|
22
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
18
23
|
with:
|
|
19
24
|
ruby-version: "3.2"
|
|
20
25
|
bundler-cache: true
|
|
@@ -23,8 +23,15 @@ jobs:
|
|
|
23
23
|
runs-on: ubuntu-latest
|
|
24
24
|
timeout-minutes: 30
|
|
25
25
|
steps:
|
|
26
|
+
# No explicit ref: checkout's default resolves to this event's recorded commit
|
|
27
|
+
# (GITHUB_SHA), which is trusted base-repository code and is exempt from checkout v7's fork-PR guard.
|
|
28
|
+
- name: Checkout generated cpflow actions
|
|
29
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
30
|
+
with:
|
|
31
|
+
persist-credentials: false
|
|
32
|
+
|
|
26
33
|
- name: Checkout control-plane-flow actions
|
|
27
|
-
uses: actions/checkout@
|
|
34
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
28
35
|
with:
|
|
29
36
|
repository: ${{ job.workflow_repository }}
|
|
30
37
|
ref: ${{ job.workflow_sha }}
|
|
@@ -32,7 +39,7 @@ jobs:
|
|
|
32
39
|
persist-credentials: false
|
|
33
40
|
|
|
34
41
|
- name: Validate required secrets and variables
|
|
35
|
-
uses: ./.
|
|
42
|
+
uses: ./.github/actions/cpflow-validate-config
|
|
36
43
|
env:
|
|
37
44
|
CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
|
|
38
45
|
with:
|
|
@@ -40,21 +47,21 @@ jobs:
|
|
|
40
47
|
secret:CPLN_TOKEN_STAGING
|
|
41
48
|
|
|
42
49
|
- name: Checkout caller repository
|
|
43
|
-
uses: actions/checkout@
|
|
50
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
51
|
with:
|
|
45
52
|
path: app
|
|
46
53
|
persist-credentials: false
|
|
47
54
|
|
|
48
55
|
- name: Resolve review app config
|
|
49
56
|
id: review-config
|
|
50
|
-
uses: ./.
|
|
57
|
+
uses: ./.github/actions/cpflow-resolve-review-config
|
|
51
58
|
with:
|
|
52
59
|
working_directory: app
|
|
53
60
|
configured_cpln_org_staging: ${{ vars.CPLN_ORG_STAGING }}
|
|
54
61
|
configured_review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
|
|
55
62
|
|
|
56
63
|
- name: Setup environment
|
|
57
|
-
uses: ./.
|
|
64
|
+
uses: ./.github/actions/cpflow-setup-environment
|
|
58
65
|
with:
|
|
59
66
|
token: ${{ secrets.CPLN_TOKEN_STAGING }}
|
|
60
67
|
org: ${{ steps.review-config.outputs.cpln_org }}
|