code-foundry 1.53.7 → 1.53.9

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.
@@ -39,8 +39,10 @@ on:
39
39
  description: >-
40
40
  JSON object of the Detect job's outputs, computed by the caller's
41
41
  planning job (`validation plan`). When set, Detect is skipped and
42
- analysis reads the plan instead, saving one runner hop. Empty runs
43
- Detect here as before.
42
+ analysis reads the plan instead, saving one runner hop. Without a
43
+ code-scanning merge gate the planned matrix omits unchanged
44
+ languages and Rust shards. Empty runs Detect here as before, with
45
+ the full matrix.
44
46
  required: false
45
47
  type: string
46
48
  default: ''
@@ -160,17 +162,29 @@ jobs:
160
162
  set -euo pipefail
161
163
  # The code-scanning merge gate waits for results in every tracked
162
164
  # category, so change detection must never skip an upload while it
163
- # is active. Any ruleset API failure also disables change
164
- # detection: full analysis is the safe default.
165
+ # is active. Any ruleset API failure counts as an active gate: full
166
+ # analysis is the safe default, and the planner drops unchanged
167
+ # analyzers only on a positive "no gate" answer.
165
168
  gated=false
166
- for id in $(gh api "repos/$GITHUB_REPOSITORY/rulesets" --paginate \
167
- -q '.[] | select(.enforcement == "active") | .id' 2>/dev/null); do
168
- if gh api "repos/$GITHUB_REPOSITORY/rulesets/$id" \
169
- -q 'any(.rules[]?; .type == "code_scanning")' 2>/dev/null | grep -q true; then
170
- gated=true
171
- break
172
- fi
173
- done
169
+ if ! ids="$(gh api "repos/$GITHUB_REPOSITORY/rulesets" --paginate \
170
+ -q '.[] | select(.enforcement == "active") | .id')"; then
171
+ echo "Ruleset listing failed; treating the code-scanning merge gate as active."
172
+ gated=true
173
+ else
174
+ for id in $ids; do
175
+ if ! rule="$(gh api "repos/$GITHUB_REPOSITORY/rulesets/$id" \
176
+ -q 'any(.rules[]?; .type == "code_scanning")')"; then
177
+ echo "Ruleset $id lookup failed; treating the code-scanning merge gate as active."
178
+ gated=true
179
+ break
180
+ fi
181
+ if [ "$rule" = "true" ]; then
182
+ gated=true
183
+ break
184
+ fi
185
+ done
186
+ fi
187
+ echo "Code-scanning merge gate active: $gated"
174
188
  echo "gated=$gated" >> "$GITHUB_OUTPUT"
175
189
  - id: languages
176
190
  name: Detect languages
@@ -554,56 +554,77 @@ jobs:
554
554
  # A ready head already receives synchronize validation. Preserve
555
555
  # its live/completed owner instead of cancelling and reallocating
556
556
  # CI every time another queued main release run prepares it.
557
- runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
558
- "repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
559
- -f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
560
- validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
561
- --arg repo "$GITHUB_REPOSITORY" '
562
- def active: .status == "queued" or .status == "in_progress" or
563
- .status == "waiting" or .status == "pending" or .status == "requested";
564
- def valid_run:
565
- type == "object" and
566
- (.id | type == "number" and . > 0 and floor == .) and
567
- (.run_attempt | type == "number" and . >= 1 and floor == .) and
568
- (.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
569
- (.head_branch | type == "string" and length > 0) and
570
- (.event | type == "string" and length > 0) and
571
- (.repository | type == "object") and
572
- (.repository.full_name | type == "string" and length > 0) and
573
- (.path | type == "string" and length > 0) and
574
- ((active and has("conclusion") and .conclusion == null) or
575
- (.status == "completed" and
576
- (.conclusion == "success" or .conclusion == "failure" or
577
- .conclusion == "timed_out" or .conclusion == "action_required" or
578
- .conclusion == "cancelled" or .conclusion == "skipped" or
579
- .conclusion == "neutral")));
580
- if (.workflow_runs | type) != "array" or
581
- (.total_count | type) != "number" then
582
- error("Invalid validation owner response")
583
- elif .total_count != (.workflow_runs | length) then
584
- error("Incomplete validation owner response")
585
- elif any(.workflow_runs[]; valid_run | not) then
586
- error("Invalid validation run identity or state")
587
- else
588
- [.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
589
- .head_sha == $head and .head_branch == $branch and
590
- .event == "pull_request" and .repository.full_name == $repo and
591
- ($workflow == ".github/workflows/validation.yml" or
592
- $workflow == ".github/workflows/validation_self-ci.yml")
593
- )] | sort_by(.id) | last |
594
- if . == null then "missing"
595
- elif active or (.status == "completed" and .conclusion == "success") then "owned"
596
- elif .status == "completed" and
597
- (.conclusion == "failure" or .conclusion == "timed_out" or
598
- .conclusion == "action_required") then "failed"
599
- elif .status == "completed" and
600
- (.conclusion == "cancelled" or .conclusion == "skipped" or
601
- .conclusion == "neutral") then "missing"
602
- else error("Unconfirmed validation owner state") end
603
- end' <<< "$runs")
604
- fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
605
- --json isDraft,headRefOid,headRefName \
606
- --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
557
+ #
558
+ # GitHub registers the synchronize run for a head this job (or
559
+ # Release Please) just pushed a few seconds after the push. An
560
+ # absent owner is therefore polled for a bounded window before the
561
+ # canonical transition, so the transition does not cancel that
562
+ # run. A cancelled, skipped or neutral owner is already terminal
563
+ # and transitions at once; after the window an absent owner
564
+ # still gets the explicit transition, and a failed one still fails.
565
+ owner_poll=1
566
+ while :; do
567
+ runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
568
+ "repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
569
+ -f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
570
+ validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
571
+ --arg repo "$GITHUB_REPOSITORY" '
572
+ def active: .status == "queued" or .status == "in_progress" or
573
+ .status == "waiting" or .status == "pending" or .status == "requested";
574
+ def valid_run:
575
+ type == "object" and
576
+ (.id | type == "number" and . > 0 and floor == .) and
577
+ (.run_attempt | type == "number" and . >= 1 and floor == .) and
578
+ (.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
579
+ (.head_branch | type == "string" and length > 0) and
580
+ (.event | type == "string" and length > 0) and
581
+ (.repository | type == "object") and
582
+ (.repository.full_name | type == "string" and length > 0) and
583
+ (.path | type == "string" and length > 0) and
584
+ ((active and has("conclusion") and .conclusion == null) or
585
+ (.status == "completed" and
586
+ (.conclusion == "success" or .conclusion == "failure" or
587
+ .conclusion == "timed_out" or .conclusion == "action_required" or
588
+ .conclusion == "cancelled" or .conclusion == "skipped" or
589
+ .conclusion == "neutral")));
590
+ if (.workflow_runs | type) != "array" or
591
+ (.total_count | type) != "number" then
592
+ error("Invalid validation owner response")
593
+ elif .total_count != (.workflow_runs | length) then
594
+ error("Incomplete validation owner response")
595
+ elif any(.workflow_runs[]; valid_run | not) then
596
+ error("Invalid validation run identity or state")
597
+ else
598
+ [.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
599
+ .head_sha == $head and .head_branch == $branch and
600
+ .event == "pull_request" and .repository.full_name == $repo and
601
+ ($workflow == ".github/workflows/validation.yml" or
602
+ $workflow == ".github/workflows/validation_self-ci.yml")
603
+ )] | sort_by(.id) | last |
604
+ if . == null then "absent"
605
+ elif active or (.status == "completed" and .conclusion == "success") then "owned"
606
+ elif .status == "completed" and
607
+ (.conclusion == "failure" or .conclusion == "timed_out" or
608
+ .conclusion == "action_required") then "failed"
609
+ elif .status == "completed" and
610
+ (.conclusion == "cancelled" or .conclusion == "skipped" or
611
+ .conclusion == "neutral") then "missing"
612
+ else error("Unconfirmed validation owner state") end
613
+ end' <<< "$runs")
614
+ fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
615
+ --json isDraft,headRefOid,headRefName \
616
+ --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
617
+ if [ "$fresh_state" != "$pr_state" ] || [ "$validation" != absent ] ||
618
+ [ "$owner_poll" -ge 7 ]; then
619
+ break
620
+ fi
621
+ echo "Release PR #$pr has no current-head validation yet (lookup $owner_poll of 7); waiting for its synchronize run to register."
622
+ owner_poll=$((owner_poll + 1))
623
+ sleep 5
624
+ done
625
+ if [ "$validation" = absent ]; then
626
+ validation=missing
627
+ fi
607
628
  if [ "$fresh_state" != "$pr_state" ]; then
608
629
  if [ "$state_attempt" -eq 3 ]; then
609
630
  echo 'Release PR state did not stabilize during validation lookup; refusing readiness mutation.' >&2
@@ -135,8 +135,9 @@ on:
135
135
  description: >-
136
136
  JSON CodeQL Detect outputs from the caller's planning job. When set,
137
137
  the CodeQL workflow skips its Detect job and reads the plan, and a
138
- plan marked idle (CodeQL unavailable) skips the workflow. Empty
139
- detects inside the workflow as before.
138
+ plan marked idle (CodeQL unavailable, or no analyzed language or
139
+ Rust shard changed) skips the workflow. Empty detects inside the
140
+ workflow as before.
140
141
  required: false
141
142
  type: string
142
143
  default: ''
@@ -113,8 +113,9 @@ on:
113
113
  description: >-
114
114
  JSON CodeQL Detect outputs from the caller's planning job. When set,
115
115
  the CodeQL workflow skips its Detect job and reads the plan, and a
116
- plan marked idle (CodeQL unavailable) skips the workflow. Empty
117
- detects inside the workflow as before.
116
+ plan marked idle (CodeQL unavailable, or no analyzed language or
117
+ Rust shard changed) skips the workflow. Empty detects inside the
118
+ workflow as before.
118
119
  required: false
119
120
  type: string
120
121
  default: ''
@@ -127,17 +127,29 @@ jobs:
127
127
  set -euo pipefail
128
128
  # The code-scanning merge gate waits for results in every tracked
129
129
  # category, so change detection must never skip an upload while it
130
- # is active. Any ruleset API failure also disables change
131
- # detection: full analysis is the safe default.
130
+ # is active. Any ruleset API failure counts as an active gate: full
131
+ # analysis is the safe default, and the planner drops unchanged
132
+ # analyzers only on a positive "no gate" answer.
132
133
  gated=false
133
- for id in $(gh api "repos/$GITHUB_REPOSITORY/rulesets" --paginate \
134
- -q '.[] | select(.enforcement == "active") | .id' 2>/dev/null); do
135
- if gh api "repos/$GITHUB_REPOSITORY/rulesets/$id" \
136
- -q 'any(.rules[]?; .type == "code_scanning")' 2>/dev/null | grep -q true; then
137
- gated=true
138
- break
139
- fi
140
- done
134
+ if ! ids="$(gh api "repos/$GITHUB_REPOSITORY/rulesets" --paginate \
135
+ -q '.[] | select(.enforcement == "active") | .id')"; then
136
+ echo "Ruleset listing failed; treating the code-scanning merge gate as active."
137
+ gated=true
138
+ else
139
+ for id in $ids; do
140
+ if ! rule="$(gh api "repos/$GITHUB_REPOSITORY/rulesets/$id" \
141
+ -q 'any(.rules[]?; .type == "code_scanning")')"; then
142
+ echo "Ruleset $id lookup failed; treating the code-scanning merge gate as active."
143
+ gated=true
144
+ break
145
+ fi
146
+ if [ "$rule" = "true" ]; then
147
+ gated=true
148
+ break
149
+ fi
150
+ done
151
+ fi
152
+ echo "Code-scanning merge gate active: $gated"
141
153
  echo "gated=$gated" >> "$GITHUB_OUTPUT"
142
154
  - name: Plan security and CodeQL
143
155
  id: plan
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.53.9](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.8...v1.53.9) (2026-10-31)
4
+
5
+
6
+ ### Performance
7
+
8
+ * **codeql:** drop unchanged analyzers from the planned matrix ([#831](https://github.com/0xPlayerOne/code-foundry/issues/831)) ([35b891e](https://github.com/0xPlayerOne/code-foundry/commit/35b891e721ea2e5d2bd09ec44509b541120a3a64))
9
+
10
+ ## [1.53.8](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.7...v1.53.8) (2026-10-31)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **release:** wait for the synchronize validation run before re-readying ([#829](https://github.com/0xPlayerOne/code-foundry/issues/829)) ([d0abbe2](https://github.com/0xPlayerOne/code-foundry/commit/d0abbe27349635bcc5bb63aafac3ff2e989f960f))
16
+
3
17
  ## [1.53.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.6...v1.53.7) (2026-10-31)
4
18
 
5
19
 
@@ -136,23 +150,9 @@
136
150
 
137
151
  * **validation:** require release checks until evidence exists ([#789](https://github.com/0xPlayerOne/code-foundry/issues/789)) ([a5f8be6](https://github.com/0xPlayerOne/code-foundry/commit/a5f8be64b7b28d2248aa71b80e321aa0fe82f30a))
138
152
 
139
- ## [1.49.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.1...v1.49.2) (2026-10-29)
140
-
141
-
142
- ### Bug Fixes
143
-
144
- * safely categorize npm publication failures ([#787](https://github.com/0xPlayerOne/code-foundry/issues/787)) ([a4268af](https://github.com/0xPlayerOne/code-foundry/commit/a4268af5ce7cd0a32407420424419c0cad01dce8))
145
-
146
- ## [1.49.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.0...v1.49.1) (2026-10-28)
147
-
148
-
149
- ### Bug Fixes
150
-
151
- * handle empty security audit matrices ([#785](https://github.com/0xPlayerOne/code-foundry/issues/785)) ([02f0565](https://github.com/0xPlayerOne/code-foundry/commit/02f05652e68267a2aa2df5d5af8f13df9ba1e3aa))
152
-
153
153
  <!-- code-foundry: packaged changelog -->
154
154
 
155
155
  ---
156
156
 
157
- This packaged changelog lists the 20 most recent releases; 278 older releases are omitted to keep the package small.
157
+ This packaged changelog lists the 20 most recent releases; 280 older releases are omitted to keep the package small.
158
158
  See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
@@ -427,8 +427,14 @@ every tracked category. Two practical consequences:
427
427
 
428
428
  On pull requests without a code-scanning merge gate, analyzers run only for
429
429
  languages with changed files, and Rust shards run only when their scope (or a
430
- workspace-wide `Cargo` manifest or toolchain file) changed; unchanged matrix
431
- entries report `Not applicable` instead of analyzing. Repositories whose
430
+ workspace-wide `Cargo` manifest or toolchain file) changed. The caller's
431
+ `Plan` job drops unchanged languages and shards from the analysis matrix, so
432
+ they take no runner and register no `Analyze` check; when nothing changed, the
433
+ whole CodeQL workflow is a planned idle skip that the gate counts as success.
434
+ Entries are dropped only when the planner positively found no merge gate; a
435
+ failed ruleset lookup counts as an active gate, and any other planning failure
436
+ leaves the workflow to detect for itself, where unchanged entries still report
437
+ `Not applicable` on their own runner. Repositories whose
432
438
  rulesets enforce a code-scanning requirement are detected automatically and
433
439
  analyze everything, because that merge gate waits for results in every tracked
434
440
  category. Push, schedule, and merge-queue events always analyze the complete
package/docs/WORKFLOWS.md CHANGED
@@ -326,9 +326,13 @@ Security Profile and CodeQL language detection (Code Security status, changed
326
326
  files, code-scanning merge gate) and passes their outputs to the orchestrator
327
327
  as `security-plan` and `codeql-plan`. The Security workflow then skips its
328
328
  `Profile` job and the CodeQL workflow skips its `Detect` job, so audits and
329
- analyzers start one runner hop earlier. A plan marked idle (no audit or review
330
- to run, or CodeQL unavailable) skips that workflow, and the gate counts only
331
- that planned skip as success. Planning fails open: if any planning step fails,
329
+ analyzers start one runner hop earlier. Without a code-scanning merge gate,
330
+ the CodeQL plan also drops the languages and Rust shards that change detection
331
+ marked unchanged, so they no longer take a runner only to print `Not
332
+ applicable`; `Analyze` checks are not required checks, so no required check
333
+ name changes. A plan marked idle (no audit or review to run, CodeQL
334
+ unavailable, or no analyzed language or shard changed) skips that workflow,
335
+ and the gate counts only that planned skip as success. Planning fails open: if any planning step fails,
332
336
  the plans stay empty and each workflow profiles and detects for itself, as it
333
337
  does for callers that pass no plans (the scheduled audit and default-branch
334
338
  CodeQL lanes).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.53.7",
3
+ "version": "1.53.9",
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": {
@@ -411,15 +411,18 @@ function validation(task) {
411
411
  }
412
412
  const detection = codeqlDetection()
413
413
  const enabled = detection.enabled === 'true'
414
+ const matrix = enabled
415
+ ? plannedCodeqlMatrix(
416
+ codeqlAnalysisMatrix(JSON.parse(detection.languages), configuredRustShards())
417
+ )
418
+ : []
414
419
  const codeqlPlan = {
415
420
  ...detection,
416
- matrix: enabled
417
- ? JSON.stringify(
418
- codeqlAnalysisMatrix(JSON.parse(detection.languages), configuredRustShards())
419
- )
420
- : '[]',
421
+ matrix: JSON.stringify(matrix),
421
422
  code_security: process.env.REPO_FOUNDRY_CODE_SECURITY ?? '',
422
- idle: enabled ? 'false' : 'true',
423
+ // Idle when CodeQL is unavailable, or when change detection left no
424
+ // entry to analyze: the gate counts that planned skip as success.
425
+ idle: matrix.length ? 'false' : 'true',
423
426
  }
424
427
  // Dependency Review rides on the first audit job of a planned run. With
425
428
  // no audit to ride on, leave Security unplanned so its Profile job runs
@@ -1260,6 +1263,24 @@ function codeqlAnalysisMatrix(detected, shards) {
1260
1263
  return matrix
1261
1264
  }
1262
1265
 
1266
+ /**
1267
+ * The planned CodeQL analysis matrix: the full matrix minus entries (whole
1268
+ * languages or single Rust shards) that change detection marked unchanged.
1269
+ * Such an entry would only take a runner to print "Not applicable": it
1270
+ * uploads nothing either way. Entries are dropped only when the planner
1271
+ * positively found no code-scanning merge gate (`FOUNDRY_CODEQL_MERGE_GATE`
1272
+ * is exactly `false`); an active gate waits on every tracked category, and
1273
+ * an unknown gate state keeps the full matrix. Analyze check names are not
1274
+ * required checks ("Validation / Gate" is), so dropping entries never changes
1275
+ * a required check. An empty result means nothing changed and the caller
1276
+ * marks the plan idle.
1277
+ * @param {Array<Record<string, string | boolean>>} matrix
1278
+ */
1279
+ function plannedCodeqlMatrix(matrix) {
1280
+ if ((process.env.FOUNDRY_CODEQL_MERGE_GATE ?? '') !== 'false') return matrix
1281
+ return matrix.filter((entry) => entry.changed === true)
1282
+ }
1283
+
1263
1284
  /**
1264
1285
  * Changed file paths for the current pull request, read from the list the
1265
1286
  * caller materializes with the pull request files API. Returns null when