code-foundry 1.48.7 → 1.48.8
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/release.yml +97 -87
- package/.github/workflows/validation-billing.yml +1 -1
- package/.github/workflows/validation-no-codeql.yml +1 -1
- package/.github/workflows/validation.yml +1 -1
- package/CHANGELOG.md +8 -8
- package/package.json +1 -1
- package/src/lib/node-package-performance.mjs +37 -2
- package/src/lib/product-quality.mjs +11 -11
- package/src/runtime-core.mjs +22 -0
|
@@ -514,96 +514,106 @@ jobs:
|
|
|
514
514
|
fi
|
|
515
515
|
|
|
516
516
|
for pr in "${release_prs[@]}"; do
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
(.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
517
|
+
# A lock-sync/synchronize transition can move the PR while Actions is
|
|
518
|
+
# being read. Re-evaluate the whole tuple and its owner; never carry
|
|
519
|
+
# eligibility across heads or retry a stable failed qualification.
|
|
520
|
+
for state_attempt in 1 2 3; do
|
|
521
|
+
pr_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
|
|
522
|
+
--json isDraft,headRefOid,headRefName \
|
|
523
|
+
--jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
|
|
524
|
+
read -r PR_DRAFT PR_HEAD PR_BRANCH <<< "$pr_state"
|
|
525
|
+
if [[ ! "$PR_DRAFT" =~ ^(true|false)$ || ! "$PR_HEAD" =~ ^[a-f0-9]{40}$ ]]; then
|
|
526
|
+
echo 'Unable to verify generated release PR state.' >&2
|
|
527
|
+
exit 1
|
|
528
|
+
fi
|
|
529
|
+
case "$PR_BRANCH" in
|
|
530
|
+
release-please--branches--main|release-please--branches--main--*) ;;
|
|
531
|
+
*) echo 'Release PR head left the generated namespace; refusing readiness mutation.' >&2; exit 1 ;;
|
|
532
|
+
esac
|
|
533
|
+
if [ "$AUTO_MERGE" = true ]; then
|
|
534
|
+
if [ "$PR_DRAFT" = false ]; then
|
|
535
|
+
# A ready head already receives synchronize validation. Preserve
|
|
536
|
+
# its live/completed owner instead of cancelling and reallocating
|
|
537
|
+
# CI every time another queued main release run prepares it.
|
|
538
|
+
runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
|
|
539
|
+
"repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
|
|
540
|
+
-f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
|
|
541
|
+
validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
|
|
542
|
+
--arg repo "$GITHUB_REPOSITORY" '
|
|
543
|
+
def active: .status == "queued" or .status == "in_progress" or
|
|
544
|
+
.status == "waiting" or .status == "pending" or .status == "requested";
|
|
545
|
+
def valid_run:
|
|
546
|
+
type == "object" and
|
|
547
|
+
(.id | type == "number" and . > 0 and floor == .) and
|
|
548
|
+
(.run_attempt | type == "number" and . >= 1 and floor == .) and
|
|
549
|
+
(.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
|
|
550
|
+
(.head_branch | type == "string" and length > 0) and
|
|
551
|
+
(.event | type == "string" and length > 0) and
|
|
552
|
+
(.repository | type == "object") and
|
|
553
|
+
(.repository.full_name | type == "string" and length > 0) and
|
|
554
|
+
(.path | type == "string" and length > 0) and
|
|
555
|
+
((active and has("conclusion") and .conclusion == null) or
|
|
556
|
+
(.status == "completed" and
|
|
557
|
+
(.conclusion == "success" or .conclusion == "failure" or
|
|
558
|
+
.conclusion == "timed_out" or .conclusion == "action_required" or
|
|
559
|
+
.conclusion == "cancelled" or .conclusion == "skipped" or
|
|
560
|
+
.conclusion == "neutral")));
|
|
561
|
+
if (.workflow_runs | type) != "array" or
|
|
562
|
+
(.total_count | type) != "number" then
|
|
563
|
+
error("Invalid validation owner response")
|
|
564
|
+
elif .total_count != (.workflow_runs | length) then
|
|
565
|
+
error("Incomplete validation owner response")
|
|
566
|
+
elif any(.workflow_runs[]; valid_run | not) then
|
|
567
|
+
error("Invalid validation run identity or state")
|
|
568
|
+
else
|
|
569
|
+
[.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
|
|
570
|
+
.head_sha == $head and .head_branch == $branch and
|
|
571
|
+
.event == "pull_request" and .repository.full_name == $repo and
|
|
572
|
+
($workflow == ".github/workflows/validation.yml" or
|
|
573
|
+
$workflow == ".github/workflows/validation_self-ci.yml")
|
|
574
|
+
)] | sort_by(.id) | last |
|
|
575
|
+
if . == null then "missing"
|
|
576
|
+
elif active or (.status == "completed" and .conclusion == "success") then "owned"
|
|
577
|
+
elif .status == "completed" and
|
|
578
|
+
(.conclusion == "failure" or .conclusion == "timed_out" or
|
|
579
|
+
.conclusion == "action_required") then "failed"
|
|
580
|
+
elif .status == "completed" and
|
|
581
|
+
(.conclusion == "cancelled" or .conclusion == "skipped" or
|
|
582
|
+
.conclusion == "neutral") then "missing"
|
|
583
|
+
else error("Unconfirmed validation owner state") end
|
|
584
|
+
end' <<< "$runs")
|
|
585
|
+
fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
|
|
586
|
+
--json isDraft,headRefOid,headRefName \
|
|
587
|
+
--jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
|
|
588
|
+
if [ "$fresh_state" != "$pr_state" ]; then
|
|
589
|
+
if [ "$state_attempt" -eq 3 ]; then
|
|
590
|
+
echo 'Release PR state did not stabilize during validation lookup; refusing readiness mutation.' >&2
|
|
591
|
+
exit 1
|
|
592
|
+
fi
|
|
593
|
+
echo "Release PR #$pr changed during validation lookup; reading its complete state and owner again."
|
|
594
|
+
continue
|
|
595
|
+
fi
|
|
596
|
+
if [ "$validation" = owned ]; then
|
|
597
|
+
echo "Release PR #$pr already has current-head validation; preserving readiness."
|
|
598
|
+
break
|
|
599
|
+
fi
|
|
600
|
+
if [ "$validation" = failed ]; then
|
|
601
|
+
echo "Current-head validation failed for release PR #$pr; resolve or explicitly retry it before release." >&2
|
|
602
|
+
exit 1
|
|
603
|
+
fi
|
|
604
|
+
# A missing, cancelled or skipped owner still needs the existing
|
|
605
|
+
# explicit ready_for_review transition with the validated token.
|
|
606
|
+
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
591
607
|
fi
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
608
|
+
gh pr ready "$pr" --repo "$GITHUB_REPOSITORY"
|
|
609
|
+
else
|
|
610
|
+
if [ "$PR_DRAFT" = false ]; then
|
|
611
|
+
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
595
612
|
fi
|
|
596
|
-
|
|
597
|
-
# explicit ready_for_review transition with the validated token.
|
|
598
|
-
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
613
|
+
echo "::notice title=Manual release merge required::Release Please created or updated a version pull request. No valid automation token is available (absent or rejected by GitHub), so release PRs are left in draft and need manual readiness before validation and merge workflows trigger."
|
|
599
614
|
fi
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if [ "$PR_DRAFT" = false ]; then
|
|
603
|
-
gh pr ready --undo "$pr" --repo "$GITHUB_REPOSITORY"
|
|
604
|
-
fi
|
|
605
|
-
echo "::notice title=Manual release merge required::Release Please created or updated a version pull request. No valid automation token is available (absent or rejected by GitHub), so release PRs are left in draft and need manual readiness before validation and merge workflows trigger."
|
|
606
|
-
fi
|
|
615
|
+
break
|
|
616
|
+
done
|
|
607
617
|
done
|
|
608
618
|
- name: Leave release pull request for manual merge
|
|
609
619
|
if: steps.release.outputs.prs_created == 'true' && steps.credentials.outputs.auto_merge != 'true'
|
|
@@ -1161,8 +1161,8 @@ jobs:
|
|
|
1161
1161
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
1162
1162
|
with:
|
|
1163
1163
|
persist-credentials: false
|
|
1164
|
+
# The content audit needs historical blobs after checkout removes credentials.
|
|
1164
1165
|
fetch-depth: 0
|
|
1165
|
-
filter: blob:none
|
|
1166
1166
|
- name: Validate generated release diff
|
|
1167
1167
|
if: ${{ inputs.mode == 'release' }}
|
|
1168
1168
|
env:
|
|
@@ -205,8 +205,8 @@ jobs:
|
|
|
205
205
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
206
206
|
with:
|
|
207
207
|
persist-credentials: false
|
|
208
|
+
# The content audit needs historical blobs after checkout removes credentials.
|
|
208
209
|
fetch-depth: 0
|
|
209
|
-
filter: blob:none
|
|
210
210
|
- name: Validate generated release diff
|
|
211
211
|
if: ${{ inputs.mode == 'release' }}
|
|
212
212
|
env:
|
|
@@ -216,8 +216,8 @@ jobs:
|
|
|
216
216
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
217
217
|
with:
|
|
218
218
|
persist-credentials: false
|
|
219
|
+
# The content audit needs historical blobs after checkout removes credentials.
|
|
219
220
|
fetch-depth: 0
|
|
220
|
-
filter: blob:none
|
|
221
221
|
- name: Validate generated release diff
|
|
222
222
|
if: ${{ inputs.mode == 'release' }}
|
|
223
223
|
env:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.48.8](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.7...v1.48.8) (2026-10-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **release:** hydrate audit blobs and refresh changing PR state ([#779](https://github.com/0xPlayerOne/code-foundry/issues/779)) ([8734ed7](https://github.com/0xPlayerOne/code-foundry/commit/8734ed77d159818cd5ff613f40a5dab796789d8a))
|
|
9
|
+
|
|
3
10
|
## [1.48.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.6...v1.48.7) (2026-10-23)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -133,16 +140,9 @@
|
|
|
133
140
|
|
|
134
141
|
* **billing:** join fast-lane receipts in a second parallel group ([#742](https://github.com/0xPlayerOne/code-foundry/issues/742)) ([c63a39c](https://github.com/0xPlayerOne/code-foundry/commit/c63a39cf5e236d6747bd85cf10b04bd10f690b11))
|
|
135
142
|
|
|
136
|
-
## [1.46.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.0...v1.46.1) (2026-10-07)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
### Bug Fixes
|
|
140
|
-
|
|
141
|
-
* **qualification:** skip parallel-syntax files until actionlint supports them ([#740](https://github.com/0xPlayerOne/code-foundry/issues/740)) ([db98c7f](https://github.com/0xPlayerOne/code-foundry/commit/db98c7f40cae080cdc9dcab7baac4976e5e0656e))
|
|
142
|
-
|
|
143
143
|
<!-- code-foundry: packaged changelog -->
|
|
144
144
|
|
|
145
145
|
---
|
|
146
146
|
|
|
147
|
-
This packaged changelog lists the 20 most recent releases;
|
|
147
|
+
This packaged changelog lists the 20 most recent releases; 256 older releases are omitted to keep the package small.
|
|
148
148
|
See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.48.
|
|
3
|
+
"version": "1.48.8",
|
|
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": {
|
|
@@ -17,6 +17,42 @@ const METRICS = new Set([
|
|
|
17
17
|
'productionDependencyCount',
|
|
18
18
|
])
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* npm 12 keys pack reports by package name; older npm versions return an array.
|
|
22
|
+
* Count reports before validating their shape so an extra malformed entry cannot be ignored.
|
|
23
|
+
* @param {unknown} value
|
|
24
|
+
* @returns {{filename: string, size: number, unpackedSize: number, files: Array<{path: string}>}}
|
|
25
|
+
*/
|
|
26
|
+
export function singleNpmPackReport(value) {
|
|
27
|
+
let reports
|
|
28
|
+
if (Array.isArray(value)) reports = value
|
|
29
|
+
else if (value && typeof value === 'object') {
|
|
30
|
+
const object = /** @type {Record<string, unknown>} */ (value)
|
|
31
|
+
reports = typeof object.filename === 'string' ? [object] : Object.values(object)
|
|
32
|
+
} else reports = []
|
|
33
|
+
if (reports.length !== 1) throw new Error('Expected one npm pack report.')
|
|
34
|
+
const packed = reports[0]
|
|
35
|
+
if (!packed || typeof packed !== 'object' || Array.isArray(packed))
|
|
36
|
+
throw new Error('npm pack returned no package result.')
|
|
37
|
+
if (typeof packed.filename !== 'string' || packed.filename.length === 0)
|
|
38
|
+
throw new Error('npm pack result is missing filename.')
|
|
39
|
+
for (const field of ['size', 'unpackedSize'])
|
|
40
|
+
if (typeof packed[field] !== 'number' || !Number.isFinite(packed[field]) || packed[field] < 0)
|
|
41
|
+
throw new Error(`npm pack result is missing non-negative numeric ${field}.`)
|
|
42
|
+
if (
|
|
43
|
+
!Array.isArray(packed.files) ||
|
|
44
|
+
!packed.files.every(
|
|
45
|
+
(/** @type {unknown} */ file) =>
|
|
46
|
+
file !== null &&
|
|
47
|
+
typeof file === 'object' &&
|
|
48
|
+
!Array.isArray(file) &&
|
|
49
|
+
typeof (/** @type {{path?: unknown}} */ (file).path) === 'string'
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
throw new Error('npm pack result is missing a valid files list.')
|
|
53
|
+
return packed
|
|
54
|
+
}
|
|
55
|
+
|
|
20
56
|
/** @param {number[]} values @param {number} percent */
|
|
21
57
|
export function percentile(values, percent) {
|
|
22
58
|
if (values.length === 0) throw new Error('percentile requires at least one value')
|
|
@@ -59,8 +95,7 @@ export function runNodePackagePerformance(
|
|
|
59
95
|
? measureImports(root, String(policy.controlImport), samples)
|
|
60
96
|
: null
|
|
61
97
|
const pack = npmJson(root, ['pack', '--dry-run', '--json', '--ignore-scripts'], 'npm pack')
|
|
62
|
-
const packed =
|
|
63
|
-
if (!packed || typeof packed !== 'object') throw new Error('npm pack returned no package result.')
|
|
98
|
+
const packed = singleNpmPackReport(pack)
|
|
64
99
|
/** @type {Array<{path?: string}>} */
|
|
65
100
|
const files = Array.isArray(packed.files) ? packed.files : []
|
|
66
101
|
const productionDependencyCount = npmProductionDependencyCount(root)
|
|
@@ -17,6 +17,7 @@ import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
|
17
17
|
import { pathToFileURL } from 'node:url'
|
|
18
18
|
import { tmpdir } from 'node:os'
|
|
19
19
|
import { gzipSync } from 'node:zlib'
|
|
20
|
+
import { singleNpmPackReport } from './node-package-performance.mjs'
|
|
20
21
|
|
|
21
22
|
/** @typedef {Record<string, any>} Profile */
|
|
22
23
|
/** @param {unknown} condition @param {string} message @returns {asserts condition} */
|
|
@@ -602,24 +603,23 @@ export function checkPackage(root, profile, output) {
|
|
|
602
603
|
// Pack outside the consumer repository so test installations cannot become package input.
|
|
603
604
|
const temporary = mkdtempSync(join(tmpdir(), 'foundry-package-quality-'))
|
|
604
605
|
try {
|
|
605
|
-
const pack =
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
606
|
+
const pack = singleNpmPackReport(
|
|
607
|
+
JSON.parse(
|
|
608
|
+
runQualityCommand(
|
|
609
|
+
['npm', 'pack', '--ignore-scripts', '--json', '--pack-destination', temporary],
|
|
610
|
+
root,
|
|
611
|
+
output
|
|
612
|
+
)
|
|
610
613
|
)
|
|
611
614
|
)
|
|
615
|
+
requireValue(/^[A-Za-z0-9._-]+\.tgz$/.test(pack.filename), 'Expected one package archive')
|
|
612
616
|
requireValue(
|
|
613
|
-
|
|
614
|
-
'Expected one package archive'
|
|
615
|
-
)
|
|
616
|
-
requireValue(
|
|
617
|
-
pack[0].files.every(
|
|
617
|
+
pack.files.every(
|
|
618
618
|
(/** @type {{path:string}} */ file) => !/^\.code-foundry(?:\/|$)/.test(file.path)
|
|
619
619
|
),
|
|
620
620
|
'Exclude .code-foundry quality evidence from the published package'
|
|
621
621
|
)
|
|
622
|
-
const archive = ownedPath(temporary, pack
|
|
622
|
+
const archive = ownedPath(temporary, pack.filename)
|
|
623
623
|
const archiveSha256 = createHash('sha256').update(readFileSync(archive)).digest('hex')
|
|
624
624
|
const consumer = join(temporary, 'consumer')
|
|
625
625
|
mkdirSync(consumer)
|
package/src/runtime-core.mjs
CHANGED
|
@@ -404,6 +404,28 @@ function validation(task) {
|
|
|
404
404
|
{ cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }
|
|
405
405
|
)
|
|
406
406
|
: undefined
|
|
407
|
+
if (diff && diff.status !== 0) {
|
|
408
|
+
// Git can relay arbitrary remote messages, URLs and credentials on stderr. Emit only
|
|
409
|
+
// bounded, recognised failure facts; never replay remote text as workflow annotations.
|
|
410
|
+
const stderr = (diff.stderr ?? '').slice(0, 8192)
|
|
411
|
+
const missingBlob = /could not fetch ([0-9a-f]{40,64}) from promisor remote/i.exec(stderr)
|
|
412
|
+
const reason = missingBlob
|
|
413
|
+
? `historical blob ${missingBlob[1]} unavailable from promisor remote`
|
|
414
|
+
: /authentication failed|could not read (?:username|password)|terminal prompts disabled/i.test(
|
|
415
|
+
stderr
|
|
416
|
+
)
|
|
417
|
+
? 'authentication unavailable'
|
|
418
|
+
: /could not read from remote repository/i.test(stderr)
|
|
419
|
+
? 'remote repository unavailable'
|
|
420
|
+
: /bad object|unknown revision|invalid symmetric difference|ambiguous argument/i.test(
|
|
421
|
+
stderr
|
|
422
|
+
)
|
|
423
|
+
? 'revision unavailable'
|
|
424
|
+
: 'Git error details omitted'
|
|
425
|
+
console.error(
|
|
426
|
+
`::error::Git content diff failed (status ${diff.status ?? 'unavailable'}): ${reason}.`
|
|
427
|
+
)
|
|
428
|
+
}
|
|
407
429
|
const result = validateGeneratedReleaseDiff({
|
|
408
430
|
headRef: process.env.FOUNDRY_HEAD_REF ?? '',
|
|
409
431
|
headRepo: process.env.FOUNDRY_HEAD_REPO ?? '',
|