code-foundry 1.28.2 → 1.28.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.
@@ -12,7 +12,7 @@ dependency_review: auto
12
12
  package_manager: bun
13
13
  toolchain: auto
14
14
  runtime_repository: 0xPlayerOne/code-foundry
15
- runtime_ref: v1.9.6
15
+ runtime_ref: v1.28.2
16
16
  release_type: node
17
17
  npm_publish: true
18
18
  license: agpl-3.0-or-later
@@ -41,9 +41,9 @@ jobs:
41
41
  # The partial clone (filter: blob:none) defers blob downloads, so the
42
42
  # merge-base/diff comparison below can trigger a promisor fetch. With
43
43
  # persist-credentials disabled, git has no auth for that fetch; configure
44
- # the credential helper with the short-lived workflow token. NiftyLeague
45
- # rejects long-lived fine-grained automation tokens with HTTP 403 even
46
- # on REST, so all read-only operations use github.token. It is used
44
+ # the credential helper with the short-lived workflow token. Some
45
+ # organizations reject long-lived fine-grained automation tokens with
46
+ # HTTP 403 even on REST, so all read-only operations use github.token. It is used
47
47
  # only for GitHub operations owned by this workflow. The job's
48
48
  # contents: write permission is required later for the isolated,
49
49
  # deterministic promotion ref; protected main and staging are never
@@ -59,11 +59,11 @@ jobs:
59
59
  run: |
60
60
  set -euo pipefail
61
61
  # Every lookup in this step is read-only and uses the short-lived
62
- # workflow token: NiftyLeague rejects long-lived fine-grained
63
- # automation tokens with HTTP 403 even on REST. The
62
+ # workflow token because some organizations reject long-lived
63
+ # fine-grained automation tokens with HTTP 403 even on REST. The
64
64
  # existing-promotion-PR lookup uses authenticated REST (gh api)
65
- # rather than gh pr list, which issues a GraphQL query that
66
- # NiftyLeague's long-lived fine-grained token policy rejects. The
65
+ # rather than gh pr list, which issues a GraphQL query that an
66
+ # organization's long-lived fine-grained token policy rejects. The
67
67
  # REST query qualifies head as owner:branch; GitHub otherwise ignores
68
68
  # the incomplete head filter and can count an unrelated open PR. The
69
69
  # exact base/head/state filter and per_page=1 cap keep existing at 0
@@ -247,8 +247,8 @@ jobs:
247
247
  EOF
248
248
 
249
249
  # Creation uses authenticated REST (gh api POST) rather than
250
- # `gh pr create`, which issues a GraphQL mutation that
251
- # NiftyLeague's long-lived fine-grained token policy rejects. The
250
+ # `gh pr create`, which issues a GraphQL mutation that an
251
+ # organization's long-lived fine-grained token policy rejects. The
252
252
  # REST payload keeps the exact base/head/title/body fields, always
253
253
  # opens the automated promotion as a draft, and stays fail-closed
254
254
  # through the create-race fallback below.
@@ -263,8 +263,8 @@ jobs:
263
263
  )
264
264
  echo "::notice title=Manual PR readiness required::Created a draft promotion PR. Mark it ready for review to start validation."
265
265
 
266
- # Prefer the configured automation token for the write, but
267
- # NiftyLeague rejects long-lived fine-grained tokens with HTTP 403
266
+ # Prefer the configured automation token for the write, but some
267
+ # organizations reject long-lived fine-grained tokens with HTTP 403
268
268
  # even on REST, so retry the POST once with the short-lived workflow
269
269
  # token before the create-race fallback. With no automation token
270
270
  # configured, the first attempt already runs as github.token, so the
@@ -286,7 +286,7 @@ jobs:
286
286
  # the first attempt already ran as github.token. The lookup uses
287
287
  # the same authenticated REST query as the Check step, always with
288
288
  # the short-lived workflow token; gh pr list issues a GraphQL query
289
- # that NiftyLeague's long-lived fine-grained token policy rejects.
289
+ # that an organization's long-lived fine-grained token policy rejects.
290
290
  EXISTING_NUMBER=$(GH_TOKEN="${{ github.token }}" gh api --method GET \
291
291
  "repos/${GITHUB_REPOSITORY}/pulls" \
292
292
  --field base=main \
@@ -18,6 +18,11 @@ on:
18
18
  required: false
19
19
  type: string
20
20
  default: v1.0.5
21
+ git-workflow:
22
+ description: Repository topology; staging reconciliation is opt-in for staging-release.
23
+ required: false
24
+ type: string
25
+ default: direct
21
26
  billing-pause-bypass:
22
27
  description: Allow this explicitly dispatched release to run while other CI remains paused.
23
28
  required: false
@@ -73,7 +78,7 @@ jobs:
73
78
  name: Version
74
79
  if: vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true
75
80
  runs-on: ${{ inputs.runner }}
76
- timeout-minutes: 20
81
+ timeout-minutes: 45
77
82
  concurrency:
78
83
  group: code-foundry-release-${{ github.repository }}-${{ github.ref }}
79
84
  cancel-in-progress: false
@@ -172,11 +177,11 @@ jobs:
172
177
  GH_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN }}
173
178
  run: |
174
179
  set -euo pipefail
175
- # NiftyLeague rejects long-lived fine-grained automation tokens with
176
- # HTTP 403 even on REST (observed in the release-pr promotion flow),
177
- # so the configured automation token is validated against the current
178
- # repository before any write. gh api authenticates through the
179
- # GH_TOKEN environment variable and never prints the token; the
180
+ # Some organizations reject long-lived fine-grained automation tokens
181
+ # with HTTP 403 even on REST, so the configured automation token is
182
+ # validated against the current repository before any write. gh api
183
+ # authenticates through the GH_TOKEN environment variable and never
184
+ # prints the token; the
180
185
  # response body is discarded and only the exit status selects the
181
186
  # credential for the rest of the job.
182
187
  if [ "$HAS_AUTOMATION_TOKEN" = true ] && gh api "repos/${GITHUB_REPOSITORY}" --jq '.full_name' >/dev/null 2>&1; then
@@ -315,7 +320,7 @@ jobs:
315
320
  echo "Waiting for release PR #$pr to become mergeable under branch policy"
316
321
  merge_state=UNKNOWN
317
322
  mergeable=UNKNOWN
318
- for attempt in $(seq 1 30); do
323
+ for attempt in $(seq 1 90); do
319
324
  : "$attempt"
320
325
  if read -r merge_state mergeable < <(
321
326
  gh pr view "$pr" \
@@ -372,16 +377,18 @@ jobs:
372
377
  # creating a release. Reconciliation is only meaningful after a release
373
378
  # commit/tag is actually created; otherwise the promotion's source changes
374
379
  # are incorrectly rejected as non-metadata commits.
375
- if: inputs['defer-publication'] != true && (vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true) && needs.release.result == 'success' && needs.release.outputs.release_created == 'true'
380
+ if: inputs['git-workflow'] == 'staging-release' && inputs['defer-publication'] != true && (vars.CI_BILLING_PAUSED != 'true' || inputs['billing-pause-bypass'] == true) && needs.release.result == 'success' && needs.release.outputs.release_created == 'true'
376
381
  runs-on: ${{ inputs.runner }}
377
382
  timeout-minutes: 15
378
383
  permissions:
379
384
  contents: write
380
385
  pull-requests: write
381
386
  env:
382
- # Reconciliation is the sole staging-ruleset bypass. Keep GH_TOKEN scoped to
383
- # the repository's own GitHub Actions integration token; deploy key use is
384
- # optional and only activated when STAGING_DEPLOY_KEY is provided.
387
+ # Reconciliation is the sole staging-ruleset bypass and is enabled only
388
+ # for repositories that explicitly opt into the staging-release topology.
389
+ # Keep GH_TOKEN scoped to the repository's own GitHub Actions integration
390
+ # token; deploy key use is optional and only activated when
391
+ # STAGING_DEPLOY_KEY is provided.
385
392
  GH_TOKEN: ${{ github.token }}
386
393
  STAGING_DEPLOY_KEY_PRESENT: ${{ secrets.STAGING_DEPLOY_KEY != '' }}
387
394
  steps:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.28.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.3...v1.28.4) (2026-09-10)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **release:** scope reconciliation to staging topology ([bfba0c1](https://github.com/0xPlayerOne/code-foundry/commit/bfba0c147ea6281c47754938d6711d3e5e0335a2))
9
+
10
+ ## [1.28.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.2...v1.28.3) (2026-09-10)
11
+
12
+
13
+ ### Maintenance
14
+
15
+ * **config:** pin Code Foundry runtime to v1.28.2 ([#599](https://github.com/0xPlayerOne/code-foundry/issues/599)) ([ed61358](https://github.com/0xPlayerOne/code-foundry/commit/ed6135813e12e1f5410066a92183f2f2d18dcc8c))
16
+
3
17
  ## [1.28.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.28.1...v1.28.2) (2026-09-10)
4
18
 
5
19
 
@@ -165,7 +165,9 @@ release_merge_strategy: rebase
165
165
  Use `simple` with `version.txt` when no package manifest exists. Use `none` to
166
166
  skip automated releases. `npm_publish` affects generated consumer release
167
167
  callers; Code Foundry's own repository uses the qualified publication path
168
- described in [Qualified publication](qualified-publication.md).
168
+ described in [Qualified publication](qualified-publication.md). A self-sync of
169
+ the Code Foundry source does not advance its own `runtime_ref`; this prevents a
170
+ self-referencing runtime pin from creating a release loop.
169
171
 
170
172
  ## Synchronization and extensions
171
173
 
package/docs/RELEASES.md CHANGED
@@ -74,8 +74,11 @@ The generated commit preserves the staged tip's conventional subject so
74
74
  Release Please still detects the appropriate release type after promotion.
75
75
 
76
76
  The `staging` → `main` reconciliation exists only in the `staging-release`
77
- topology. Patch-equivalent divergence between `main` and `staging` is treated
78
- as aligned. When `staging` has pending commits that are not yet represented on
77
+ topology. The reusable release workflow defaults to `direct` and requires its
78
+ caller to opt into `staging-release`, so a stale remote `staging` branch cannot
79
+ activate reconciliation in a direct repository. Patch-equivalent divergence
80
+ between `main` and `staging` is treated as aligned. When `staging` has pending
81
+ commits that are not yet represented on
79
82
  `main`, the release workflow replays those staging-only commits in order onto a
80
83
  detached worktree rooted at `main`, and then updates `staging` with an exact
81
84
  `--force-with-lease` to prevent
@@ -144,6 +147,8 @@ already passed).
144
147
  topology method: squash for `direct`, rebase for `staging-release`.
145
148
  3. For a generated consumer caller, confirm the GitHub Release and any package
146
149
  publication.
147
- 4. For Code Foundry itself, confirm qualification, draft staging, immutable
150
+ 4. For Code Foundry itself, do not advance the self-referencing runtime pin as
151
+ part of a release; self-sync preserves that compatibility pin and avoids a
152
+ self-update release loop. Confirm qualification, draft staging, immutable
148
153
  publication, and the retained identity receipts.
149
154
  5. In `staging-release`, synchronize `staging` with the new `main` release commit.
@@ -1,8 +1,9 @@
1
1
  # Declarative fleet inventory and staged rollouts
2
2
 
3
3
  Place `code-foundry-fleet.json` in the directory passed to `--root`. Its presence
4
- opts that fleet into inventory-based discovery and controlled upgrades, without
5
- changing legacy directory discovery for existing users.
4
+ opts that fleet into inventory-based discovery and controlled upgrades. The
5
+ runtime has no organization-specific fleet inventory; legacy discovery is limited
6
+ to the explicitly supplied root and its immediate children.
6
7
 
7
8
  ```json
8
9
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.28.2",
3
+ "version": "1.28.4",
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": {
@@ -18,7 +18,11 @@ export function discoverRepositories(root) {
18
18
  if (hasFleetManifest(root)) return discoverManifestRepositories(root)
19
19
  /** @type {FleetRepository[]} */
20
20
  const result = []
21
- const candidates = [root, ...children(root), ...children(join(root, 'NiftyLeague'))]
21
+ // Legacy discovery is intentionally limited to the explicitly supplied root
22
+ // and its immediate children. Consumer-fleet inventories belong in an
23
+ // explicit code-foundry-fleet.json manifest; the runtime must not know about
24
+ // any organization's repositories or directory names.
25
+ const candidates = [root, ...children(root)]
22
26
  for (const candidate of new Set(candidates)) {
23
27
  if (!existsSync(join(candidate, '.git'))) continue
24
28
  const configured = existsSync(join(candidate, '.github/code-foundry.yml'))
@@ -119,6 +119,7 @@ const legacyFiles = [
119
119
  function synchronize(options) {
120
120
  const target = resolve(options.target)
121
121
  const source = resolve(options.source)
122
+ const selfRepository = target === source
122
123
  const dryRun = options.dryRun ?? false
123
124
  const force = options.force ?? false
124
125
  const configPath = join(target, '.github/code-foundry.yml')
@@ -212,14 +213,17 @@ function synchronize(options) {
212
213
  )
213
214
  }
214
215
  }
215
- // Keep normal semver pins current during sync while preserving intentional
216
- // refs such as `main`, `staging`, or a custom immutable SHA. An explicit
217
- // runtime ref (fleet upgrade) is authoritative and overrides even those so
218
- // the rendered callers and the config pin land on the same runtime.
216
+ // Keep normal semver pins current during consumer syncs while preserving
217
+ // intentional refs such as `main`, `staging`, or a custom immutable SHA. An
218
+ // explicit runtime ref (fleet upgrade) is authoritative and overrides even
219
+ // those so the rendered callers and the config pin land on the same runtime.
220
+ // The runtime source itself is excluded from automatic pin advancement so a
221
+ // self-referencing config cannot create a release loop.
219
222
  if (
220
223
  existingConfig.runtime_ref &&
221
224
  existingConfig.runtime_ref !== targetRuntimeRef &&
222
- (options.runtimeRef !== undefined || /^v\d+\.\d+\.\d+$/.test(existingConfig.runtime_ref))
225
+ (options.runtimeRef !== undefined ||
226
+ (!selfRepository && /^v\d+\.\d+\.\d+$/.test(existingConfig.runtime_ref)))
223
227
  ) {
224
228
  runtimeRef = targetRuntimeRef
225
229
  const current = readFileSync(configPath, 'utf8')
@@ -260,7 +264,7 @@ function synchronize(options) {
260
264
  runtimeRef,
261
265
  rustCodeql,
262
266
  file,
263
- target === source
267
+ selfRepository
264
268
  )
265
269
  )
266
270
  }
@@ -636,6 +640,12 @@ function renderWorkflow(content, config, repository, ref, rustCodeql, file, self
636
640
  release: config.release_runner ?? config.runner,
637
641
  }
638
642
  const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
643
+ if (workflow === 'release' && !selfRepository) {
644
+ rendered = rendered.replace(
645
+ /^(\s+runtime-ref:)\s+.*$/m,
646
+ `$1 ${ref}\n git-workflow: ${isStagingRelease(config.git_workflow) ? 'staging-release' : 'direct'}`
647
+ )
648
+ }
639
649
  // Generated PR callers protect drafts by default. Consumers that intentionally
640
650
  // run gates while a PR is still draft can opt out in code-foundry.yml.
641
651
  if (configured(config.draft_protection, 'true') === 'false') {