code-foundry 1.28.14 → 1.29.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.
@@ -89,6 +89,15 @@ runs:
89
89
  if [ -f .github/code-foundry.yml ]; then
90
90
  configured_toolchain="$(awk -F': ' '/^toolchain:/ {print $2; exit}' .github/code-foundry.yml)"
91
91
  [ -n "$configured_toolchain" ] && toolchain="$configured_toolchain"
92
+ for flag in rust_sccache rust_nextest; do
93
+ if grep -Eq "^${flag}:\\s*[\"']?true[\"']?\\s*$" .github/code-foundry.yml; then
94
+ printf '%s=true\n' "$flag" >> "$GITHUB_OUTPUT"
95
+ else
96
+ printf '%s=false\n' "$flag" >> "$GITHUB_OUTPUT"
97
+ fi
98
+ done
99
+ else
100
+ printf 'rust_sccache=false\nrust_nextest=false\n' >> "$GITHUB_OUTPUT"
92
101
  fi
93
102
  if [ "$toolchain" = auto ] && [ -f .mise.toml ]; then
94
103
  toolchain=mise
@@ -746,6 +755,36 @@ runs:
746
755
  # primary saves but reuse a prior workflow's dependency artifacts.
747
756
  restore-keys: |
748
757
  ${{ runner.os }}-rust-build-v2-
758
+ # sccache is opt-in (`rust_sccache: true` in code-foundry.yml): it caches
759
+ # per-compilation objects keyed by content, so dependency artifacts
760
+ # survive Cargo.toml/Cargo.lock churn that busts the target/ cache above.
761
+ - name: Cache sccache artifacts
762
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && steps.profile.outputs.rust_sccache == 'true'
763
+ uses: ./.github/actions/cache
764
+ with:
765
+ save: ${{ inputs.cache-save }}
766
+ path: ~/.cache/sccache
767
+ key: ${{ runner.os }}-rust-sccache-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('rust-toolchain.toml', 'rust-toolchain', '.cargo/config*', '.mise.toml', 'mise.lock') }}
768
+ restore-keys: |
769
+ ${{ runner.os }}-rust-sccache-
770
+ - name: Install sccache
771
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && steps.profile.outputs.rust_sccache == 'true'
772
+ uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2
773
+ with:
774
+ tool: sccache
775
+ - name: Wrap rustc with sccache
776
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && steps.profile.outputs.rust_sccache == 'true'
777
+ shell: bash
778
+ run: |
779
+ echo 'RUSTC_WRAPPER=sccache' >> "$GITHUB_ENV"
780
+ echo "SCCACHE_DIR=$HOME/.cache/sccache" >> "$GITHUB_ENV"
781
+ # cargo-nextest is opt-in (`rust_nextest: true`): the runtime uses
782
+ # `cargo nextest run` when the binary is present.
783
+ - name: Install cargo-nextest
784
+ if: inputs.install == 'true' && inputs.install-rust == 'true' && steps.profile.outputs.rust == 'true' && steps.profile.outputs.rust_nextest == 'true'
785
+ uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2
786
+ with:
787
+ tool: cargo-nextest
749
788
  - name: Setup
750
789
  if: ${{ steps.profile.outputs.mise_required == 'true' }}
751
790
  uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4
@@ -78,9 +78,15 @@ jobs:
78
78
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
79
79
  - name: Detect
80
80
  id: applicability
81
+ env:
82
+ EVENT_NAME: ${{ github.event_name }}
83
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
84
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
85
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
86
+ BEFORE_SHA: ${{ github.event.before }}
81
87
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run format >> "$GITHUB_OUTPUT"
82
88
  - name: Setup
83
- if: steps.applicability.outputs.applicable == 'true'
89
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
84
90
  uses: ./.github/actions/setup
85
91
  with:
86
92
  # Repository format scripts normally call a project-local formatter
@@ -93,13 +99,13 @@ jobs:
93
99
  task: format
94
100
  - name: Format
95
101
  id: execute
96
- if: steps.applicability.outputs.applicable == 'true'
102
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
97
103
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci format
98
104
  - name: Retain task receipt
99
105
  if: >-
100
106
  always() &&
101
107
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
102
- steps.applicability.outputs.applicable == 'false') &&
108
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
103
109
  hashFiles('.code-foundry/results/format.json') != ''
104
110
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
105
111
  with:
@@ -138,9 +144,15 @@ jobs:
138
144
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
139
145
  - name: Detect
140
146
  id: applicability
147
+ env:
148
+ EVENT_NAME: ${{ github.event_name }}
149
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
150
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
151
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
152
+ BEFORE_SHA: ${{ github.event.before }}
141
153
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run lint >> "$GITHUB_OUTPUT"
142
154
  - name: Setup
143
- if: steps.applicability.outputs.applicable == 'true'
155
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
144
156
  uses: ./.github/actions/setup
145
157
  with:
146
158
  install: 'true'
@@ -148,13 +160,13 @@ jobs:
148
160
  task: lint
149
161
  - name: Lint
150
162
  id: execute
151
- if: steps.applicability.outputs.applicable == 'true'
163
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
152
164
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci lint
153
165
  - name: Retain task receipt
154
166
  if: >-
155
167
  always() &&
156
168
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
157
- steps.applicability.outputs.applicable == 'false') &&
169
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
158
170
  hashFiles('.code-foundry/results/lint.json') != ''
159
171
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
160
172
  with:
@@ -193,9 +205,15 @@ jobs:
193
205
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
194
206
  - name: Detect
195
207
  id: applicability
208
+ env:
209
+ EVENT_NAME: ${{ github.event_name }}
210
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
211
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
212
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
213
+ BEFORE_SHA: ${{ github.event.before }}
196
214
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run type_check >> "$GITHUB_OUTPUT"
197
215
  - name: Setup
198
- if: steps.applicability.outputs.applicable == 'true'
216
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
199
217
  uses: ./.github/actions/setup
200
218
  with:
201
219
  install: 'true'
@@ -203,13 +221,13 @@ jobs:
203
221
  task: type_check
204
222
  - name: Type-Check
205
223
  id: execute
206
- if: steps.applicability.outputs.applicable == 'true'
224
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
207
225
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci type_check
208
226
  - name: Retain task receipt
209
227
  if: >-
210
228
  always() &&
211
229
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
212
- steps.applicability.outputs.applicable == 'false') &&
230
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
213
231
  hashFiles('.code-foundry/results/type_check.json') != ''
214
232
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
215
233
  with:
@@ -253,9 +271,15 @@ jobs:
253
271
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
254
272
  - name: Detect
255
273
  id: applicability
274
+ env:
275
+ EVENT_NAME: ${{ github.event_name }}
276
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
277
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
278
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
279
+ BEFORE_SHA: ${{ github.event.before }}
256
280
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run build >> "$GITHUB_OUTPUT"
257
281
  - name: Setup
258
- if: steps.applicability.outputs.applicable == 'true'
282
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
259
283
  uses: ./.github/actions/setup
260
284
  with:
261
285
  install: 'true'
@@ -263,13 +287,13 @@ jobs:
263
287
  task: build
264
288
  - name: Build
265
289
  id: execute
266
- if: steps.applicability.outputs.applicable == 'true'
290
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
267
291
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci build
268
292
  - name: Retain task receipt
269
293
  if: >-
270
294
  always() &&
271
295
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
272
- steps.applicability.outputs.applicable == 'false') &&
296
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
273
297
  hashFiles('.code-foundry/results/build.json') != ''
274
298
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
275
299
  with:
@@ -195,11 +195,17 @@ jobs:
195
195
  runs-on: ${{ inputs.runner }}
196
196
  timeout-minutes: 30
197
197
  steps:
198
+ # Analyze the pull request head, not the ephemeral merge ref. The
199
+ # codeql-action records commit_oid from `git rev-parse HEAD` on the
200
+ # checkout — the `sha` upload input is only a fallback when git is
201
+ # unavailable — so checking out the head is what actually keeps the
202
+ # recorded commit stable across mergeability evaluations.
198
203
  - name: Checkout
199
204
  if: ${{ matrix.entry.changed == true }}
200
205
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
201
206
  with:
202
207
  persist-credentials: false
208
+ ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
203
209
  - name: Runtime
204
210
  if: ${{ matrix.entry.changed == true }}
205
211
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -75,9 +75,15 @@ jobs:
75
75
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
76
76
  - name: Detect
77
77
  id: applicability
78
+ env:
79
+ EVENT_NAME: ${{ github.event_name }}
80
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
81
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
82
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
83
+ BEFORE_SHA: ${{ github.event.before }}
78
84
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci should_run eval >> "$GITHUB_OUTPUT"
79
85
  - name: Setup
80
- if: steps.applicability.outputs.applicable == 'true'
86
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
81
87
  uses: ./.github/actions/setup
82
88
  with:
83
89
  # Eval harnesses run through the repository's own package scripts and
@@ -87,13 +93,13 @@ jobs:
87
93
  task: eval
88
94
  - name: Eval
89
95
  id: execute
90
- if: steps.applicability.outputs.applicable == 'true'
96
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
91
97
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci eval
92
98
  - name: Retain task receipt
93
99
  if: >-
94
100
  always() &&
95
101
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
96
- steps.applicability.outputs.applicable == 'false') &&
102
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
97
103
  hashFiles('.code-foundry/results/eval.json') != ''
98
104
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
99
105
  with:
@@ -92,9 +92,15 @@ jobs:
92
92
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
93
93
  - name: Detect
94
94
  id: applicability
95
+ env:
96
+ EVENT_NAME: ${{ github.event_name }}
97
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
98
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
99
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
100
+ BEFORE_SHA: ${{ github.event.before }}
95
101
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile unit >> "$GITHUB_OUTPUT"
96
102
  - name: Setup
97
- if: steps.applicability.outputs.applicable == 'true'
103
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
98
104
  uses: ./.github/actions/setup
99
105
  with:
100
106
  install: true
@@ -110,13 +116,13 @@ jobs:
110
116
  task-rust: ${{ steps.applicability.outputs.rust }}
111
117
  - name: Unit
112
118
  id: execute
113
- if: steps.applicability.outputs.applicable == 'true'
119
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
114
120
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci unit
115
121
  - name: Retain task receipt
116
122
  if: >-
117
123
  always() &&
118
124
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
119
- steps.applicability.outputs.applicable == 'false') &&
125
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
120
126
  hashFiles('.code-foundry/results/unit.json') != ''
121
127
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
122
128
  with:
@@ -172,9 +178,15 @@ jobs:
172
178
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
173
179
  - name: Detect
174
180
  id: applicability
181
+ env:
182
+ EVENT_NAME: ${{ github.event_name }}
183
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
184
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
185
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
186
+ BEFORE_SHA: ${{ github.event.before }}
175
187
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile performance >> "$GITHUB_OUTPUT"
176
188
  - name: Setup
177
- if: steps.applicability.outputs.applicable == 'true'
189
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
178
190
  uses: ./.github/actions/setup
179
191
  with:
180
192
  install: true
@@ -191,13 +203,13 @@ jobs:
191
203
  task-rust: ${{ steps.applicability.outputs.rust }}
192
204
  - name: Performance
193
205
  id: execute
194
- if: steps.applicability.outputs.applicable == 'true'
206
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
195
207
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci performance
196
208
  - name: Retain task receipt
197
209
  if: >-
198
210
  always() &&
199
211
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
200
- steps.applicability.outputs.applicable == 'false') &&
212
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
201
213
  hashFiles('.code-foundry/results/performance.json') != ''
202
214
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
203
215
  with:
@@ -252,9 +264,15 @@ jobs:
252
264
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
253
265
  - name: Detect
254
266
  id: applicability
267
+ env:
268
+ EVENT_NAME: ${{ github.event_name }}
269
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
270
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
271
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
272
+ BEFORE_SHA: ${{ github.event.before }}
255
273
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile integration >> "$GITHUB_OUTPUT"
256
274
  - name: Setup
257
- if: steps.applicability.outputs.applicable == 'true'
275
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
258
276
  uses: ./.github/actions/setup
259
277
  with:
260
278
  install: true
@@ -270,13 +288,13 @@ jobs:
270
288
  task-rust: ${{ steps.applicability.outputs.rust }}
271
289
  - name: Integration
272
290
  id: execute
273
- if: steps.applicability.outputs.applicable == 'true'
291
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
274
292
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci integration
275
293
  - name: Retain task receipt
276
294
  if: >-
277
295
  always() &&
278
296
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
279
- steps.applicability.outputs.applicable == 'false') &&
297
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
280
298
  hashFiles('.code-foundry/results/integration.json') != ''
281
299
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
282
300
  with:
@@ -316,9 +334,15 @@ jobs:
316
334
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
317
335
  - name: Detect
318
336
  id: applicability
337
+ env:
338
+ EVENT_NAME: ${{ github.event_name }}
339
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
340
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
341
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
342
+ BEFORE_SHA: ${{ github.event.before }}
319
343
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile e2e >> "$GITHUB_OUTPUT"
320
344
  - name: Cache browser binaries
321
- if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.browser == 'true'
345
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false' && steps.applicability.outputs.browser == 'true'
322
346
  uses: ./.github/actions/cache
323
347
  with:
324
348
  save: ${{ github.event_name == 'push' }}
@@ -330,7 +354,7 @@ jobs:
330
354
  restore-keys: |
331
355
  ${{ runner.os }}-e2e-browsers-
332
356
  - name: Setup
333
- if: steps.applicability.outputs.applicable == 'true'
357
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
334
358
  uses: ./.github/actions/setup
335
359
  with:
336
360
  install: true
@@ -346,13 +370,13 @@ jobs:
346
370
  task-rust: ${{ steps.applicability.outputs.rust }}
347
371
  - name: E2E
348
372
  id: execute
349
- if: steps.applicability.outputs.applicable == 'true'
373
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
350
374
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci e2e
351
375
  - name: Retain task receipt
352
376
  if: >-
353
377
  always() &&
354
378
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
355
- steps.applicability.outputs.applicable == 'false') &&
379
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
356
380
  hashFiles('.code-foundry/results/e2e.json') != ''
357
381
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
358
382
  with:
@@ -392,9 +416,15 @@ jobs:
392
416
  cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
393
417
  - name: Detect
394
418
  id: applicability
419
+ env:
420
+ EVENT_NAME: ${{ github.event_name }}
421
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
422
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
423
+ HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
424
+ BEFORE_SHA: ${{ github.event.before }}
395
425
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci task_profile smoke >> "$GITHUB_OUTPUT"
396
426
  - name: Setup
397
- if: steps.applicability.outputs.applicable == 'true'
427
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
398
428
  uses: ./.github/actions/setup
399
429
  with:
400
430
  install: true
@@ -410,13 +440,13 @@ jobs:
410
440
  task-rust: ${{ steps.applicability.outputs.rust }}
411
441
  - name: Smoke
412
442
  id: execute
413
- if: steps.applicability.outputs.applicable == 'true'
443
+ if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false'
414
444
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" ci smoke
415
445
  - name: Retain task receipt
416
446
  if: >-
417
447
  always() &&
418
448
  (steps.execute.outcome == 'success' || steps.execute.outcome == 'failure' ||
419
- steps.applicability.outputs.applicable == 'false') &&
449
+ steps.applicability.outputs.applicable == 'false' || steps.applicability.outputs.affected == 'false') &&
420
450
  hashFiles('.code-foundry/results/smoke.json') != ''
421
451
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
422
452
  with:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.29.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.15...v1.29.0) (2026-09-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * opt-in lane path filters, sccache, and nextest for Rust CI ([#626](https://github.com/0xPlayerOne/code-foundry/issues/626)) ([691ccad](https://github.com/0xPlayerOne/code-foundry/commit/691ccad529d88cde4ddd134b30cfb86988a1cb0a))
9
+
10
+ ## [1.28.15](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.14...v1.28.15) (2026-09-17)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **codeql:** analyze the pull request head commit ([#624](https://github.com/0xPlayerOne/code-foundry/issues/624)) ([ce05d1f](https://github.com/0xPlayerOne/code-foundry/commit/ce05d1f4f6c5df3a2d9134dd4657f478a265cb85))
16
+
3
17
  ## [1.28.14](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.13...v1.28.14) (2026-09-11)
4
18
 
5
19
 
@@ -105,6 +105,9 @@ See [Merge queue validation](merge-queues.md) before enabling it.
105
105
  | `coverage_minimum` | `0`–`100` | Minimum percentage; defaults to `80`. |
106
106
  | `coverage_metrics` | `lines`, `functions`, `branches`, `statements` | Metrics checked by the coverage gate. |
107
107
  | `coverage_report` | comma-separated repository paths | Istanbul JSON summary or LCOV evidence files. |
108
+ | `filter_<task>` | comma-separated repository path globs | Skip a lane when no changed path matches; see [Lane path filters](#lane-path-filters). |
109
+ | `rust_sccache` | `true`, `false` | Install sccache and wrap rustc so compile artifacts survive manifest/lockfile churn. |
110
+ | `rust_nextest` | `true`, `false` | Install cargo-nextest and prefer `cargo nextest run` over `cargo test` in Rust test lanes. |
108
111
 
109
112
  Supported task capabilities are `format`, `lint`, `type_check`, `build`, `unit`,
110
113
  `integration`, `e2e`, `smoke`, `eval`, and `performance`. `coverage` is a policy
@@ -200,6 +203,34 @@ behavior:
200
203
  Use these controls only after measuring a repeatable benefit. See [Caching and
201
204
  remote caching](CACHING.md).
202
205
 
206
+ ## Lane path filters
207
+
208
+ Validation lanes run their full suite by default. Repositories with clearly
209
+ separated areas — for example a Rust workspace next to a web app — can opt any
210
+ task lane into path filtering so a change that cannot affect the lane skips
211
+ its setup and execution steps:
212
+
213
+ ```yaml
214
+ filter_integration: 'src/**/*.py,tests/**,pyproject.toml,uv.lock'
215
+ filter_eval: 'eval/**,crates/**,src/**'
216
+ filter_e2e: 'apps/**,src/**'
217
+ ```
218
+
219
+ `filter_<task>` accepts the same task names as `required_capabilities`. Globs
220
+ are repo-relative: `**/` matches any directory depth, `*` stays inside one
221
+ path segment, `?` matches one character, and a trailing `/` is shorthand for
222
+ `dir/**`.
223
+
224
+ The gate is fail-open by design: a task without a configured filter, a diff
225
+ that cannot be resolved (shallow checkout edge, missing base ref), or an empty
226
+ change set all keep the lane running. Paths are evaluated against
227
+ `base...head` for pull requests and `before..after` for pushes; other events
228
+ always run. Because filters only decide whether a lane _executes_, required
229
+ checks stay green and the aggregate gate sees a normal success.
230
+
231
+ Security lanes and CodeQL are never filterable: their coverage is structural,
232
+ not change-scoped.
233
+
203
234
  ## Rust CodeQL tuning
204
235
 
205
236
  Rust CodeQL defaults to one full scan with one worker. Larger multi-crate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.28.14",
3
+ "version": "1.29.0",
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": {
@@ -0,0 +1,143 @@
1
+ // @ts-check
2
+
3
+ import { execFileSync } from 'node:child_process'
4
+
5
+ /**
6
+ * Tasks that support `filter_<task>` path filters in code-foundry.yml. A
7
+ * filter lists repo-relative globs; when every changed path misses the filter
8
+ * the lane reports `affected=false` and its workflow steps skip. CI lanes and
9
+ * security tasks are not filterable because their coverage is structural.
10
+ */
11
+ export const FILTERABLE_TASKS = [
12
+ 'format',
13
+ 'lint',
14
+ 'type_check',
15
+ 'build',
16
+ 'unit',
17
+ 'integration',
18
+ 'e2e',
19
+ 'smoke',
20
+ 'eval',
21
+ 'performance',
22
+ ]
23
+
24
+ /**
25
+ * Parse `filter_<task>` config keys into per-task glob lists.
26
+ * @param {Record<string, string>} config
27
+ * @returns {Map<string, string[]>}
28
+ */
29
+ export function readTaskFilters(config) {
30
+ const filters = new Map()
31
+ for (const task of FILTERABLE_TASKS) {
32
+ const value = config[`filter_${task}`]
33
+ if (value === undefined || value === '') continue
34
+ const globs = value
35
+ .split(',')
36
+ .map((glob) => glob.trim())
37
+ .filter(Boolean)
38
+ if (globs.length > 0) filters.set(task, globs)
39
+ }
40
+ return filters
41
+ }
42
+
43
+ /**
44
+ * Compile a repo-relative glob to a RegExp. Supported syntax: double-star
45
+ * segments match any directory depth, `*` stays inside a path segment, `?`
46
+ * matches one character, and a trailing `/` is a directory shorthand.
47
+ * @param {string} glob
48
+ * @returns {RegExp}
49
+ */
50
+ export function globToRegExp(glob) {
51
+ let pattern = glob.replace(/^\.\//, '')
52
+ if (pattern.endsWith('/')) pattern += '**'
53
+ let regex = '^'
54
+ let index = 0
55
+ while (index < pattern.length) {
56
+ if (pattern.startsWith('**/', index)) {
57
+ regex += '(?:[^/]+/)*'
58
+ index += 3
59
+ } else if (pattern.startsWith('/**', index) && index + 3 === pattern.length) {
60
+ regex += '/.*'
61
+ index += 3
62
+ } else if (pattern.startsWith('**', index)) {
63
+ regex += '.*'
64
+ index += 2
65
+ } else if (pattern[index] === '*') {
66
+ regex += '[^/]*'
67
+ index += 1
68
+ } else if (pattern[index] === '?') {
69
+ regex += '[^/]'
70
+ index += 1
71
+ } else {
72
+ regex += pattern[index].replace(/[.+^${}()|[\]\\]/g, '\\$&')
73
+ index += 1
74
+ }
75
+ }
76
+ return new RegExp(`${regex}$`)
77
+ }
78
+
79
+ /**
80
+ * Resolve the pull request or push change set. The generated Detect steps
81
+ * pass EVENT_NAME plus BASE_REF/BASE_SHA/HEAD_SHA (pull_request) or
82
+ * BEFORE_SHA/HEAD_SHA (push). Returns null when the change set cannot be
83
+ * resolved; callers treat null as fail-open so a checkout edge never skips a
84
+ * required lane.
85
+ * @param {string} root
86
+ * @param {Record<string, string | undefined>} env
87
+ * @returns {string[] | null}
88
+ */
89
+ export function resolveChangedPaths(root, env = process.env) {
90
+ const event = env.EVENT_NAME
91
+ let range = null
92
+ try {
93
+ if (event === 'pull_request' || event === 'pull_request_target') {
94
+ const baseRef = env.BASE_REF
95
+ const baseSha = env.BASE_SHA
96
+ const head = env.HEAD_SHA || 'HEAD'
97
+ if (!baseSha) return null
98
+ if (baseRef) {
99
+ execFileSync('git', ['fetch', '--no-tags', '--depth=100', 'origin', baseRef], {
100
+ cwd: root,
101
+ stdio: 'pipe',
102
+ })
103
+ range = `FETCH_HEAD...${head}`
104
+ } else {
105
+ range = `${baseSha}...${head}`
106
+ }
107
+ } else if (event === 'push') {
108
+ const before = env.BEFORE_SHA
109
+ const head = env.HEAD_SHA || 'HEAD'
110
+ if (!before || /^0+$/.test(before)) return null
111
+ range = `${before}..${head}`
112
+ } else {
113
+ return null
114
+ }
115
+ const output = execFileSync('git', ['diff', '--name-only', range], {
116
+ cwd: root,
117
+ encoding: 'utf8',
118
+ stdio: ['ignore', 'pipe', 'pipe'],
119
+ })
120
+ return output
121
+ .split('\n')
122
+ .map((line) => line.trim())
123
+ .filter((line) => line.length > 0)
124
+ } catch {
125
+ return null
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Report whether a task's configured path filter matches the change set.
131
+ * Fail-open by contract: no filter, no resolvable diff, or an empty diff all
132
+ * keep the lane running.
133
+ * @param {string} task
134
+ * @param {Map<string, string[]>} filters
135
+ * @param {string[] | null} paths
136
+ * @returns {boolean}
137
+ */
138
+ export function taskAffected(task, filters, paths) {
139
+ const globs = filters.get(task)
140
+ if (!globs || globs.length === 0 || paths === null || paths.length === 0) return true
141
+ const matchers = globs.map(globToRegExp)
142
+ return paths.some((path) => matchers.some((matcher) => matcher.test(path)))
143
+ }
@@ -15,6 +15,7 @@ import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
15
15
  import { configured, readConfig } from './lib/config.mjs'
16
16
  import { classifyTestFiles } from './lib/test-discovery.mjs'
17
17
  import { docsOnlyPullRequest } from './lib/docs-only.mjs'
18
+ import { readTaskFilters, resolveChangedPaths, taskAffected } from './lib/task-filters.mjs'
18
19
  import { classifyValidationMode, evaluateValidationGate } from './lib/validation-policy.mjs'
19
20
  import { readReleaseConfig, validateGeneratedReleaseDiff } from './lib/release-policy.mjs'
20
21
  import { runNodePackagePerformance } from './lib/node-package-performance.mjs'
@@ -688,6 +689,15 @@ function ci(task) {
688
689
  if (task === 'should_run' || task === 'task_profile') {
689
690
  const selected = process.argv[4]
690
691
  writeOutput('applicable', relevant(selected) ? 'true' : 'false')
692
+ // Path-filtered lanes are opt-in via `filter_<task>` config keys. The
693
+ // gate fails open: no filter, an unresolvable diff, or an empty change
694
+ // set all keep the lane running.
695
+ writeOutput(
696
+ 'affected',
697
+ taskAffected(selected ?? '', readTaskFilters(config), resolveChangedPaths(root))
698
+ ? 'true'
699
+ : 'false'
700
+ )
691
701
  writeOutput(
692
702
  'javascript',
693
703
  hasLanguage('typescript') || hasLanguage('javascript') ? 'true' : 'false'
@@ -784,9 +794,14 @@ function ci(task) {
784
794
  }
785
795
 
786
796
  if (hasLanguage('rust') && hasRootRustProject()) {
797
+ // `rust_nextest` opts into process-per-test parallelism. cargo-nextest
798
+ // accepts the same --lib/--bin/--test target flags as `cargo test`.
799
+ const nextest =
800
+ configured(config.rust_nextest, 'false') === 'true' && commandExists('cargo-nextest')
801
+ const testArgs = nextest ? ['nextest', 'run'] : ['test']
787
802
  if (task === 'unit') {
788
803
  // Batch exact prior targets through one Cargo graph.
789
- const args = ['test']
804
+ const args = [...testArgs]
790
805
  if (existsSync(resolve(root, 'src/lib.rs'))) args.push('--lib')
791
806
  if (existsSync(resolve(root, 'src/main.rs'))) args.push('--bin', packageName())
792
807
  run('cargo', args)
@@ -796,7 +811,7 @@ function ci(task) {
796
811
  const match = file.match(/^tests\/(.+)\.rs$/)
797
812
  return match && !match[1].includes('/') ? ['--test', match[1]] : []
798
813
  })
799
- if (targets.length > 0) run('cargo', ['test', ...targets])
814
+ if (targets.length > 0) run('cargo', [...testArgs, ...targets])
800
815
  }
801
816
  }
802
817
  }
package/src/runtime.mjs CHANGED
@@ -42,6 +42,7 @@ export function taskProfile(root, task, entry = core) {
42
42
  throw new Error(`Task discovery returned no applicability: ${task}`)
43
43
  return {
44
44
  ...describeTask(root, task, profile),
45
+ affected: profile.affected ?? 'true',
45
46
  javascript: profile.javascript ?? 'false',
46
47
  python: profile.python ?? 'false',
47
48
  rust: profile.rust ?? 'false',
@@ -110,6 +111,24 @@ export function runRuntime(args, root = process.cwd(), entry = core) {
110
111
  (name) => name !== 'coverage' && name !== selected
111
112
  ))
112
113
  taskProfile(root, requiredName, entry)
114
+ // A path-filtered lane that misses the diff still leaves a receipt so the
115
+ // run records why its steps skipped.
116
+ if (profile.applicable && profile.affected === 'false') {
117
+ const now = new Date().toISOString()
118
+ saveResult(root, selected, {
119
+ schemaVersion: 1,
120
+ kind: 'code-foundry-task-result',
121
+ task: selected,
122
+ sourceSha: sourceSha(root),
123
+ startedAt: now,
124
+ completedAt: now,
125
+ status: 'skipped',
126
+ reason: 'no changed path matched the task filter',
127
+ required: false,
128
+ commands: [],
129
+ artifacts: [],
130
+ })
131
+ }
113
132
  if (!profile.applicable) {
114
133
  const now = new Date().toISOString()
115
134
  saveResult(root, selected, {
@@ -128,6 +147,7 @@ export function runRuntime(args, root = process.cwd(), entry = core) {
128
147
  }
129
148
  for (const [key, value] of Object.entries({
130
149
  applicable: String(profile.applicable),
150
+ affected: profile.affected ?? 'true',
131
151
  javascript: profile.javascript ?? 'false',
132
152
  python: profile.python ?? 'false',
133
153
  rust: profile.rust ?? 'false',