code-foundry 1.9.5 → 1.9.6
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.
- package/.github/actions/setup/action.yml +2 -1
- package/.github/code-foundry.yml +0 -2
- package/.github/workflows/ci.yml +8 -8
- package/.github/workflows/cloudflare-deploy.yml +33 -0
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/draft-control_self-ci.yml +44 -0
- package/.github/workflows/opencode-security_self-ci.yml +4 -12
- package/.github/workflows/release.yml +8 -4
- package/.github/workflows/security.yml +12 -9
- package/.github/workflows/test.yml +10 -10
- package/.github/workflows/validation_self-ci.yml +4 -9
- package/CHANGELOG.md +7 -0
- package/README.md +6 -4
- package/docs/CONFIGURATION.md +13 -11
- package/docs/RELEASES.md +2 -2
- package/docs/WORKFLOWS.md +23 -21
- package/package.json +1 -1
- package/src/commands/doctor.mjs +29 -8
- package/src/commands/sync.mjs +43 -14
- package/src/lib/github-doctor.mjs +50 -10
|
@@ -748,8 +748,9 @@ runs:
|
|
|
748
748
|
${{ runner.os }}-rust-build-v2-
|
|
749
749
|
- name: Setup
|
|
750
750
|
if: ${{ steps.profile.outputs.mise_required == 'true' }}
|
|
751
|
-
uses: jdx/mise-action@v4
|
|
751
|
+
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4
|
|
752
752
|
with:
|
|
753
|
+
version: 2026.9.2
|
|
753
754
|
cache: true
|
|
754
755
|
install_args: ${{ steps.profile.outputs.mise_tools }}
|
|
755
756
|
- name: Setup Rust components
|
package/.github/code-foundry.yml
CHANGED
|
@@ -35,10 +35,8 @@ prune_standard: false
|
|
|
35
35
|
post_release: false
|
|
36
36
|
post_release_workflow:
|
|
37
37
|
post_release_mode: auto
|
|
38
|
-
opencode_security: false
|
|
39
38
|
sync_mode: overlay
|
|
40
39
|
custom_workflows: preserve
|
|
41
|
-
staging_validation_mode: fast
|
|
42
40
|
performance: auto
|
|
43
41
|
performance_command:
|
|
44
42
|
performance_profile:
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -71,7 +71,7 @@ jobs:
|
|
|
71
71
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
72
72
|
- name: Detect
|
|
73
73
|
id: applicability
|
|
74
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci should_run format >> "$GITHUB_OUTPUT"
|
|
74
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run format >> "$GITHUB_OUTPUT"
|
|
75
75
|
- name: Setup
|
|
76
76
|
if: steps.applicability.outputs.applicable == 'true'
|
|
77
77
|
uses: ./.github/actions/setup
|
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
task: format
|
|
87
87
|
- name: Format
|
|
88
88
|
if: steps.applicability.outputs.applicable == 'true'
|
|
89
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci format
|
|
89
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci format
|
|
90
90
|
|
|
91
91
|
lint:
|
|
92
92
|
name: Lint
|
|
@@ -115,7 +115,7 @@ jobs:
|
|
|
115
115
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
116
116
|
- name: Detect
|
|
117
117
|
id: applicability
|
|
118
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci should_run lint >> "$GITHUB_OUTPUT"
|
|
118
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run lint >> "$GITHUB_OUTPUT"
|
|
119
119
|
- name: Setup
|
|
120
120
|
if: steps.applicability.outputs.applicable == 'true'
|
|
121
121
|
uses: ./.github/actions/setup
|
|
@@ -125,7 +125,7 @@ jobs:
|
|
|
125
125
|
task: lint
|
|
126
126
|
- name: Lint
|
|
127
127
|
if: steps.applicability.outputs.applicable == 'true'
|
|
128
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci lint
|
|
128
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci lint
|
|
129
129
|
|
|
130
130
|
type-check:
|
|
131
131
|
name: Type-Check
|
|
@@ -154,7 +154,7 @@ jobs:
|
|
|
154
154
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
155
155
|
- name: Detect
|
|
156
156
|
id: applicability
|
|
157
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci should_run type_check >> "$GITHUB_OUTPUT"
|
|
157
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run type_check >> "$GITHUB_OUTPUT"
|
|
158
158
|
- name: Setup
|
|
159
159
|
if: steps.applicability.outputs.applicable == 'true'
|
|
160
160
|
uses: ./.github/actions/setup
|
|
@@ -164,7 +164,7 @@ jobs:
|
|
|
164
164
|
task: type_check
|
|
165
165
|
- name: Type-Check
|
|
166
166
|
if: steps.applicability.outputs.applicable == 'true'
|
|
167
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci type_check
|
|
167
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci type_check
|
|
168
168
|
|
|
169
169
|
build:
|
|
170
170
|
name: Build
|
|
@@ -198,7 +198,7 @@ jobs:
|
|
|
198
198
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
199
199
|
- name: Detect
|
|
200
200
|
id: applicability
|
|
201
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci should_run build >> "$GITHUB_OUTPUT"
|
|
201
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run build >> "$GITHUB_OUTPUT"
|
|
202
202
|
- name: Setup
|
|
203
203
|
if: steps.applicability.outputs.applicable == 'true'
|
|
204
204
|
uses: ./.github/actions/setup
|
|
@@ -208,4 +208,4 @@ jobs:
|
|
|
208
208
|
task: build
|
|
209
209
|
- name: Build
|
|
210
210
|
if: steps.applicability.outputs.applicable == 'true'
|
|
211
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci build
|
|
211
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci build
|
|
@@ -13,6 +13,21 @@ on:
|
|
|
13
13
|
required: false
|
|
14
14
|
type: string
|
|
15
15
|
default: '.'
|
|
16
|
+
install-working-directory:
|
|
17
|
+
description: 'Directory containing bun.lock. Used only when build-script is set.'
|
|
18
|
+
required: false
|
|
19
|
+
type: string
|
|
20
|
+
default: '.'
|
|
21
|
+
build-script:
|
|
22
|
+
description: 'Optional Bun package script to run before Wrangler deploys.'
|
|
23
|
+
required: false
|
|
24
|
+
type: string
|
|
25
|
+
default: ''
|
|
26
|
+
bun-version:
|
|
27
|
+
description: 'Bun version used for dependency installation and the optional build.'
|
|
28
|
+
required: false
|
|
29
|
+
type: string
|
|
30
|
+
default: '1.4.0'
|
|
16
31
|
runner:
|
|
17
32
|
required: false
|
|
18
33
|
type: string
|
|
@@ -48,6 +63,24 @@ jobs:
|
|
|
48
63
|
with:
|
|
49
64
|
persist-credentials: false
|
|
50
65
|
|
|
66
|
+
- name: Set up Bun
|
|
67
|
+
if: inputs.build-script != ''
|
|
68
|
+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
69
|
+
with:
|
|
70
|
+
bun-version: ${{ inputs.bun-version }}
|
|
71
|
+
|
|
72
|
+
- name: Install dependencies
|
|
73
|
+
if: inputs.build-script != ''
|
|
74
|
+
working-directory: ${{ inputs.install-working-directory }}
|
|
75
|
+
run: bun install --frozen-lockfile
|
|
76
|
+
|
|
77
|
+
- name: Build Worker
|
|
78
|
+
if: inputs.build-script != ''
|
|
79
|
+
working-directory: ${{ inputs.working-directory }}
|
|
80
|
+
env:
|
|
81
|
+
BUILD_SCRIPT: ${{ inputs.build-script }}
|
|
82
|
+
run: bun run "$BUILD_SCRIPT"
|
|
83
|
+
|
|
51
84
|
- name: Deploy to Cloudflare
|
|
52
85
|
id: deploy
|
|
53
86
|
working-directory: ${{ inputs.working-directory }}
|
|
@@ -131,7 +131,7 @@ jobs:
|
|
|
131
131
|
REPO_FOUNDRY_CODE_SECURITY: ${{ steps.security.outputs.status }}
|
|
132
132
|
REPO_FOUNDRY_PRIVATE: ${{ github.event.repository.private }}
|
|
133
133
|
REPO_FOUNDRY_VISIBILITY: ${{ github.event.repository.visibility }}
|
|
134
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs codeql
|
|
134
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" codeql
|
|
135
135
|
- id: matrix
|
|
136
136
|
name: Build analysis matrix
|
|
137
137
|
env:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Code Foundry Draft Control
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, staging]
|
|
6
|
+
types:
|
|
7
|
+
- converted_to_draft
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
actions: write
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
cancel:
|
|
15
|
+
name: Draft Control / Halt CI
|
|
16
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
17
|
+
runs-on: ubuntu-slim
|
|
18
|
+
timeout-minutes: 5
|
|
19
|
+
steps:
|
|
20
|
+
- name: Cancel queued and running checks for this branch
|
|
21
|
+
env:
|
|
22
|
+
GH_TOKEN: ${{ github.token }}
|
|
23
|
+
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
24
|
+
CURRENT_RUN_ID: ${{ github.run_id }}
|
|
25
|
+
run: |
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
for status in queued in_progress; do
|
|
28
|
+
gh api "repos/$GITHUB_REPOSITORY/actions/runs" \
|
|
29
|
+
--method GET \
|
|
30
|
+
-f branch="$HEAD_BRANCH" \
|
|
31
|
+
-f event=pull_request \
|
|
32
|
+
-f status="$status" \
|
|
33
|
+
--paginate \
|
|
34
|
+
--jq '.workflow_runs[].id' |
|
|
35
|
+
while read -r run_id; do
|
|
36
|
+
if [ -n "$run_id" ] && [ "$run_id" -lt "$CURRENT_RUN_ID" ]; then
|
|
37
|
+
gh api \
|
|
38
|
+
--method POST \
|
|
39
|
+
"repos/$GITHUB_REPOSITORY/actions/runs/$run_id/cancel" \
|
|
40
|
+
--silent || true
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
done
|
|
44
|
+
echo "CI is halted while pull request #${{ github.event.pull_request.number }} remains draft."
|
|
@@ -4,11 +4,7 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
branches: [main, staging]
|
|
6
6
|
types:
|
|
7
|
-
- opened
|
|
8
|
-
- synchronize
|
|
9
|
-
- reopened
|
|
10
7
|
- ready_for_review
|
|
11
|
-
- converted_to_draft
|
|
12
8
|
workflow_dispatch:
|
|
13
9
|
|
|
14
10
|
permissions:
|
|
@@ -20,8 +16,7 @@ jobs:
|
|
|
20
16
|
if: >-
|
|
21
17
|
vars.CI_BILLING_PAUSED != 'true' &&
|
|
22
18
|
(github.event_name == 'workflow_dispatch' ||
|
|
23
|
-
(github.event.pull_request.
|
|
24
|
-
startsWith(github.event.pull_request.head.ref, 'release-please--branches--main')))
|
|
19
|
+
startsWith(github.event.pull_request.head.ref, 'release-please--branches--main'))
|
|
25
20
|
runs-on: ubuntu-slim
|
|
26
21
|
outputs:
|
|
27
22
|
enabled: ${{ steps.detect.outputs.enabled }}
|
|
@@ -39,14 +34,11 @@ jobs:
|
|
|
39
34
|
run: |
|
|
40
35
|
enabled=false
|
|
41
36
|
token=false
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# an unset value falls back to the code-foundry configuration.
|
|
37
|
+
# OpenCode Security is controlled only by the repository or
|
|
38
|
+
# organization variable. An unset value is disabled.
|
|
45
39
|
if [ "$OPENCODE_SECURITY_OVERRIDE" = "true" ]; then
|
|
46
40
|
enabled=true
|
|
47
|
-
|
|
48
|
-
enabled=false
|
|
49
|
-
elif grep -Eq '^opencode_security:\s*(true|auto)\s*$' .github/code-foundry.yml 2>/dev/null; then enabled=true; fi
|
|
41
|
+
fi
|
|
50
42
|
if [ -n "$OPENCODE_API_KEY" ]; then token=true; fi
|
|
51
43
|
echo "enabled=$enabled" >> "$GITHUB_OUTPUT"
|
|
52
44
|
echo "token=$token" >> "$GITHUB_OUTPUT"
|
|
@@ -101,9 +101,9 @@ jobs:
|
|
|
101
101
|
releaseType = fs.existsSync('package.json') ? 'node' : fs.existsSync('pyproject.toml') ? 'python' : fs.existsSync('Cargo.toml') ? 'rust' : fs.existsSync('version.txt') ? 'simple' : 'none'
|
|
102
102
|
}
|
|
103
103
|
if (!['node', 'python', 'rust', 'simple', 'none'].includes(releaseType)) throw new Error(`Unsupported release_type: ${releaseType}`)
|
|
104
|
-
const allowedStrategies = gitWorkflow === 'staging-release' ? ['rebase'] : ['
|
|
104
|
+
const allowedStrategies = gitWorkflow === 'staging-release' ? ['rebase'] : ['squash']
|
|
105
105
|
if (!allowedStrategies.includes(releaseMergeStrategy)) {
|
|
106
|
-
throw new Error(`release_merge_strategy must be "
|
|
106
|
+
throw new Error(`release_merge_strategy must be "${gitWorkflow === 'staging-release' ? 'rebase' : 'squash'}" for automated release merges; got ${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}`)
|
|
107
107
|
}
|
|
108
108
|
if (releaseType === 'none' || !fs.existsSync('package.json')) npmPublish = 'false'
|
|
109
109
|
let legacyReleaseType = ''
|
|
@@ -218,9 +218,13 @@ jobs:
|
|
|
218
218
|
for pr in "${release_prs[@]}"; do
|
|
219
219
|
PR_DRAFT=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft')
|
|
220
220
|
if [ "$AUTO_MERGE" = true ]; then
|
|
221
|
-
if [ "$PR_DRAFT" =
|
|
222
|
-
|
|
221
|
+
if [ "$PR_DRAFT" = false ]; then
|
|
222
|
+
# A Release Please PR can be updated while already ready. Reset
|
|
223
|
+
# it so ready_for_review always starts validation for the exact
|
|
224
|
+
# current release head.
|
|
225
|
+
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
223
226
|
fi
|
|
227
|
+
gh pr ready "$pr" --repo "$GITHUB_REPOSITORY"
|
|
224
228
|
else
|
|
225
229
|
if [ "$PR_DRAFT" = false ]; then
|
|
226
230
|
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
@@ -99,7 +99,7 @@ jobs:
|
|
|
99
99
|
env:
|
|
100
100
|
REPO_FOUNDRY_PRIVATE: ${{ github.event.repository.private }}
|
|
101
101
|
REPO_FOUNDRY_VISIBILITY: ${{ github.event.repository.visibility }}
|
|
102
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security profile >> "$GITHUB_OUTPUT"
|
|
102
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security profile >> "$GITHUB_OUTPUT"
|
|
103
103
|
- id: matrix
|
|
104
104
|
name: Build audit matrix
|
|
105
105
|
env:
|
|
@@ -268,21 +268,24 @@ jobs:
|
|
|
268
268
|
esac
|
|
269
269
|
fi
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
271
|
+
{
|
|
272
|
+
printf 'toolchain_mode=%s\n' "$toolchain"
|
|
273
|
+
printf 'package_manager=%s\n' "$package_manager"
|
|
274
|
+
printf 'mise_tools=%s\n' "$mise_tools"
|
|
275
|
+
} >> "$GITHUB_OUTPUT"
|
|
274
276
|
- name: Detect JavaScript
|
|
275
277
|
id: applicability-javascript
|
|
276
278
|
if: matrix.entry.language == 'javascript'
|
|
277
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security should_run javascript >> "$GITHUB_OUTPUT"
|
|
279
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security should_run javascript >> "$GITHUB_OUTPUT"
|
|
278
280
|
- name: Detect Rust
|
|
279
281
|
id: applicability-rust
|
|
280
282
|
if: matrix.entry.language == 'rust'
|
|
281
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security should_run rust >> "$GITHUB_OUTPUT"
|
|
283
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security should_run rust >> "$GITHUB_OUTPUT"
|
|
282
284
|
- name: Setup mise toolchain
|
|
283
285
|
if: steps.audit-toolchain.outputs.mise_tools != ''
|
|
284
286
|
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4
|
|
285
287
|
with:
|
|
288
|
+
version: 2026.9.2
|
|
286
289
|
cache: false
|
|
287
290
|
install_args: ${{ steps.audit-toolchain.outputs.mise_tools }}
|
|
288
291
|
- name: Setup Node
|
|
@@ -316,7 +319,7 @@ jobs:
|
|
|
316
319
|
if: >-
|
|
317
320
|
matrix.entry.language == 'javascript' &&
|
|
318
321
|
steps.applicability-javascript.outputs.applicable == 'true'
|
|
319
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security audit javascript
|
|
322
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security audit javascript
|
|
320
323
|
- name: Check Rust
|
|
321
324
|
id: system-rust
|
|
322
325
|
if: >-
|
|
@@ -349,7 +352,7 @@ jobs:
|
|
|
349
352
|
if: >-
|
|
350
353
|
matrix.entry.language == 'rust' &&
|
|
351
354
|
steps.applicability-rust.outputs.applicable == 'true'
|
|
352
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security audit rust
|
|
355
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security audit rust
|
|
353
356
|
- name: Setup Python
|
|
354
357
|
if: >-
|
|
355
358
|
matrix.entry.language == 'python' &&
|
|
@@ -375,4 +378,4 @@ jobs:
|
|
|
375
378
|
enable-cache: false
|
|
376
379
|
- name: Audit Python dependencies
|
|
377
380
|
if: matrix.entry.language == 'python'
|
|
378
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs security audit python
|
|
381
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" security audit python
|
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
87
87
|
- name: Detect
|
|
88
88
|
id: applicability
|
|
89
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci task_profile unit >> "$GITHUB_OUTPUT"
|
|
89
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile unit >> "$GITHUB_OUTPUT"
|
|
90
90
|
- name: Setup
|
|
91
91
|
if: steps.applicability.outputs.applicable == 'true'
|
|
92
92
|
uses: ./.github/actions/setup
|
|
@@ -104,7 +104,7 @@ jobs:
|
|
|
104
104
|
task-rust: ${{ steps.applicability.outputs.rust }}
|
|
105
105
|
- name: Unit
|
|
106
106
|
if: steps.applicability.outputs.applicable == 'true'
|
|
107
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci unit
|
|
107
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci unit
|
|
108
108
|
- name: Upload coverage
|
|
109
109
|
if: >-
|
|
110
110
|
always() &&
|
|
@@ -151,7 +151,7 @@ jobs:
|
|
|
151
151
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
152
152
|
- name: Detect
|
|
153
153
|
id: applicability
|
|
154
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci task_profile performance >> "$GITHUB_OUTPUT"
|
|
154
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile performance >> "$GITHUB_OUTPUT"
|
|
155
155
|
- name: Setup
|
|
156
156
|
if: steps.applicability.outputs.applicable == 'true'
|
|
157
157
|
uses: ./.github/actions/setup
|
|
@@ -170,7 +170,7 @@ jobs:
|
|
|
170
170
|
task-rust: ${{ steps.applicability.outputs.rust }}
|
|
171
171
|
- name: Performance
|
|
172
172
|
if: steps.applicability.outputs.applicable == 'true'
|
|
173
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci performance
|
|
173
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci performance
|
|
174
174
|
- name: Upload performance results
|
|
175
175
|
if: >-
|
|
176
176
|
always() &&
|
|
@@ -216,7 +216,7 @@ jobs:
|
|
|
216
216
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
217
217
|
- name: Detect
|
|
218
218
|
id: applicability
|
|
219
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci task_profile integration >> "$GITHUB_OUTPUT"
|
|
219
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile integration >> "$GITHUB_OUTPUT"
|
|
220
220
|
- name: Setup
|
|
221
221
|
if: steps.applicability.outputs.applicable == 'true'
|
|
222
222
|
uses: ./.github/actions/setup
|
|
@@ -234,7 +234,7 @@ jobs:
|
|
|
234
234
|
task-rust: ${{ steps.applicability.outputs.rust }}
|
|
235
235
|
- name: Integration
|
|
236
236
|
if: steps.applicability.outputs.applicable == 'true'
|
|
237
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci integration
|
|
237
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci integration
|
|
238
238
|
|
|
239
239
|
e2e:
|
|
240
240
|
name: E2E
|
|
@@ -265,7 +265,7 @@ jobs:
|
|
|
265
265
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
266
266
|
- name: Detect
|
|
267
267
|
id: applicability
|
|
268
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci task_profile e2e >> "$GITHUB_OUTPUT"
|
|
268
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile e2e >> "$GITHUB_OUTPUT"
|
|
269
269
|
- name: Cache browser binaries
|
|
270
270
|
if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.browser == 'true'
|
|
271
271
|
uses: ./.github/actions/cache
|
|
@@ -295,7 +295,7 @@ jobs:
|
|
|
295
295
|
task-rust: ${{ steps.applicability.outputs.rust }}
|
|
296
296
|
- name: E2E
|
|
297
297
|
if: steps.applicability.outputs.applicable == 'true'
|
|
298
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci e2e
|
|
298
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci e2e
|
|
299
299
|
|
|
300
300
|
smoke:
|
|
301
301
|
name: Smoke
|
|
@@ -326,7 +326,7 @@ jobs:
|
|
|
326
326
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
327
327
|
- name: Detect
|
|
328
328
|
id: applicability
|
|
329
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci task_profile smoke >> "$GITHUB_OUTPUT"
|
|
329
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile smoke >> "$GITHUB_OUTPUT"
|
|
330
330
|
- name: Setup
|
|
331
331
|
if: steps.applicability.outputs.applicable == 'true'
|
|
332
332
|
uses: ./.github/actions/setup
|
|
@@ -344,4 +344,4 @@ jobs:
|
|
|
344
344
|
task-rust: ${{ steps.applicability.outputs.rust }}
|
|
345
345
|
- name: Smoke
|
|
346
346
|
if: steps.applicability.outputs.applicable == 'true'
|
|
347
|
-
run: node $RUNNER_TEMP/code-foundry/src/runtime.mjs ci smoke
|
|
347
|
+
run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci smoke
|
|
@@ -4,11 +4,7 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
branches: [main, staging]
|
|
6
6
|
types:
|
|
7
|
-
- opened
|
|
8
|
-
- synchronize
|
|
9
|
-
- reopened
|
|
10
7
|
- ready_for_review
|
|
11
|
-
- converted_to_draft
|
|
12
8
|
|
|
13
9
|
# Default every caller job to no repository permissions. Individual jobs grant
|
|
14
10
|
# only the scopes their own validation path consumes.
|
|
@@ -22,10 +18,9 @@ concurrency:
|
|
|
22
18
|
jobs:
|
|
23
19
|
mode:
|
|
24
20
|
name: Mode
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
if: vars.CI_BILLING_PAUSED != 'true' && github.event.pull_request.draft == false
|
|
21
|
+
# The workflow is emitted only for ready_for_review, so draft pull requests
|
|
22
|
+
# register no skipped validation checks and allocate no validation runner.
|
|
23
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
29
24
|
runs-on: ubuntu-slim
|
|
30
25
|
timeout-minutes: 10
|
|
31
26
|
permissions:
|
|
@@ -64,7 +59,7 @@ jobs:
|
|
|
64
59
|
validation:
|
|
65
60
|
name: Validation
|
|
66
61
|
needs: mode
|
|
67
|
-
if: vars.CI_BILLING_PAUSED != 'true'
|
|
62
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
68
63
|
# Reusable workflows can only maintain or reduce the caller job's scopes.
|
|
69
64
|
# The audit tier needs security-events: write for CodeQL uploads.
|
|
70
65
|
permissions:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.9.6](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.5...v1.9.6) (2026-09-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** finalize direct fleet policy ([#513](https://github.com/0xPlayerOne/code-foundry/issues/513)) ([98c59a7](https://github.com/0xPlayerOne/code-foundry/commit/98c59a713f31d0bc5e3b9893434d89dba3257550))
|
|
9
|
+
|
|
3
10
|
## [1.9.5](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.4...v1.9.5) (2026-09-08)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -75,9 +75,10 @@ The standard workflow triggers are:
|
|
|
75
75
|
- Draft PR automation for supported feature/fix branches.
|
|
76
76
|
|
|
77
77
|
Automated feature/fix and staging-promotion pull requests open as drafts.
|
|
78
|
-
Pull-request validation
|
|
79
|
-
commits
|
|
80
|
-
|
|
78
|
+
Pull-request validation runs on the ready-for-review transition. After later
|
|
79
|
+
commits, convert the PR to draft and mark it ready again so checks attach to
|
|
80
|
+
the current head. Converting a PR to draft runs only the lightweight
|
|
81
|
+
cancellation control. Scheduled and manually dispatched audits are unaffected.
|
|
81
82
|
|
|
82
83
|
Jobs are language-aware and skip irrelevant setup inside the applicable
|
|
83
84
|
aggregate checks. TypeScript uses Oxlint, Oxfmt, and Bun's native
|
|
@@ -102,7 +103,8 @@ checks, runners, coverage, caching, and custom workflow extensions.
|
|
|
102
103
|
|
|
103
104
|
The contribution policy defaults to the `direct` workflow: feature PRs squash
|
|
104
105
|
into `main`, and Release Please version PRs use the configured
|
|
105
|
-
`release_merge_strategy` (
|
|
106
|
+
`release_merge_strategy` (squash for direct repositories; rebase for the
|
|
107
|
+
optional staging-release topology).
|
|
106
108
|
Repositories with a preview/staging environment opt into
|
|
107
109
|
`git_workflow: staging-release`, where feature PRs squash into `staging`, the
|
|
108
110
|
promotion PR rebases into `main` (`merge_strategy: rebase`), and Release Please
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -35,7 +35,7 @@ repository manifests and source
|
|
|
35
35
|
| `languages` | detected list | TypeScript, Rust, Python, Solidity |
|
|
36
36
|
| `package_manager` | `bun`, `pnpm`, `yarn`, `npm`, `none` | JavaScript setup |
|
|
37
37
|
| `toolchain` | `auto`, `native`, `mise` | Environment setup policy; defaults to `auto` |
|
|
38
|
-
| `staging_validation_mode` | `fast`, `audit` |
|
|
38
|
+
| `staging_validation_mode` | `fast`, `audit` | Staging-release-only validation tier; omitted from direct repositories |
|
|
39
39
|
| `performance` | `auto`, `true`, `false` | Run a deterministic performance task when a supported entrypoint exists; defaults to `auto` |
|
|
40
40
|
| `performance_command` | JSON argv array or array of argv arrays | One or more ordered commands for non-package harnesses; package scripts take precedence |
|
|
41
41
|
| `performance_profile` | empty, `node-package` | Optional shared package import, memory, archive, and dependency budget harness |
|
|
@@ -53,8 +53,8 @@ repository manifests and source
|
|
|
53
53
|
| `npm_publish` | `true` or `false` | Opt into npm publication |
|
|
54
54
|
| `license` | `gpl-3.0-or-later`, `agpl-3.0-or-later`, `apache-2.0`, `mit`, `preserve`, `none` | License policy; new repositories default to GPLv3 |
|
|
55
55
|
| `git_workflow` | `direct` (default), `staging-release` | Branch/release model; `direct` opens feature branches into `main`, `staging-release` promotes `staging` into `main` |
|
|
56
|
-
| `merge_strategy` | `
|
|
57
|
-
| `release_merge_strategy` | `
|
|
56
|
+
| `merge_strategy` | `squash` (direct), `rebase` (staging-release) | Feature/promotion merge policy; direct repositories require squash |
|
|
57
|
+
| `release_merge_strategy` | `squash` (direct), `rebase` (staging-release) | Merge method for Release Please version PRs into `main`; release automation fails closed on anything else |
|
|
58
58
|
| `runner` fields | GitHub runner names | Per-workflow runner policy, including `performance_runner` |
|
|
59
59
|
|
|
60
60
|
Supported features are `ci`, `codeql`, `security`, `test`, `draft-pr`,
|
|
@@ -113,14 +113,13 @@ checks should remain separate from this deterministic validation task.
|
|
|
113
113
|
|
|
114
114
|
## OpenCode Security opt-in and opt-out
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
repository without a configuration change or a new secret:
|
|
116
|
+
The generated OpenCode caller ships in every repository. The
|
|
117
|
+
`OPENCODE_SECURITY` repository or organization variable is its only enablement
|
|
118
|
+
control, so a scan can be toggled without a code change:
|
|
120
119
|
|
|
121
120
|
- `OPENCODE_SECURITY: true` opts the repository in.
|
|
122
121
|
- `OPENCODE_SECURITY: false` opts the repository out.
|
|
123
|
-
- unset
|
|
122
|
+
- unset is disabled.
|
|
124
123
|
|
|
125
124
|
The scan only runs when it is enabled and the `OPENCODE_API_KEY` secret is
|
|
126
125
|
present.
|
|
@@ -131,8 +130,8 @@ present.
|
|
|
131
130
|
|
|
132
131
|
- `direct` (default): feature branches open pull requests directly into
|
|
133
132
|
`main`. Validation and security scans run on every PR. No `staging` branch
|
|
134
|
-
exists, no promotion caller is generated, and `merge_strategy`
|
|
135
|
-
|
|
133
|
+
exists, no promotion caller is generated, and `merge_strategy` must be
|
|
134
|
+
`squash`. Release Please version PRs squash into `main`
|
|
136
135
|
(`release_merge_strategy: squash`). Dependabot updates target `main`. This is
|
|
137
136
|
the right choice when a repository has no preview or staging environment.
|
|
138
137
|
- `staging-release` (opt-in): feature branches squash into `staging`, a
|
|
@@ -181,4 +180,7 @@ Vercel's integration) by adding a small caller for the runtime's reusable
|
|
|
181
180
|
for pull-request previews and `wrangler deploy` for production, records a
|
|
182
181
|
GitHub deployment plus status with the workers.dev URL, and respects
|
|
183
182
|
`CI_BILLING_PAUSED`. It requires the `CLOUDFLARE_API_TOKEN` and
|
|
184
|
-
`CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository.
|
|
183
|
+
`CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository. Bun consumers may
|
|
184
|
+
also pass `build-script`, `install-working-directory`, and `bun-version`; the
|
|
185
|
+
runtime installs the frozen lockfile and builds the Worker before invoking
|
|
186
|
+
Wrangler.
|
package/docs/RELEASES.md
CHANGED
|
@@ -27,7 +27,7 @@ rebase`), and Release Please version PRs merge with **rebase**
|
|
|
27
27
|
branches squash straight into `main` and Release Please version PRs squash into
|
|
28
28
|
`main` (`release_merge_strategy: squash`). `merge_strategy` is not enforced. Release automation never defaults
|
|
29
29
|
to a merge method and never merges with `--admin`: `staging-release` accepts
|
|
30
|
-
only rebase for release PRs, while `direct`
|
|
30
|
+
only rebase for release PRs, while `direct` requires squash.
|
|
31
31
|
|
|
32
32
|
The release workflow opens or updates a versioned PR after changes reach
|
|
33
33
|
`main`. Merging that PR updates the changelog, creates the Git tag and GitHub
|
|
@@ -54,7 +54,7 @@ selected, `merge_strategy` applies to promotion PRs (`staging` into `main`)
|
|
|
54
54
|
and `release_merge_strategy` to Release Please version PRs; feature PRs into
|
|
55
55
|
`staging` use squash merges. `code-foundry doctor`, `code-foundry sync`, and
|
|
56
56
|
the release workflow reject any non-`rebase` `merge_strategy` or release
|
|
57
|
-
strategy in `staging-release`; `direct`
|
|
57
|
+
strategy in `staging-release`; `direct` requires `squash` for
|
|
58
58
|
release PRs and never falls back to `merge`. Both keep `main` fully linear,
|
|
59
59
|
which is what makes the post-release reconciliation possible. The final
|
|
60
60
|
branch trees are inspected before mutation; validated main-only changes are
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -12,10 +12,12 @@ pull_request:
|
|
|
12
12
|
# direct topology: branches: [main]
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The generated caller
|
|
16
|
-
pull
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
The generated validation caller listens only for `ready_for_review`, so draft
|
|
16
|
+
pull requests register no validation checks and allocate no validation runner.
|
|
17
|
+
After any later update, convert the pull request to draft and mark it ready
|
|
18
|
+
again so required checks attach to the current head. A separate lightweight
|
|
19
|
+
draft-control caller listens for `converted_to_draft` and cancels queued or
|
|
20
|
+
running pull-request workflows without creating skipped validation jobs.
|
|
19
21
|
|
|
20
22
|
The separate `validation-audit.yml` caller is pinned to the configured released
|
|
21
23
|
runtime and handles scheduled and manual audits:
|
|
@@ -39,7 +41,7 @@ opens those PRs as drafts. Promotion automation listens to `staging` pushes
|
|
|
39
41
|
(staging-release only) and also always opens its PR as a draft. Release
|
|
40
42
|
automation listens to `main` pushes.
|
|
41
43
|
Custom deployment, indexing, search, Slither, or other workflows are
|
|
42
|
-
repository-owned extensions and should
|
|
44
|
+
repository-owned extensions and should use the same ready-transition policy.
|
|
43
45
|
|
|
44
46
|
## Billing pause
|
|
45
47
|
|
|
@@ -83,9 +85,9 @@ throughout the bounded release flow.
|
|
|
83
85
|
Custom workflows are repository-owned and are not rewritten by sync. Add
|
|
84
86
|
`if: vars.CI_BILLING_PAUSED != 'true'` to each custom root job that should
|
|
85
87
|
honor the shared billing pause. The optional OpenCode Security scan honors a
|
|
86
|
-
second toggle: the `OPENCODE_SECURITY` repository variable
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
second toggle: the `OPENCODE_SECURITY` repository or organization variable
|
|
89
|
+
(`true`/`false`) is its only enablement control, so individual repositories can
|
|
90
|
+
opt in or out without a code change.
|
|
89
91
|
|
|
90
92
|
## Standard workflow responsibilities
|
|
91
93
|
|
|
@@ -114,20 +116,20 @@ succeeds.
|
|
|
114
116
|
|
|
115
117
|
The merge audit pins one merge method per transition. `code-foundry doctor`
|
|
116
118
|
and `code-foundry sync` validate release strategy against the repository
|
|
117
|
-
topology.
|
|
118
|
-
|
|
119
|
-
strategy and never falls back to `merge`.
|
|
119
|
+
topology. Direct feature and release PRs require `squash`; staging-release
|
|
120
|
+
promotion and release PRs require `rebase`. The release workflow fails closed
|
|
121
|
+
on any other strategy and never falls back to `merge`.
|
|
120
122
|
|
|
121
|
-
| Transition | Merge method
|
|
122
|
-
| ---------------------------------------------------------- |
|
|
123
|
-
| Feature/fix PR into `main` (direct topology) | Squash
|
|
124
|
-
| Feature/fix PR into `staging` (staging-release topology) | Squash
|
|
125
|
-
| `staging` → `main` promotion PR (staging-release topology) | Rebase (`merge_strategy: rebase`)
|
|
126
|
-
| Release Please version PR into `main` |
|
|
123
|
+
| Transition | Merge method | Enforcement |
|
|
124
|
+
| ---------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| Feature/fix PR into `main` (direct topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
|
|
126
|
+
| Feature/fix PR into `staging` (staging-release topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
|
|
127
|
+
| `staging` → `main` promotion PR (staging-release topology) | Rebase (`merge_strategy: rebase`) | Code Foundry creates a one-commit head with `main` as its parent and the exact validated `staging` tree; `merge_strategy` must be `rebase`, and merge commits are rejected |
|
|
128
|
+
| Release Please version PR into `main` | Squash (direct) or rebase (staging-release) | Release automation fails closed on unsupported topology/strategy; never defaults to `merge`, never uses `--admin` |
|
|
127
129
|
|
|
128
130
|
The promotion rows above apply only to `staging-release`; `direct`
|
|
129
|
-
repositories never generate a promotion caller and `merge_strategy
|
|
130
|
-
|
|
131
|
+
repositories never generate a promotion caller and require `merge_strategy:
|
|
132
|
+
squash` for feature pull requests.
|
|
131
133
|
|
|
132
134
|
Release auto-merge waits for required checks and then polls `mergeStateStatus`
|
|
133
135
|
until it is `CLEAN`, or `UNSTABLE` with `mergeable` `MERGEABLE`, before
|
|
@@ -139,7 +141,7 @@ merge policy-blocked. The mergeability poll is bounded and fails closed on
|
|
|
139
141
|
conflicts or timeout; releases without an automation token remain manual.
|
|
140
142
|
|
|
141
143
|
Keeping `main` linear — rebase promotions and release PRs in the
|
|
142
|
-
`staging-release` topology, or a single-commit
|
|
144
|
+
`staging-release` topology, or a single-commit squash release PR in the
|
|
143
145
|
`direct` topology — is what lets the post-release reconciliation fast-forward
|
|
144
146
|
or replay `staging` safely. `direct` repositories have no reconciliation step:
|
|
145
147
|
releases merge straight into `main` with the configured
|
|
@@ -147,7 +149,7 @@ releases merge straight into `main` with the configured
|
|
|
147
149
|
|
|
148
150
|
Protect `main` with the aggregate `Validation / Gate`. Require squash for
|
|
149
151
|
feature/fix pull requests and permit the configured Release Please method:
|
|
150
|
-
rebase in `staging-release`, or
|
|
152
|
+
rebase in `staging-release`, or squash in `direct`. In the
|
|
151
153
|
`staging-release` topology, protect `staging` the same way with a single GitHub
|
|
152
154
|
Actions integration path. That path uses the
|
|
153
155
|
GitHub Actions integration token by default, and optionally an SSH deploy key
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.6",
|
|
4
4
|
"description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
|
|
5
5
|
"homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
|
|
6
6
|
"bugs": {
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -116,30 +116,45 @@ export function doctor(root, options = {}) {
|
|
|
116
116
|
error(`unsupported git_workflow: ${workflow}; use direct or staging-release`)
|
|
117
117
|
}
|
|
118
118
|
const mergeStrategy = config.merge_strategy ?? 'rebase'
|
|
119
|
+
if (workflow === 'direct' && mergeStrategy !== 'squash') {
|
|
120
|
+
error(
|
|
121
|
+
`merge_strategy must be "squash" for direct feature pull requests; got "${mergeStrategy}".`
|
|
122
|
+
)
|
|
123
|
+
}
|
|
119
124
|
if (workflow === 'staging-release' && mergeStrategy !== 'rebase') {
|
|
120
125
|
error(
|
|
121
126
|
`merge_strategy must be "rebase" for the staging-release promotion topology; got "${mergeStrategy}".`
|
|
122
127
|
)
|
|
123
128
|
}
|
|
124
129
|
const releaseMergeStrategy = config.release_merge_strategy ?? ''
|
|
125
|
-
const allowedReleaseStrategies =
|
|
126
|
-
workflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
|
|
130
|
+
const allowedReleaseStrategies = workflow === 'staging-release' ? ['rebase'] : ['squash']
|
|
127
131
|
if (
|
|
128
132
|
includesValue(features, 'release') &&
|
|
129
133
|
!allowedReleaseStrategies.includes(releaseMergeStrategy)
|
|
130
134
|
) {
|
|
131
135
|
error(
|
|
132
|
-
`release_merge_strategy must be "
|
|
136
|
+
`release_merge_strategy must be "${workflow === 'staging-release' ? 'rebase' : 'squash'}" for automated release merges; got "${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}".`
|
|
133
137
|
)
|
|
134
138
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if ('opencode_security' in config)
|
|
140
|
+
error('opencode_security is obsolete; use the OPENCODE_SECURITY repository variable.')
|
|
141
|
+
if (workflow === 'direct' && 'staging_validation_mode' in config)
|
|
142
|
+
error(
|
|
143
|
+
'staging_validation_mode is not valid in a direct-topology repository; run sync to prune it.'
|
|
144
|
+
)
|
|
140
145
|
const validationEnabled =
|
|
141
146
|
includesValue(features, 'validation') ||
|
|
142
147
|
['ci', 'test', 'security', 'codeql'].some((legacy) => includesValue(features, legacy))
|
|
148
|
+
for (const name of ['validation', 'draft-control', 'draft-pr', 'release-pr', 'release']) {
|
|
149
|
+
if (name === 'release-pr' && workflow !== 'staging-release') continue
|
|
150
|
+
const enabled = name === 'draft-control' ? validationEnabled : includesValue(features, name)
|
|
151
|
+
const candidates = [
|
|
152
|
+
join(target, `.github/workflows/${name}.yml`),
|
|
153
|
+
join(target, `.github/workflows/${name}_self-ci.yml`),
|
|
154
|
+
]
|
|
155
|
+
if (enabled && !candidates.some((file) => existsSync(file)))
|
|
156
|
+
error(`missing enabled workflow: ${name}.yml`)
|
|
157
|
+
}
|
|
143
158
|
const validationCaller = ['validation.yml', 'validation_self-ci.yml']
|
|
144
159
|
.map((file) => join(target, `.github/workflows/${file}`))
|
|
145
160
|
.find((file) => existsSync(file) && /pull_request:/.test(readFileSync(file, 'utf8')))
|
|
@@ -152,6 +167,12 @@ export function doctor(root, options = {}) {
|
|
|
152
167
|
'validation caller is missing the Validation job; the Validation / Gate aggregate check cannot form.'
|
|
153
168
|
)
|
|
154
169
|
}
|
|
170
|
+
if (!/types:\n\s+- ready_for_review/.test(caller)) {
|
|
171
|
+
error('validation caller must wait for the ready_for_review transition.')
|
|
172
|
+
}
|
|
173
|
+
if (/\s+- (?:opened|synchronize|reopened|converted_to_draft)\s*$/.test(caller)) {
|
|
174
|
+
error('validation caller registers draft-time checks; run code-foundry sync.')
|
|
175
|
+
}
|
|
155
176
|
if (
|
|
156
177
|
!/uses:\s+(?:\.\/\.github\/workflows\/validation(?:-no-codeql)?\.yml|\S+\/\.github\/workflows\/validation(?:-no-codeql)?\.yml@)/.test(
|
|
157
178
|
caller
|
package/src/commands/sync.mjs
CHANGED
|
@@ -43,6 +43,7 @@ const standardFiles = [
|
|
|
43
43
|
'.github/ISSUE_TEMPLATE/feature_request.yml',
|
|
44
44
|
'.github/workflows/validation.yml',
|
|
45
45
|
'.github/workflows/validation-audit.yml',
|
|
46
|
+
'.github/workflows/draft-control.yml',
|
|
46
47
|
'.github/workflows/draft-pr.yml',
|
|
47
48
|
'.github/workflows/release-pr.yml',
|
|
48
49
|
'.github/workflows/release.yml',
|
|
@@ -117,6 +118,15 @@ export function syncRepository(options) {
|
|
|
117
118
|
throw new Error('Missing .github/code-foundry.yml; run init first.')
|
|
118
119
|
const defaults = createDefaultConfig(target, source, existingConfig.git_workflow)
|
|
119
120
|
let config = { ...defaults, ...existingConfig }
|
|
121
|
+
const workflow = gitWorkflow(config.git_workflow)
|
|
122
|
+
if (!['direct', 'staging-release'].includes(workflow)) {
|
|
123
|
+
throw new Error(`Unsupported git_workflow: ${workflow}; use direct or staging-release.`)
|
|
124
|
+
}
|
|
125
|
+
const obsoleteConfigKeys = [
|
|
126
|
+
'opencode_security',
|
|
127
|
+
...(workflow === 'direct' ? ['staging_validation_mode'] : []),
|
|
128
|
+
]
|
|
129
|
+
for (const key of obsoleteConfigKeys) delete config[key]
|
|
120
130
|
// Resolve and validate the license policy before any sync writes occur
|
|
121
131
|
// (including config/default additions) so an unsupported policy fails
|
|
122
132
|
// fast without leaving partially generated files behind.
|
|
@@ -133,10 +143,11 @@ export function syncRepository(options) {
|
|
|
133
143
|
writeOrReport(configPath, renderConfig(config), dryRun)
|
|
134
144
|
} else {
|
|
135
145
|
const missing = Object.keys(defaults).filter((key) => !(key in existingConfig))
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
const original = readFileSync(configPath, 'utf8')
|
|
147
|
+
const normalized = removeConfigKeys(original, obsoleteConfigKeys).trimEnd()
|
|
148
|
+
if (missing.length || normalized !== original.trimEnd()) {
|
|
138
149
|
const additions = missing.map((key) => renderConfigLine(key, defaults[key])).join('\n')
|
|
139
|
-
writeOrReport(configPath, `${
|
|
150
|
+
writeOrReport(configPath, `${normalized}${additions ? `\n${additions}` : ''}\n`, dryRun)
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -155,17 +166,18 @@ export function syncRepository(options) {
|
|
|
155
166
|
if (!['auto', 'native', 'mise'].includes(toolchain)) {
|
|
156
167
|
throw new Error(`Unsupported toolchain: ${toolchain}; use auto, native, or mise.`)
|
|
157
168
|
}
|
|
158
|
-
const workflow = gitWorkflow(config.git_workflow)
|
|
159
|
-
if (!['direct', 'staging-release'].includes(workflow)) {
|
|
160
|
-
throw new Error(`Unsupported git_workflow: ${workflow}; use direct or staging-release.`)
|
|
161
|
-
}
|
|
162
169
|
const stagingValidationMode = configured(config.staging_validation_mode, 'fast')
|
|
163
|
-
if (!['fast', 'audit'].includes(stagingValidationMode)) {
|
|
170
|
+
if (workflow === 'staging-release' && !['fast', 'audit'].includes(stagingValidationMode)) {
|
|
164
171
|
throw new Error(
|
|
165
172
|
`Unsupported staging_validation_mode: ${stagingValidationMode}; use fast or audit.`
|
|
166
173
|
)
|
|
167
174
|
}
|
|
168
175
|
const mergeStrategy = configured(config.merge_strategy, 'rebase')
|
|
176
|
+
if (workflow === 'direct' && mergeStrategy !== 'squash') {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Unsupported merge_strategy: ${mergeStrategy}; the direct topology requires squash for feature pull requests.`
|
|
179
|
+
)
|
|
180
|
+
}
|
|
169
181
|
if (workflow === 'staging-release' && mergeStrategy !== 'rebase') {
|
|
170
182
|
throw new Error(
|
|
171
183
|
`Unsupported merge_strategy: ${mergeStrategy}; the staging-release topology requires rebase for staging to main promotions.`
|
|
@@ -177,11 +189,10 @@ export function syncRepository(options) {
|
|
|
177
189
|
// release commits; the direct topology has no reconciliation step, so it
|
|
178
190
|
// may also squash Release Please version PRs (a single-commit release PR
|
|
179
191
|
// squashes to the identical tree, and release-please recommends squash).
|
|
180
|
-
const allowedReleaseStrategies =
|
|
181
|
-
workflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
|
|
192
|
+
const allowedReleaseStrategies = workflow === 'staging-release' ? ['rebase'] : ['squash']
|
|
182
193
|
if (!allowedReleaseStrategies.includes(releaseMergeStrategy)) {
|
|
183
194
|
throw new Error(
|
|
184
|
-
`Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires
|
|
195
|
+
`Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires ${workflow === 'staging-release' ? 'rebase' : 'squash'} for Release Please version pull requests and never defaults to merge.`
|
|
185
196
|
)
|
|
186
197
|
}
|
|
187
198
|
}
|
|
@@ -459,6 +470,7 @@ function shouldInclude(file, languages, features, config) {
|
|
|
459
470
|
// the configuration or the variable enables it and the API key exists.
|
|
460
471
|
if (file === '.github/workflows/opencode-security.yml') return true
|
|
461
472
|
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
473
|
+
if (workflow === 'draft-control') return true
|
|
462
474
|
// The staging promotion caller only exists in the staging-release topology;
|
|
463
475
|
// direct repositories open feature branches into main and need no promotion.
|
|
464
476
|
if (workflow === 'release-pr' && !isStagingRelease(config.git_workflow)) return false
|
|
@@ -695,7 +707,7 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
695
707
|
],
|
|
696
708
|
[
|
|
697
709
|
'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 **rebases** into `main` (`release_merge_strategy: rebase`). 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.',
|
|
698
|
-
'The Git workflow is `direct`: topic branches **squash** directly into `main`, 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 release merge strategy.
|
|
710
|
+
'The Git workflow is `direct`: topic branches **squash** directly into `main`, 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 release merge strategy. This repository has one protected integration and release branch: `main`.',
|
|
699
711
|
],
|
|
700
712
|
[
|
|
701
713
|
'git switch staging\ngit pull --ff-only origin staging',
|
|
@@ -723,6 +735,10 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
723
735
|
'| Change | Target | Merge method | Merge gate |\n| ---------------------------- | --------- | ----------------------------------------------- | --------------------------------------------------------- |\n| Working branch | `staging` | Squash | All applicable required checks pass |\n| `staging` → `main` promotion | `main` | Rebase (`merge_strategy`) | Current staging checks, release review, and rollout notes |\n| Release Please version PR | `main` | Rebase (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |\n',
|
|
724
736
|
'| Change | Target | Merge method | Merge gate |\n|----------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Working branch | `main` | Squash | All applicable required checks pass |\n| Release Please version PR | `main` | Squash (`release_merge_strategy`) | Validation gate and release policy pass |',
|
|
725
737
|
],
|
|
738
|
+
[
|
|
739
|
+
'Draft pull requests do not start runner-heavy validation. Marking a pull request ready for review starts the applicable validation tier; converting it back to draft cancels in-flight validation, and no replacement starts until it is ready again.',
|
|
740
|
+
'Draft pull requests do not start validation. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.',
|
|
741
|
+
],
|
|
726
742
|
['1. Create a focused branch from `staging`.', '1. Create a focused branch from `main`.'],
|
|
727
743
|
],
|
|
728
744
|
'.github/SECURITY.md': [
|
|
@@ -1149,7 +1165,7 @@ function createDefaultConfig(root, source, configuredWorkflow) {
|
|
|
1149
1165
|
runtime_ref: `v${readPackageVersion(source)}`,
|
|
1150
1166
|
...runners,
|
|
1151
1167
|
toolchain: 'auto',
|
|
1152
|
-
staging_validation_mode: 'fast',
|
|
1168
|
+
...(stagingRelease ? { staging_validation_mode: 'fast' } : {}),
|
|
1153
1169
|
performance: 'auto',
|
|
1154
1170
|
performance_command: '',
|
|
1155
1171
|
performance_profile: '',
|
|
@@ -1164,7 +1180,6 @@ function createDefaultConfig(root, source, configuredWorkflow) {
|
|
|
1164
1180
|
post_release: 'false',
|
|
1165
1181
|
post_release_workflow: '',
|
|
1166
1182
|
post_release_mode: 'auto',
|
|
1167
|
-
opencode_security: 'false',
|
|
1168
1183
|
opencode_security_model: '',
|
|
1169
1184
|
sync_mode: 'overlay',
|
|
1170
1185
|
custom_workflows: 'preserve',
|
|
@@ -1181,6 +1196,20 @@ function renderConfig(config) {
|
|
|
1181
1196
|
.map(([key, value]) => renderConfigLine(key, value))
|
|
1182
1197
|
.join('\n')}\n`
|
|
1183
1198
|
}
|
|
1199
|
+
|
|
1200
|
+
/** @param {string} content @param {string[]} keys */
|
|
1201
|
+
function removeConfigKeys(content, keys) {
|
|
1202
|
+
if (!keys.length) return content
|
|
1203
|
+
const rejected = new Set(keys)
|
|
1204
|
+
return content
|
|
1205
|
+
.split(/\r?\n/)
|
|
1206
|
+
.filter((line) => {
|
|
1207
|
+
const key = line.match(/^([A-Za-z0-9_-]+):/)?.[1]
|
|
1208
|
+
return !key || !rejected.has(key)
|
|
1209
|
+
})
|
|
1210
|
+
.join('\n')
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1184
1213
|
/** @param {string} key @param {string} value */
|
|
1185
1214
|
function renderConfigLine(key, value) {
|
|
1186
1215
|
if (value === '') return `${key}:`
|
|
@@ -71,7 +71,42 @@ export function doctorGithub(root) {
|
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const config = readConfig(root)
|
|
74
|
+
const config = readConfig(join(root, '.github/code-foundry.yml'))
|
|
75
|
+
const repositorySettings = ghJson(['api', `repos/${repository}`]) ?? {}
|
|
76
|
+
const configuredWorkflow = config.git_workflow ?? 'direct'
|
|
77
|
+
details.mergeMethods = {
|
|
78
|
+
squash: repositorySettings.allow_squash_merge,
|
|
79
|
+
rebase: repositorySettings.allow_rebase_merge,
|
|
80
|
+
merge: repositorySettings.allow_merge_commit,
|
|
81
|
+
autoMerge: repositorySettings.allow_auto_merge,
|
|
82
|
+
}
|
|
83
|
+
if (configuredWorkflow === 'direct') {
|
|
84
|
+
if (repositorySettings.allow_squash_merge !== true)
|
|
85
|
+
errors.push('direct topology requires squash merging to be enabled.')
|
|
86
|
+
if (
|
|
87
|
+
repositorySettings.allow_rebase_merge === true ||
|
|
88
|
+
repositorySettings.allow_merge_commit === true
|
|
89
|
+
)
|
|
90
|
+
errors.push('direct topology must disable rebase and merge-commit methods.')
|
|
91
|
+
if (config.release_type !== 'none' && repositorySettings.allow_auto_merge !== true)
|
|
92
|
+
errors.push('automated releases require repository auto-merge to be enabled.')
|
|
93
|
+
details.stagingBranchExists = Boolean(
|
|
94
|
+
ghJson(['api', `repos/${repository}/branches/staging`])?.name
|
|
95
|
+
)
|
|
96
|
+
if (details.stagingBranchExists)
|
|
97
|
+
errors.push('direct topology still has a staging branch; remove the obsolete branch.')
|
|
98
|
+
for (const ruleset of mainRulesets) {
|
|
99
|
+
for (const rule of ruleset.rules ?? []) {
|
|
100
|
+
if (rule?.type !== 'pull_request') continue
|
|
101
|
+
const methods = rule.parameters?.allowed_merge_methods ?? []
|
|
102
|
+
if (methods.length !== 1 || methods[0] !== 'squash') {
|
|
103
|
+
errors.push(
|
|
104
|
+
`main ruleset ${ruleset.name ?? ruleset.id} must allow only squash merges; got ${methods.join(', ') || '(none)'}.`
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
75
110
|
if (
|
|
76
111
|
['workflow-dispatch', 'dispatch'].includes(config.post_release_mode) &&
|
|
77
112
|
config.post_release !== 'false' &&
|
|
@@ -80,11 +115,19 @@ export function doctorGithub(root) {
|
|
|
80
115
|
) {
|
|
81
116
|
errors.push('post-release workflow-dispatch mode requires CODE_FOUNDRY_TOKEN to be present.')
|
|
82
117
|
}
|
|
118
|
+
const repositoryVariables =
|
|
119
|
+
ghJson(['variable', 'list', '--repo', repository, '--json', 'name,value']) ?? []
|
|
120
|
+
const opencodeSecurityVariable = Array.isArray(repositoryVariables)
|
|
121
|
+
? repositoryVariables.find(
|
|
122
|
+
/** @param {{ name?: string }} variable */ (variable) =>
|
|
123
|
+
variable.name === 'OPENCODE_SECURITY'
|
|
124
|
+
)
|
|
125
|
+
: undefined
|
|
83
126
|
const credentialChecks = {
|
|
84
127
|
npmTokenPresent: secretNames.includes('NPM_TOKEN'),
|
|
85
128
|
turboTokenPresent: secretNames.includes('TURBO_TOKEN'),
|
|
86
129
|
turboTeamPresent: Boolean(
|
|
87
|
-
|
|
130
|
+
repositoryVariables.some(
|
|
88
131
|
/** @param {{ name?: string }} variable */ (variable) => variable.name === 'TURBO_TEAM'
|
|
89
132
|
)
|
|
90
133
|
),
|
|
@@ -103,18 +146,15 @@ export function doctorGithub(root) {
|
|
|
103
146
|
'turbo_remote is enabled but TURBO_TOKEN and/or TURBO_TEAM is not configured; remote caching will be skipped.'
|
|
104
147
|
)
|
|
105
148
|
if (
|
|
106
|
-
|
|
149
|
+
String(opencodeSecurityVariable?.value ?? 'false').toLowerCase() === 'true' &&
|
|
107
150
|
!credentialChecks.opencodeApiKeyPresent
|
|
108
151
|
)
|
|
109
152
|
warnings.push(
|
|
110
|
-
'
|
|
153
|
+
'OPENCODE_SECURITY is true but OPENCODE_API_KEY is not configured; the optional scan will be skipped.'
|
|
111
154
|
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
details.credentials.opencodeSecurityVariable =
|
|
115
|
-
ghJson(['variable', 'list', '--repo', repository, '--json', 'name'])?.some(
|
|
116
|
-
/** @param {{ name?: string }} variable */ (variable) => variable.name === 'OPENCODE_SECURITY'
|
|
117
|
-
) ?? false
|
|
155
|
+
details.credentials.opencodeSecurityEnabled =
|
|
156
|
+
String(opencodeSecurityVariable?.value ?? 'false').toLowerCase() === 'true'
|
|
157
|
+
details.credentials.opencodeSecurityVariable = Boolean(opencodeSecurityVariable)
|
|
118
158
|
|
|
119
159
|
const prs = ghJson([
|
|
120
160
|
'pr',
|