code-foundry 0.32.4 → 0.33.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.
@@ -48,7 +48,7 @@ docs/* test/* refactor/* │ │
48
48
  | `staging` | Integration branch | Target normal pull requests here. Required checks must pass before merge. |
49
49
  | `feat/*`, `fix/*`, `chore/*`, `refactor/*`, `docs/*`, `test/*` | Focused work | Branch from `staging`; keep changes small and reviewable. |
50
50
 
51
- The default Git workflow is `staging-release`: topic branches merge into `staging` (typically after rebasing), then a promotion PR moves validated changes into `main`, followed by the versioned release PR. The default release merge strategy is `merge`, matching your preference for merge-commit promotions from staging to main. Configure `merge_strategy` as `rebase` or `squash` in `.github/code-foundry.yml` if a repository intentionally differs. Re-align `staging` with `main` after a release when needed.
51
+ The Git workflow is `staging-release`: topic branches **squash** into `staging`, a promotion PR **rebases** validated changes into `main` (`merge_strategy: rebase`), and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other merge strategy. Re-align `staging` with `main` after a release when needed.
52
52
 
53
53
  ## Before you start
54
54
 
@@ -107,7 +107,7 @@ For maintainers, trusted contributors, and automation agents:
107
107
 
108
108
  8. Push the branch and open a pull request into `staging`.
109
109
  9. Address review feedback and failed checks on the same branch.
110
- 10. Merge using the repository's configured `merge_strategy` after required checks pass and the change is ready.
110
+ 10. Merge with a squash after required checks pass and the change is ready; feature PRs land on `staging` with squash merges.
111
111
 
112
112
  ### Internal agent handoff
113
113
 
@@ -153,15 +153,17 @@ Keep pull requests focused and reviewable. Include screenshots or recordings for
153
153
 
154
154
  ## Workflow and check behavior
155
155
 
156
- | Event | Expected automation |
157
- | -------------------------------- | ---------------------------------------- |
158
- | Push to `main` or `staging` | CI, Test, Security, and CodeQL workflows |
159
- | Pull request targeting `staging` | CI, Test, Security, and CodeQL workflows |
160
- | Push to a working branch | Draft PR workflow |
161
- | Push to `staging` | Release PR workflow |
162
- | Version tag such as `v1.2.3` | Release workflow |
156
+ | Event | Expected automation |
157
+ | --- | --- |
158
+ | Pull request targeting `staging` | Fast validation: CI plus unit tests, ending in `Validation / Gate` |
159
+ | Ordinary pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |
160
+ | Exact Release Please pull request targeting `main` | Release-policy validation only, ending in `Validation / Gate` |
161
+ | Scheduled or manual validation | Full audit tier |
162
+ | Push to a working branch | Draft PR workflow |
163
+ | Push to `staging` | Promotion PR workflow; canonical validation waits for the PR event |
164
+ | Push to `main` | Release workflow; canonical validation already ran on the merged PR |
163
165
 
164
- The workflows use separate concurrency groups keyed by the commit under test. A newer run for the same commit cancels a duplicate event-triggered run, while newer commits cancel older runs and independent CI, Test, Security, and CodeQL workflows continue in parallel.
166
+ The single validation caller keys concurrency by event and pull-request head. A newer update to the same pull request cancels its superseded validation run; scheduled and manual audits remain independent. The mode-aware orchestrator fans out only the jobs required by that event and always concludes with the stable aggregate gate.
165
167
 
166
168
  Required checks are enforced by branch protection rulesets/branch protection. Do not duplicate their checklists in the pull request description; document validation commands and results instead.
167
169
 
@@ -173,10 +175,11 @@ Security checks can be skipped when repository visibility or the GitHub plan doe
173
175
 
174
176
  ## Review and merge protocol
175
177
 
176
- | Change | Target | Merge gate |
177
- | ----------------- | --------- | --------------------------------------------------------- |
178
- | Working branch | `staging` | All applicable required checks pass |
179
- | `staging` release | `main` | Current staging checks, release review, and rollout notes |
178
+ | Change | Target | Merge method | Merge gate |
179
+ | ----------------- | --------- | ------------------------------------------------------- | --------------------------------------------------------- |
180
+ | Working branch | `staging` | Squash | All applicable required checks pass |
181
+ | `staging` `main` promotion | `main` | Rebase (`merge_strategy`) | Current staging checks, release review, and rollout notes |
182
+ | Release Please version PR | `main` | Squash (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |
180
183
 
181
184
  Reviewers focus on correctness, security, maintainability, test coverage, operational impact, and compatibility. Authors remain responsible for responding to feedback and verifying the final commit.
182
185
 
@@ -18,6 +18,11 @@ on:
18
18
  required: false
19
19
  type: string
20
20
  default: ubuntu-latest
21
+ secrets:
22
+ TURBO_TOKEN:
23
+ required: false
24
+ NEXTAUTH_SECRET:
25
+ required: false
21
26
 
22
27
  permissions:
23
28
  contents: read
@@ -44,10 +49,13 @@ jobs:
44
49
  runs-on: ${{ inputs.runner }}
45
50
  steps:
46
51
  - name: Checkout
47
- uses: actions/checkout@v7
52
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
53
+ with:
54
+ persist-credentials: false
48
55
  - name: Runtime
49
- uses: actions/checkout@v7
56
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
50
57
  with:
58
+ persist-credentials: false
51
59
  repository: ${{ inputs.runtime-repository }}
52
60
  ref: ${{ inputs.runtime-ref }}
53
61
  path: .github/.code-foundry
@@ -84,10 +92,13 @@ jobs:
84
92
  runs-on: ${{ inputs.runner }}
85
93
  steps:
86
94
  - name: Checkout
87
- uses: actions/checkout@v7
95
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
96
+ with:
97
+ persist-credentials: false
88
98
  - name: Runtime
89
- uses: actions/checkout@v7
99
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
90
100
  with:
101
+ persist-credentials: false
91
102
  repository: ${{ inputs.runtime-repository }}
92
103
  ref: ${{ inputs.runtime-ref }}
93
104
  path: .github/.code-foundry
@@ -119,10 +130,13 @@ jobs:
119
130
  runs-on: ${{ inputs.runner }}
120
131
  steps:
121
132
  - name: Checkout
122
- uses: actions/checkout@v7
133
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
134
+ with:
135
+ persist-credentials: false
123
136
  - name: Runtime
124
- uses: actions/checkout@v7
137
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
125
138
  with:
139
+ persist-credentials: false
126
140
  repository: ${{ inputs.runtime-repository }}
127
141
  ref: ${{ inputs.runtime-ref }}
128
142
  path: .github/.code-foundry
@@ -159,10 +173,13 @@ jobs:
159
173
  NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET || 'code-foundry-ci-placeholder-0123456789' }}
160
174
  steps:
161
175
  - name: Checkout
162
- uses: actions/checkout@v7
176
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
177
+ with:
178
+ persist-credentials: false
163
179
  - name: Runtime
164
- uses: actions/checkout@v7
180
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
165
181
  with:
182
+ persist-credentials: false
166
183
  repository: ${{ inputs.runtime-repository }}
167
184
  ref: ${{ inputs.runtime-ref }}
168
185
  path: .github/.code-foundry
@@ -33,7 +33,6 @@ on:
33
33
  required: false
34
34
  type: number
35
35
  default: 1
36
-
37
36
  permissions:
38
37
  actions: read
39
38
  contents: read
@@ -76,17 +75,19 @@ jobs:
76
75
  id: security
77
76
  env:
78
77
  GH_TOKEN: ${{ github.token }}
78
+ REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }}
79
79
  run: |
80
80
  status="disabled"
81
- if [ "${{ github.event.repository.visibility }}" = "public" ]; then
81
+ if [ "$REPOSITORY_VISIBILITY" = "public" ]; then
82
82
  status="enabled"
83
83
  else
84
84
  status="$(gh api "repos/${GITHUB_REPOSITORY}" --jq '.security_and_analysis.code_security.status // "disabled"' 2>/dev/null || printf 'disabled')"
85
85
  fi
86
86
  printf 'status=%s\n' "$status" >> "$GITHUB_OUTPUT"
87
87
  - name: Checkout
88
- uses: actions/checkout@v7
88
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
89
89
  with:
90
+ persist-credentials: false
90
91
  fetch-depth: 2
91
92
  filter: blob:none
92
93
  sparse-checkout: |
@@ -101,8 +102,9 @@ jobs:
101
102
  package-lock.json
102
103
  sparse-checkout-cone-mode: false
103
104
  - name: Runtime
104
- uses: actions/checkout@v7
105
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
105
106
  with:
107
+ persist-credentials: false
106
108
  repository: ${{ inputs.runtime-repository }}
107
109
  ref: ${{ inputs.runtime-ref }}
108
110
  path: .github/.code-foundry
@@ -138,14 +140,17 @@ jobs:
138
140
  needs.detect.outputs.enabled == 'true' &&
139
141
  needs.detect.outputs.actions_available == 'true' &&
140
142
  needs.detect.outputs.actions_changed == 'true'
141
- uses: actions/checkout@v7
143
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
144
+ with:
145
+ persist-credentials: false
142
146
  - name: Runtime
143
147
  if: >-
144
148
  needs.detect.outputs.enabled == 'true' &&
145
149
  needs.detect.outputs.actions_available == 'true' &&
146
150
  needs.detect.outputs.actions_changed == 'true'
147
- uses: actions/checkout@v7
151
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
148
152
  with:
153
+ persist-credentials: false
149
154
  repository: ${{ inputs.runtime-repository }}
150
155
  ref: ${{ inputs.runtime-ref }}
151
156
  path: .github/.code-foundry
@@ -193,14 +198,17 @@ jobs:
193
198
  needs.detect.outputs.enabled == 'true' &&
194
199
  needs.detect.outputs.javascript_available == 'true' &&
195
200
  needs.detect.outputs.javascript_changed == 'true'
196
- uses: actions/checkout@v7
201
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
202
+ with:
203
+ persist-credentials: false
197
204
  - name: Runtime
198
205
  if: >-
199
206
  needs.detect.outputs.enabled == 'true' &&
200
207
  needs.detect.outputs.javascript_available == 'true' &&
201
208
  needs.detect.outputs.javascript_changed == 'true'
202
- uses: actions/checkout@v7
209
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
203
210
  with:
211
+ persist-credentials: false
204
212
  repository: ${{ inputs.runtime-repository }}
205
213
  ref: ${{ inputs.runtime-ref }}
206
214
  path: .github/.code-foundry
@@ -248,14 +256,17 @@ jobs:
248
256
  needs.detect.outputs.enabled == 'true' &&
249
257
  needs.detect.outputs.python_available == 'true' &&
250
258
  needs.detect.outputs.python_changed == 'true'
251
- uses: actions/checkout@v7
259
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
260
+ with:
261
+ persist-credentials: false
252
262
  - name: Runtime
253
263
  if: >-
254
264
  needs.detect.outputs.enabled == 'true' &&
255
265
  needs.detect.outputs.python_available == 'true' &&
256
266
  needs.detect.outputs.python_changed == 'true'
257
- uses: actions/checkout@v7
267
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
258
268
  with:
269
+ persist-credentials: false
259
270
  repository: ${{ inputs.runtime-repository }}
260
271
  ref: ${{ inputs.runtime-ref }}
261
272
  path: .github/.code-foundry
@@ -308,14 +319,17 @@ jobs:
308
319
  needs.detect.outputs.enabled == 'true' &&
309
320
  needs.detect.outputs.rust_available == 'true' &&
310
321
  needs.detect.outputs.rust_changed == 'true'
311
- uses: actions/checkout@v7
322
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
323
+ with:
324
+ persist-credentials: false
312
325
  - name: Runtime
313
326
  if: >-
314
327
  needs.detect.outputs.enabled == 'true' &&
315
328
  needs.detect.outputs.rust_available == 'true' &&
316
329
  needs.detect.outputs.rust_changed == 'true'
317
- uses: actions/checkout@v7
330
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
318
331
  with:
332
+ persist-credentials: false
319
333
  repository: ${{ inputs.runtime-repository }}
320
334
  ref: ${{ inputs.runtime-ref }}
321
335
  path: .github/.code-foundry
@@ -388,7 +402,7 @@ jobs:
388
402
  needs.detect.outputs.enabled == 'true' &&
389
403
  needs.detect.outputs.rust_available == 'true' &&
390
404
  needs.detect.outputs.rust_changed == 'true'
391
- uses: github/codeql-action/init@v4
405
+ uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4
392
406
  with:
393
407
  languages: rust
394
408
  build-mode: ${{ needs.detect.outputs.rust_build_mode }}
@@ -399,7 +413,7 @@ jobs:
399
413
  needs.detect.outputs.enabled == 'true' &&
400
414
  needs.detect.outputs.rust_available == 'true' &&
401
415
  needs.detect.outputs.rust_changed == 'true'
402
- uses: github/codeql-action/analyze@v4
416
+ uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4
403
417
  with:
404
418
  category: /language:rust/${{ steps.scope.outputs.scope_id }}
405
419
  upload-database: false
@@ -8,13 +8,18 @@ on:
8
8
  required: false
9
9
  type: string
10
10
  default: ubuntu-slim
11
+ secrets:
12
+ CODE_FOUNDRY_TOKEN:
13
+ required: false
14
+ RELEASE_PLEASE_TOKEN:
15
+ required: false
11
16
 
12
17
  permissions:
13
18
  contents: read
14
19
  pull-requests: write
15
20
 
16
21
  env:
17
- GH_TOKEN: ${{ github.token }}
22
+ HAS_AUTOMATION_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN != '' || secrets.RELEASE_PLEASE_TOKEN != '' }}
18
23
 
19
24
  jobs:
20
25
  create-draft-pr:
@@ -27,15 +32,21 @@ jobs:
27
32
  steps:
28
33
  - name: Check
29
34
  id: check-pr
35
+ env:
36
+ HEAD_REF: ${{ github.ref_name }}
37
+ GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN || secrets.RELEASE_PLEASE_TOKEN || github.token }}
30
38
  run: |
31
- BRANCH="${{ github.ref_name }}"
39
+ BRANCH="$HEAD_REF"
32
40
  EXISTING=$(gh pr list --repo "$GITHUB_REPOSITORY" --limit 1 --base staging --head "$BRANCH" --state open --json number,title 2>/dev/null | node -e 'let d=""; process.stdin.on("data", c => d += c).on("end", () => console.log(JSON.parse(d || "[]").length))')
33
41
  echo "existing=$EXISTING" >> "$GITHUB_OUTPUT"
34
42
 
35
43
  - name: Create
36
44
  if: steps.check-pr.outputs.existing == '0'
45
+ env:
46
+ HEAD_REF: ${{ github.ref_name }}
47
+ GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN || secrets.RELEASE_PLEASE_TOKEN || github.token }}
37
48
  run: |
38
- BRANCH="${{ github.ref_name }}"
49
+ BRANCH="$HEAD_REF"
39
50
  PREFIX="${BRANCH%%/*}"
40
51
  SUFFIX="${BRANCH#*/}"
41
52
  PR_TITLE=""
@@ -60,23 +71,19 @@ jobs:
60
71
  PR_TITLE="$BRANCH"
61
72
  fi
62
73
 
74
+ DRAFT_ARGS=()
75
+ if [ "$HAS_AUTOMATION_TOKEN" = true ]; then
76
+ BODY_NOTICE="This PR is ready for review and merging once required validation passes."
77
+ else
78
+ DRAFT_ARGS+=(--draft)
79
+ BODY_NOTICE="This PR was created automatically and is currently in draft mode."
80
+ echo "::notice title=Manual PR readiness required::No automation token is configured; this PR may remain draft until manually marked ready for validation and merge."
81
+ fi
82
+
63
83
  gh pr create \
64
84
  --repo "$GITHUB_REPOSITORY" \
65
85
  --base staging \
66
86
  --head "$BRANCH" \
67
- --draft \
87
+ "${DRAFT_ARGS[@]}" \
68
88
  --title "$PR_TITLE" \
69
- --body "Draft PR created automatically by CI.
70
-
71
- This PR is a draft and should not be merged until CI passes and a reviewer (or the Daily Review agent) marks it ready.
72
-
73
- **CI Status:** Pending — this workflow triggered on push to \`$BRANCH\`."
74
-
75
- - name: Mark ready
76
- if: steps.check-pr.outputs.existing == '0'
77
- run: |
78
- BRANCH="${{ github.ref_name }}"
79
- PR_NUMBER=$(gh pr list --repo "$GITHUB_REPOSITORY" --limit 1 --base staging --head "$BRANCH" --state open --json number 2>/dev/null | node -e 'let d=""; process.stdin.on("data", c => d += c).on("end", () => console.log(JSON.parse(d || "[]")[0]?.number || ""))')
80
- if [ -n "$PR_NUMBER" ]; then
81
- gh pr ready "$PR_NUMBER" --repo "$GITHUB_REPOSITORY"
82
- fi
89
+ --body "CI PR created automatically by this workflow.\n\n $BODY_NOTICE\n\n **CI Status:** Pending \u2014 this workflow triggered on push to \`$BRANCH\`."
@@ -21,4 +21,6 @@ jobs:
21
21
  uses: ./.github/workflows/draft-pr.yml
22
22
  with:
23
23
  runner: ubuntu-slim
24
- secrets: inherit
24
+ secrets:
25
+ CODE_FOUNDRY_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN }}
26
+ RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
@@ -20,7 +20,9 @@ jobs:
20
20
  token: ${{ steps.detect.outputs.token }}
21
21
  steps:
22
22
  - name: Checkout
23
- uses: actions/checkout@v7
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
24
+ with:
25
+ persist-credentials: false
24
26
  - name: Detect configuration and token
25
27
  id: detect
26
28
  env:
@@ -37,7 +39,7 @@ jobs:
37
39
  name: OpenCode Security / Scan
38
40
  needs: detect
39
41
  if: needs.detect.outputs.enabled == 'true' && needs.detect.outputs.token == 'true'
40
- uses: 0xPlayerOne/opencode-security/.github/workflows/opencode-security.yml@main
42
+ uses: 0xPlayerOne/opencode-security/.github/workflows/opencode-security.yml@b3dce823322672b285fbe99b870ea984c01826cb # main
41
43
  with:
42
44
  source_ref: ${{ github.event.pull_request.head.ref || 'main' }}
43
45
  runner: ubuntu-latest
@@ -8,13 +8,18 @@ on:
8
8
  required: false
9
9
  type: string
10
10
  default: ubuntu-slim
11
+ secrets:
12
+ CODE_FOUNDRY_TOKEN:
13
+ required: false
14
+ RELEASE_PLEASE_TOKEN:
15
+ required: false
11
16
 
12
17
  permissions:
13
18
  contents: read
14
19
  pull-requests: write
15
20
 
16
21
  env:
17
- GH_TOKEN: ${{ github.token }}
22
+ HAS_AUTOMATION_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN != '' || secrets.RELEASE_PLEASE_TOKEN != '' }}
18
23
 
19
24
  jobs:
20
25
  create-release-pr:
@@ -26,13 +31,16 @@ jobs:
26
31
  cancel-in-progress: true
27
32
  steps:
28
33
  - name: Checkout
29
- uses: actions/checkout@v6
34
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
30
35
  with:
36
+ persist-credentials: false
31
37
  fetch-depth: 0
32
38
  filter: blob:none
33
39
 
34
40
  - name: Check
35
41
  id: check-pr
42
+ env:
43
+ GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN || secrets.RELEASE_PLEASE_TOKEN || github.token }}
36
44
  run: |
37
45
  set -euo pipefail
38
46
  EXISTING=$(gh pr list \
@@ -101,15 +109,19 @@ jobs:
101
109
  RELEASE_ONLY=true
102
110
  fi
103
111
  fi
104
- echo "existing=$EXISTING" >> "$GITHUB_OUTPUT"
105
- echo "commits=$TOTAL_COMMITS" >> "$GITHUB_OUTPUT"
106
- echo "tree_equal=$TREE_EQUAL" >> "$GITHUB_OUTPUT"
107
- echo "release_only=$RELEASE_ONLY" >> "$GITHUB_OUTPUT"
112
+ {
113
+ echo "existing=$EXISTING"
114
+ echo "commits=$TOTAL_COMMITS"
115
+ echo "tree_equal=$TREE_EQUAL"
116
+ echo "release_only=$RELEASE_ONLY"
117
+ } >> "$GITHUB_OUTPUT"
108
118
 
109
119
  - name: No changes
110
120
  if: steps.check-pr.outputs.existing == '0' && (steps.check-pr.outputs.commits == '0' || steps.check-pr.outputs.tree_equal == 'true' || steps.check-pr.outputs.release_only == 'true')
121
+ env:
122
+ RELEASE_ONLY: ${{ steps.check-pr.outputs.release_only }}
111
123
  run: |
112
- if [ "${{ steps.check-pr.outputs.release_only }}" = true ]; then
124
+ if [ "$RELEASE_ONLY" = true ]; then
113
125
  echo 'Only approved release metadata differs between rebased branch histories; reconciliation from main to staging is pending, so no promotion PR is needed.'
114
126
  else
115
127
  echo 'staging is already aligned with main; no promotion PR is needed.'
@@ -117,6 +129,8 @@ jobs:
117
129
 
118
130
  - name: Create
119
131
  if: steps.check-pr.outputs.existing == '0' && steps.check-pr.outputs.commits != '0' && steps.check-pr.outputs.tree_equal != 'true' && steps.check-pr.outputs.release_only != 'true'
132
+ env:
133
+ GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN || secrets.RELEASE_PLEASE_TOKEN || github.token }}
120
134
  run: |
121
135
  DATE=$(date +%Y-%m-%d)
122
136
  BODY_FILE="$RUNNER_TEMP/release-pr.md"
@@ -128,7 +142,7 @@ jobs:
128
142
  LAST_PAGE=$(( (TOTAL_COMMITS + 99) / 100 ))
129
143
  gh api "$COMPARE_URL?per_page=100&page=$LAST_PAGE" > "$COMPARE_FILE"
130
144
  fi
131
- COMMITS=$(node -e 'const d=require(process.argv[1]); for (const c of d.commits || []) console.log(`- ${c.commit.message.split("\\n")[0]} (${c.sha.slice(0, 7)})`)' "$COMPARE_FILE")
145
+ COMMITS=$(node -e "const d=require(process.argv[1]); for (const c of d.commits || []) console.log('- ' + c.commit.message.split('\\n')[0] + ' (' + c.sha.slice(0, 7) + ')')" "$COMPARE_FILE")
132
146
  if [ -z "$COMMITS" ]; then
133
147
  COMMITS='- No commits found since main.'
134
148
  fi
@@ -157,14 +171,22 @@ jobs:
157
171
  ### Validation
158
172
 
159
173
  - Required CI, test, security, and CodeQL checks must pass before merge.
160
- - Merge using the repository's configured \`merge_strategy\` (default: **rebase**).
174
+ - Merge using the repository's configured \`merge_strategy\` (**rebase**).
175
+ - The promotion topology requires rebase; never merge a promotion PR with a merge commit.
161
176
  - After merge, Release Please opens a separate versioned release PR with the changelog.
162
177
  EOF
163
178
 
179
+ DRAFT_ARGS=()
180
+ if [ "$HAS_AUTOMATION_TOKEN" != true ]; then
181
+ DRAFT_ARGS+=(--draft)
182
+ echo "::notice title=Manual PR readiness required::Created draft promotion PR because no automation token is configured. Configure CODE_FOUNDRY_TOKEN or RELEASE_PLEASE_TOKEN, or mark this PR ready manually to trigger validation."
183
+ fi
184
+
164
185
  if ! gh pr create \
165
186
  --repo "$GITHUB_REPOSITORY" \
166
187
  --base main \
167
188
  --head staging \
189
+ "${DRAFT_ARGS[@]}" \
168
190
  --title "Promote staging -> main ($DATE)" \
169
191
  --body-file "$BODY_FILE"; then
170
192
  EXISTING_NUMBER=$(gh pr list \
@@ -3,8 +3,6 @@ name: Code Foundry
3
3
  on:
4
4
  push:
5
5
  branches: [staging]
6
- pull_request:
7
- branches: [main]
8
6
  workflow_dispatch:
9
7
 
10
8
  permissions:
@@ -13,12 +11,11 @@ permissions:
13
11
 
14
12
  jobs:
15
13
  release-pr:
16
- if: >-
17
- github.event_name == 'push' ||
18
- github.event_name == 'workflow_dispatch' ||
19
- startsWith(github.head_ref, 'release-please--branches--main')
14
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
20
15
  name: Release PR
21
16
  uses: ./.github/workflows/release-pr.yml
22
17
  with:
23
18
  runner: ubuntu-slim
24
- secrets: inherit
19
+ secrets:
20
+ CODE_FOUNDRY_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN }}
21
+ RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}