code-foundry 1.28.9 → 1.28.10
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/workflows/draft-pr_self-ci.yml +1 -1
- package/.github/workflows/opencode-security_self-ci.yml +1 -1
- package/.github/workflows/release-pr_self-ci.yml +1 -1
- package/.github/workflows/release_self-ci.yml +1 -1
- package/.github/workflows/validation_self-ci.yml +1 -1
- package/CHANGELOG.md +7 -0
- package/docs/CONFIGURATION.md +18 -0
- package/package.json +1 -1
- package/src/commands/sync.mjs +6 -1
- package/src/runtime-core.mjs +80 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.28.10](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.9...v1.28.10) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** dedupe caller names and keep CodeQL Rust shards aligned ([#613](https://github.com/0xPlayerOne/code-foundry/issues/613)) ([8000e5e](https://github.com/0xPlayerOne/code-foundry/commit/8000e5e152ccc7e657933b55eb5b82a278762176))
|
|
9
|
+
|
|
3
10
|
## [1.28.9](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.8...v1.28.9) (2026-09-11)
|
|
4
11
|
|
|
5
12
|
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -215,6 +215,24 @@ Each shard must contain tracked Rust source. Absolute paths, parent traversal,
|
|
|
215
215
|
duplicates, empty scopes, and more than eight shards are rejected. Use
|
|
216
216
|
`["all"]` when complete, non-overlapping source scopes are not available.
|
|
217
217
|
|
|
218
|
+
Every lane rendered from this configuration (pull-request validation, the
|
|
219
|
+
default-branch scan, the scheduled audit, and merge queues) receives the same
|
|
220
|
+
shard list, and the CodeQL Detect job fails closed when caller workflows drift
|
|
221
|
+
apart. Keep them aligned: each shard uploads a SARIF category keyed by a hash
|
|
222
|
+
of the shard scope, GitHub code scanning baselines every category it has seen
|
|
223
|
+
on the default branch, and the code-scanning merge gate waits for results in
|
|
224
|
+
every tracked category. Two practical consequences:
|
|
225
|
+
|
|
226
|
+
- Treat `codeql_rust_shards` as add-only while the gate tracks categories.
|
|
227
|
+
Removing a shard orphans its category on the default branch, and pull
|
|
228
|
+
requests then stall on "Code scanning is still expecting N results" until
|
|
229
|
+
the category is covered again.
|
|
230
|
+
- To retire a category for real, delete its code-scanning analyses (Code
|
|
231
|
+
security → Code scanning → analyses, or the code-scanning analysis
|
|
232
|
+
deletion API) after removing the shard, then re-run `sync`. Covering retired
|
|
233
|
+
categories in the shard list keeps the gate green but re-runs their analysis
|
|
234
|
+
on every pull request and is not a substitute for cleanup.
|
|
235
|
+
|
|
218
236
|
## Cloudflare Workers
|
|
219
237
|
|
|
220
238
|
Repositories that deploy to Cloudflare Workers can use the opt-in verified
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.10",
|
|
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/sync.mjs
CHANGED
|
@@ -1003,7 +1003,12 @@ function escapeRegExp(value) {
|
|
|
1003
1003
|
*/
|
|
1004
1004
|
export function isGeneratedEventCaller(content, stem, runtimeRepository) {
|
|
1005
1005
|
const text = Buffer.isBuffer(content) ? content.toString('utf8') : String(content)
|
|
1006
|
-
|
|
1006
|
+
// Generated callers carried the shared display name "Code Foundry" before
|
|
1007
|
+
// issue 609 gave each caller a unique name; both renders must stay
|
|
1008
|
+
// recognizable so a topology switch can still prune stale promotion callers.
|
|
1009
|
+
const names = ['Code Foundry', ...(stem === 'release-pr' ? ['Code Foundry Promotion'] : [])]
|
|
1010
|
+
if (!names.some((name) => new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(text)))
|
|
1011
|
+
return false
|
|
1007
1012
|
if (/^\s*(runs-on|steps):/m.test(text)) return false
|
|
1008
1013
|
// Older generated staging-promotion callers predate the explicit runtime
|
|
1009
1014
|
// repository input. They are still safe to identify structurally by their
|
package/src/runtime-core.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
5
12
|
import { resolve } from 'node:path'
|
|
6
13
|
import { spawnSync } from 'node:child_process'
|
|
7
14
|
import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
|
|
@@ -880,6 +887,75 @@ function isPublicRepository() {
|
|
|
880
887
|
return visibility ? visibility === 'public' : process.env.REPO_FOUNDRY_PRIVATE !== 'true'
|
|
881
888
|
}
|
|
882
889
|
|
|
890
|
+
/** Split a workflow file's `jobs:` entries into [job id, block text] pairs.
|
|
891
|
+
* Caller files are re-parsed here instead of trusting the sync renderer so
|
|
892
|
+
* hand-edited or stale generated callers are still validated at run time.
|
|
893
|
+
* @param {string} text @returns {Array<[string, string]>}
|
|
894
|
+
*/
|
|
895
|
+
function workflowJobBlocks(text) {
|
|
896
|
+
const lines = text.split('\n')
|
|
897
|
+
/** @type {Array<[string, string]>} */
|
|
898
|
+
const blocks = []
|
|
899
|
+
let index = 0
|
|
900
|
+
while (index < lines.length) {
|
|
901
|
+
const match = lines[index].match(/^ ([A-Za-z0-9_-]+):\s*$/)
|
|
902
|
+
if (!match) {
|
|
903
|
+
index += 1
|
|
904
|
+
continue
|
|
905
|
+
}
|
|
906
|
+
const start = index
|
|
907
|
+
index += 1
|
|
908
|
+
while (index < lines.length && !/^ [A-Za-z0-9_-]+:\s*$/.test(lines[index])) index += 1
|
|
909
|
+
blocks.push([match[1], lines.slice(start, index).join('\n')])
|
|
910
|
+
}
|
|
911
|
+
return blocks
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* The Rust SARIF category embeds a hash of each shard scope, and GitHub code
|
|
916
|
+
* scanning baselines every category it has seen on the default branch: when
|
|
917
|
+
* two lanes calling the CodeQL workflow report different shard sets, each
|
|
918
|
+
* lane uploads categories the other never produces and the code-scanning
|
|
919
|
+
* merge gate waits forever (issue 609). Fail closed before any analysis runs.
|
|
920
|
+
*/
|
|
921
|
+
function assertRustShardsAligned() {
|
|
922
|
+
const directory = resolve(root, '.github/workflows')
|
|
923
|
+
if (!existsSync(directory)) return
|
|
924
|
+
/** @type {Map<string, string>} */
|
|
925
|
+
const lanes = new Map()
|
|
926
|
+
for (const file of readdirSync(directory)
|
|
927
|
+
.filter((name) => name.endsWith('.yml'))
|
|
928
|
+
.toSorted()) {
|
|
929
|
+
for (const [job, block] of workflowJobBlocks(readFileSync(resolve(directory, file), 'utf8'))) {
|
|
930
|
+
// Every lane that ultimately runs CodeQL analysis counts: direct
|
|
931
|
+
// codeql.yml callers and callers of the validation orchestrators whose
|
|
932
|
+
// rust-shards input flows through to the analysis matrix.
|
|
933
|
+
if (!/^\s+uses:\s*\S*\/(codeql|validation|validation-no-codeql)\.yml(@\S+)?\s*$/m.test(block))
|
|
934
|
+
continue
|
|
935
|
+
const raw = block.match(/^\s+rust-shards:\s*(.+?)\s*$/m)?.[1] ?? '["all"]'
|
|
936
|
+
// Unresolved caller expressions cannot be evaluated here; the literal
|
|
937
|
+
// shard list of the workflow that passes them is what gets compared.
|
|
938
|
+
if (raw.includes('${{')) continue
|
|
939
|
+
let shards = raw.replace(/^['"]|['"]$/g, '')
|
|
940
|
+
try {
|
|
941
|
+
shards = JSON.stringify(JSON.parse(shards).toSorted())
|
|
942
|
+
} catch {
|
|
943
|
+
// Compare unparsable values verbatim; alignment still fails loudly.
|
|
944
|
+
}
|
|
945
|
+
lanes.set(`${file} (${job})`, shards)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const distinct = new Set(lanes.values())
|
|
949
|
+
if (distinct.size <= 1) return
|
|
950
|
+
const detail = [...lanes].map(([lane, shards]) => `${lane}: ${shards}`).join(', ')
|
|
951
|
+
throw new Error(
|
|
952
|
+
`Rust CodeQL shard sets drift between lanes calling codeql.yml (${detail}). ` +
|
|
953
|
+
'GitHub code scanning tracks every uploaded category, so every lane must pass the same rust-shards list. ' +
|
|
954
|
+
'Re-render the callers with `npx code-foundry sync`, keep codeql_rust_shards aligned across callers, and retire ' +
|
|
955
|
+
'removed categories by deleting their code-scanning analyses (see docs/CONFIGURATION.md).'
|
|
956
|
+
)
|
|
957
|
+
}
|
|
958
|
+
|
|
883
959
|
function codeql() {
|
|
884
960
|
const enabled =
|
|
885
961
|
featureEnabled('codeql') &&
|
|
@@ -894,6 +970,9 @@ function codeql() {
|
|
|
894
970
|
}
|
|
895
971
|
return
|
|
896
972
|
}
|
|
973
|
+
// Detect lane drift before any analysis uploads a category the other lanes
|
|
974
|
+
// never produce; a stall surfaces only later as a blocked merge gate.
|
|
975
|
+
assertRustShardsAligned()
|
|
897
976
|
const available = []
|
|
898
977
|
if (existsSync(resolve(root, '.github/workflows'))) available.push('actions')
|
|
899
978
|
if (hasLanguage('typescript') || hasLanguage('javascript'))
|