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.
@@ -514,96 +514,106 @@ jobs:
514
514
  fi
515
515
 
516
516
  for pr in "${release_prs[@]}"; do
517
- pr_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
518
- --json isDraft,headRefOid,headRefName \
519
- --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
520
- read -r PR_DRAFT PR_HEAD PR_BRANCH <<< "$pr_state"
521
- if [[ ! "$PR_DRAFT" =~ ^(true|false)$ || ! "$PR_HEAD" =~ ^[a-f0-9]{40}$ ]]; then
522
- echo 'Unable to verify generated release PR state.' >&2
523
- exit 1
524
- fi
525
- case "$PR_BRANCH" in
526
- release-please--branches--main|release-please--branches--main--*) ;;
527
- *) echo 'Release PR head left the generated namespace; refusing readiness mutation.' >&2; exit 1 ;;
528
- esac
529
- if [ "$AUTO_MERGE" = true ]; then
530
- if [ "$PR_DRAFT" = false ]; then
531
- # A ready head already receives synchronize validation. Preserve
532
- # its live/completed owner instead of cancelling and reallocating
533
- # CI every time another queued main release run prepares it.
534
- runs=$(GH_TOKEN="$VALIDATION_TOKEN" gh api \
535
- "repos/$GITHUB_REPOSITORY/actions/runs" --method GET \
536
- -f event=pull_request -f head_sha="$PR_HEAD" -f per_page=100)
537
- validation=$(jq -er --arg head "$PR_HEAD" --arg branch "$PR_BRANCH" \
538
- --arg repo "$GITHUB_REPOSITORY" '
539
- def active: .status == "queued" or .status == "in_progress" or
540
- .status == "waiting" or .status == "pending" or .status == "requested";
541
- def valid_run:
542
- type == "object" and
543
- (.id | type == "number" and . > 0 and floor == .) and
544
- (.run_attempt | type == "number" and . >= 1 and floor == .) and
545
- (.head_sha | type == "string" and test("^[a-f0-9]{40}$")) and
546
- (.head_branch | type == "string" and length > 0) and
547
- (.event | type == "string" and length > 0) and
548
- (.repository | type == "object") and
549
- (.repository.full_name | type == "string" and length > 0) and
550
- (.path | type == "string" and length > 0) and
551
- ((active and has("conclusion") and .conclusion == null) or
552
- (.status == "completed" and
553
- (.conclusion == "success" or .conclusion == "failure" or
554
- .conclusion == "timed_out" or .conclusion == "action_required" or
555
- .conclusion == "cancelled" or .conclusion == "skipped" or
556
- .conclusion == "neutral")));
557
- if (.workflow_runs | type) != "array" or
558
- (.total_count | type) != "number" then
559
- error("Invalid validation owner response")
560
- elif .total_count != (.workflow_runs | length) then
561
- error("Incomplete validation owner response")
562
- elif any(.workflow_runs[]; valid_run | not) then
563
- error("Invalid validation run identity or state")
564
- else
565
- [.workflow_runs[] | (.path | split("@")[0]) as $workflow | select(
566
- .head_sha == $head and .head_branch == $branch and
567
- .event == "pull_request" and .repository.full_name == $repo and
568
- ($workflow == ".github/workflows/validation.yml" or
569
- $workflow == ".github/workflows/validation_self-ci.yml")
570
- )] | sort_by(.id) | last |
571
- if . == null then "missing"
572
- elif active or (.status == "completed" and .conclusion == "success") then "owned"
573
- elif .status == "completed" and
574
- (.conclusion == "failure" or .conclusion == "timed_out" or
575
- .conclusion == "action_required") then "failed"
576
- elif .status == "completed" and
577
- (.conclusion == "cancelled" or .conclusion == "skipped" or
578
- .conclusion == "neutral") then "missing"
579
- else error("Unconfirmed validation owner state") end
580
- end' <<< "$runs")
581
- fresh_state=$(gh pr view "$pr" --repo "$GITHUB_REPOSITORY" \
582
- --json isDraft,headRefOid,headRefName \
583
- --jq '[.isDraft, .headRefOid, .headRefName] | @tsv')
584
- if [ "$fresh_state" != "$pr_state" ]; then
585
- echo 'Release PR state changed during validation lookup; refusing a stale readiness mutation.' >&2
586
- exit 1
587
- fi
588
- if [ "$validation" = owned ]; then
589
- echo "Release PR #$pr already has current-head validation; preserving readiness."
590
- continue
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
- if [ "$validation" = failed ]; then
593
- echo "Current-head validation failed for release PR #$pr; resolve or explicitly retry it before release." >&2
594
- exit 1
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
- # A missing, cancelled or skipped owner still needs the existing
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
- gh pr ready "$pr" --repo "$GITHUB_REPOSITORY"
601
- else
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; 255 older releases are omitted to keep the package small.
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.7",
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 = Array.isArray(pack) ? pack[0] : pack
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 = JSON.parse(
606
- runQualityCommand(
607
- ['npm', 'pack', '--ignore-scripts', '--json', '--pack-destination', temporary],
608
- root,
609
- output
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
- Array.isArray(pack) && pack.length === 1 && /^[A-Za-z0-9._-]+\.tgz$/.test(pack[0].filename),
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[0].filename)
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)
@@ -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 ?? '',