code-foundry 0.37.2 → 0.37.4

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.
@@ -402,7 +402,7 @@ jobs:
402
402
  needs.detect.outputs.enabled == 'true' &&
403
403
  needs.detect.outputs.rust_available == 'true' &&
404
404
  needs.detect.outputs.rust_changed == 'true'
405
- uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4
405
+ uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
406
406
  with:
407
407
  languages: rust
408
408
  build-mode: ${{ needs.detect.outputs.rust_build_mode }}
@@ -413,7 +413,7 @@ jobs:
413
413
  needs.detect.outputs.enabled == 'true' &&
414
414
  needs.detect.outputs.rust_available == 'true' &&
415
415
  needs.detect.outputs.rust_changed == 'true'
416
- uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4
416
+ uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
417
417
  with:
418
418
  category: /language:rust/${{ steps.scope.outputs.scope_id }}
419
419
  upload-database: false
@@ -31,7 +31,7 @@ jobs:
31
31
  cancel-in-progress: true
32
32
  steps:
33
33
  - name: Checkout
34
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
34
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
35
  with:
36
36
  persist-credentials: false
37
37
  fetch-depth: 0
@@ -245,44 +245,14 @@ jobs:
245
245
  fi
246
246
  node "$RUNNER_TEMP/code-foundry/src/cli.mjs" release validate-prs
247
247
  for pr in "${release_prs[@]}"; do
248
- echo "Waiting for required checks on release PR #$pr"
249
- checks_seen=false
250
- for attempt in $(seq 1 60); do
251
- : "$attempt"
252
- if required_count=$(gh pr checks "$pr" \
253
- --repo "$GITHUB_REPOSITORY" \
254
- --required \
255
- --json name \
256
- --jq 'length' 2>/dev/null); then
257
- if [ "$required_count" -gt 0 ]; then
258
- checks_seen=true
259
- break
260
- fi
261
- fi
262
- sleep 5
263
- done
264
- if [ "$checks_seen" != true ]; then
265
- echo "Required checks were not registered for release PR #$pr within the readiness window." >&2
266
- exit 1
267
- fi
268
- gh pr checks "$pr" \
269
- --repo "$GITHUB_REPOSITORY" \
270
- --required \
271
- --watch \
272
- --fail-fast \
273
- --interval 10
274
- # gh pr checks --watch only waits for checks registered when the
275
- # watch starts; a ruleset may register additional required checks
276
- # afterwards (observed: "Validation / Gate" appearing after
277
- # "Tauri 2 / Linux" had passed), which leaves the PR blocked by
278
- # branch policy at merge time. Poll mergeStateStatus and accept
279
- # only states GitHub reports as mergeable: CLEAN, or UNSTABLE with
280
- # mergeable MERGEABLE. UNSTABLE is still mergeable when only
281
- # non-required checks are failing or still running (observed: Kilo
282
- # Code Review running after every required check passed); keep
283
- # waiting for BLOCKED/BEHIND/UNKNOWN, fail fast on
284
- # DIRTY/CONFLICTING, and fail closed on a bounded timeout so the
285
- # workflow never sleeps indefinitely.
248
+ # GitHub's merge state is the authoritative required-check gate.
249
+ # `gh pr checks --required` is not reliable for ruleset-backed
250
+ # required contexts in private repositories: it can return an
251
+ # empty result even while the required aggregate check is present
252
+ # and green. Polling mergeStateStatus avoids that discovery race;
253
+ # GitHub reports MERGEABLE only after branch policy permits the
254
+ # requested merge strategy. Non-required checks may leave the PR
255
+ # UNSTABLE, so accept that state only with mergeable MERGEABLE.
286
256
  echo "Waiting for release PR #$pr to become mergeable under branch policy"
287
257
  merge_state=UNKNOWN
288
258
  mergeable=UNKNOWN
@@ -506,12 +476,12 @@ jobs:
506
476
  ref: ${{ needs.release.outputs.tag_name }}
507
477
  - name: Setup Node (trusted)
508
478
  if: env.NPM_TOKEN == ''
509
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
479
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
510
480
  with:
511
481
  node-version: 24
512
482
  - name: Setup Node (token)
513
483
  if: env.NPM_TOKEN != ''
514
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
484
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
515
485
  with:
516
486
  node-version: 24
517
487
  registry-url: https://registry.npmjs.org
@@ -215,7 +215,7 @@ jobs:
215
215
  cache-save: ${{ github.event_name != 'pull_request' }}
216
216
  - name: Install cargo-audit
217
217
  if: steps.applicability.outputs.applicable == 'true'
218
- uses: taiki-e/install-action@6a1bd70eaac3c8bdf093356838d7ee09fda951cf # v2
218
+ uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2
219
219
  with:
220
220
  tool: cargo-audit
221
221
  - name: Audit dependencies
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.37.4](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.3...v0.37.4) (2026-08-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * release-workspace-state ([#395](https://github.com/0xPlayerOne/code-foundry/issues/395)) ([a708bc0](https://github.com/0xPlayerOne/code-foundry/commit/a708bc07272b9751b22a457771cc386601bbe3d8))
9
+
10
+ ## [0.37.3](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.2...v0.37.3) (2026-08-23)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * release-required-check-discovery ([#392](https://github.com/0xPlayerOne/code-foundry/issues/392)) ([d086cb5](https://github.com/0xPlayerOne/code-foundry/commit/d086cb5b6d1dc25eb28b6551ae56467887637296))
16
+
3
17
  ## [0.37.2](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.1...v0.37.2) (2026-08-23)
4
18
 
5
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.37.2",
3
+ "version": "0.37.4",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  import { existsSync, readFileSync } from 'node:fs'
4
- import { join } from 'node:path'
4
+ import { dirname, join } from 'node:path'
5
5
  import { isReleasePleaseHead } from './validation-policy.mjs'
6
6
 
7
7
  const DEFAULT_RELEASE_FILES = new Set([
@@ -71,6 +71,62 @@ export function unexpectedReleasePaths(paths, allowed) {
71
71
  .filter((path) => !allowed.has(path))
72
72
  }
73
73
 
74
+ /**
75
+ * Verify Cargo package versions remain reproducible after Release Please
76
+ * updates a manifest. Release Please's Node updater can change Cargo.toml
77
+ * while leaving a configured generic Cargo.lock extra-file untouched.
78
+ * @param {string} root
79
+ * @param {Record<string, unknown>} config
80
+ * @returns {string[]}
81
+ */
82
+ export function validateCargoLockVersions(root, config = {}) {
83
+ const paths = new Set(['Cargo.lock'])
84
+ /** @param {unknown} entries @param {string} [prefix] */
85
+ const addEntries = (entries, prefix = '') => {
86
+ if (!Array.isArray(entries)) return
87
+ for (const entry of entries) {
88
+ const value = typeof entry === 'string' ? entry : entry && typeof entry === 'object' && 'path' in entry ? entry.path : ''
89
+ if (typeof value !== 'string' || !value.endsWith('Cargo.lock')) continue
90
+ const clean = value.replace(/^\.\//, '').replace(/\\/g, '/')
91
+ paths.add(prefix ? `${prefix}/${clean}` : clean)
92
+ }
93
+ }
94
+ addEntries(config['extra-files'])
95
+ const packages = config.packages
96
+ if (packages && typeof packages === 'object' && !Array.isArray(packages)) {
97
+ for (const [directory, value] of Object.entries(packages)) {
98
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
99
+ addEntries(value['extra-files'], directory === '.' ? '' : directory.replace(/\/$/, ''))
100
+ }
101
+ }
102
+ }
103
+
104
+ /** @type {string[]} */
105
+ const errors = []
106
+ for (const relativeLockPath of paths) {
107
+ const lockPath = join(root, relativeLockPath)
108
+ const manifestPath = join(root, dirname(relativeLockPath), 'Cargo.toml')
109
+ if (!existsSync(lockPath) || !existsSync(manifestPath)) continue
110
+ const manifestLines = readFileSync(manifestPath, 'utf8').split(/\r?\n/)
111
+ const packageStart = manifestLines.findIndex((line) => line.trim() === '[package]')
112
+ if (packageStart < 0) continue
113
+ const nextSection = manifestLines.findIndex((line, index) => index > packageStart && /^\s*\[/.test(line))
114
+ const packageSection = manifestLines.slice(packageStart + 1, nextSection < 0 ? manifestLines.length : nextSection).join('\n')
115
+ const packageName = packageSection.match(/^name\s*=\s*"([^"]+)"\s*$/m)?.[1]
116
+ const packageVersion = packageSection.match(/^version\s*=\s*"([^"]+)"\s*$/m)?.[1]
117
+ if (!packageName || !packageVersion) continue
118
+ const lock = readFileSync(lockPath, 'utf8')
119
+ const lockPackage = [...lock.matchAll(/\[\[package\]\]\s+name\s*=\s*"([^"]+)"\s+version\s*=\s*"([^"]+)"/g)]
120
+ .find((match) => match[1] === packageName)
121
+ if (!lockPackage) {
122
+ errors.push(`${relativeLockPath} is missing the ${packageName} package entry.`)
123
+ } else if (lockPackage[2] !== packageVersion) {
124
+ errors.push(`${relativeLockPath} version ${lockPackage[2]} does not match ${relativeLockPath.replace(/Cargo\.lock$/, 'Cargo.toml')} version ${packageVersion}.`)
125
+ }
126
+ }
127
+ return errors
128
+ }
129
+
74
130
  /**
75
131
  * Detect release-only divergence after a rebased staging promotion. A normal
76
132
  * main-ancestor relationship must always remain promotable, even when the new
@@ -140,11 +196,11 @@ const VERSION_METADATA_FILES = new Set([
140
196
  * must carry the exact approved Release Please prefix, come from the same
141
197
  * repository, change at least one path, change no unexpected paths, and bump
142
198
  * at least one version-metadata file (or a declared extra file).
143
- * @param {{ headRef?: string, headRepo?: string, repository?: string, changedPaths?: string[], config?: Record<string, unknown> }} input
199
+ * @param {{ headRef?: string, headRepo?: string, repository?: string, changedPaths?: string[], config?: Record<string, unknown>, root?: string }} input
144
200
  * @returns {{ valid: boolean, errors: string[], changedPaths: string[] }}
145
201
  */
146
202
  export function validateGeneratedReleaseDiff(input) {
147
- const { headRef = '', headRepo = '', repository = '', changedPaths = [], config = {} } = input
203
+ const { headRef = '', headRepo = '', repository = '', changedPaths = [], config = {}, root = process.cwd() } = input
148
204
  /** @type {string[]} */
149
205
  const errors = []
150
206
  if (!isReleasePleaseHead(headRef)) {
@@ -173,6 +229,7 @@ export function validateGeneratedReleaseDiff(input) {
173
229
  if (!paths.some((path) => versionMetadata.has(path))) {
174
230
  errors.push('Generated release pull request changes no version metadata.')
175
231
  }
232
+ errors.push(...validateCargoLockVersions(root, config))
176
233
  return { valid: errors.length === 0, errors, changedPaths: paths }
177
234
  }
178
235
 
package/src/runtime.mjs CHANGED
@@ -85,6 +85,7 @@ function validation(task) {
85
85
  repository: process.env.FOUNDRY_REPOSITORY ?? '',
86
86
  changedPaths,
87
87
  config: readReleaseConfig(root),
88
+ root,
88
89
  })
89
90
  if (!result.valid) {
90
91
  for (const error of result.errors) console.error(`::error::${error}`)