code-foundry 0.39.0 → 0.39.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.39.1](https://github.com/0xPlayerOne/code-foundry/compare/v0.39.0...v0.39.1) (2026-08-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **release:** build exact-tree reconciliation heads ([33b0151](https://github.com/0xPlayerOne/code-foundry/commit/33b015102f07ca03ffb677fe4b9f5e67bbf9a485))
9
+
3
10
  ## [0.39.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.38.2...v0.39.0) (2026-08-29)
4
11
 
5
12
 
package/docs/RELEASES.md CHANGED
@@ -76,8 +76,17 @@ as aligned. When `staging` has pending commits that are not yet represented on
76
76
  detached worktree rooted at `main`, and then updates `staging` with an exact
77
77
  `--force-with-lease` to prevent
78
78
  unintended branch rewrites. There is no unconditional mirror force-push and no
79
- fallback synchronization commit path. Indeterminate history, replay conflicts,
80
- and stale leases fail closed. `direct` repositories skip this step entirely.
79
+ unreviewed fallback synchronization commit path. Indeterminate history, replay
80
+ conflicts, and stale leases fail closed. `direct` repositories skip this step
81
+ entirely.
82
+
83
+ When branch policy requires reconciliation through a pull request, Code
84
+ Foundry creates a deterministic one-commit delivery head instead of pointing
85
+ the pull request branch at the divergent `main` or replay history. Its parent
86
+ is the current protected `staging` tip and its tree is the exact reconciled
87
+ target tree. This keeps the review limited to release metadata and any genuine
88
+ replayed staging work, while preserving exact-lease updates and idempotent PR
89
+ reuse.
81
90
 
82
91
  `auto` selects a supported manifest. Use `simple` with `version.txt` for a
83
92
  repository without a package manifest and `none` for a repository that should
package/docs/WORKFLOWS.md CHANGED
@@ -139,11 +139,14 @@ a branch-policy/ruleset/required-PR error, the reconcile job does not fail:
139
139
  if the branch trees are already identical, it reports a content-aligned no-op
140
140
  because a normal pull request cannot safely represent a history-only ref move.
141
141
  Otherwise, it opens (or reuses) a generated `code-foundry/reconcile/main-to-staging`
142
- pull request whose head contains the exact target tip (the `main` tip for a
143
- fast-forward, the replay tip when staging-only commits are replayed) and whose
144
- body documents the reconciliation. Reruns reuse the open pull request instead
145
- of duplicating it. Validated changes that landed directly on `main` are synced
146
- back into `staging`; unpromoted staging commits are replayed on top. Indeterminate
142
+ pull request whose deterministic one-commit head is parented at the current
143
+ `staging` tip and contains the exact reconciled target tree (the `main` tree for
144
+ a fast-forward, the replay tree when staging-only commits are replayed). This
145
+ avoids exposing divergent squash/rebase ancestry as an enormous PR diff. The
146
+ body documents both the target snapshot and exact-tree delivery head. Reruns
147
+ reuse the open pull request instead of duplicating it. Validated changes that
148
+ landed directly on `main` are synced back into `staging`; unpromoted staging
149
+ commits are replayed on top. Indeterminate
147
150
  history, replay conflicts, authentication errors, and ambiguous or stale pull
148
151
  request state still fail the job closed, and exact-lease protection stays in
149
152
  place for every direct push.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.39.0",
3
+ "version": "0.39.1",
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",
@@ -247,12 +247,18 @@ function deliverReconciliationPullRequest(target, base, head, state, targetSha,
247
247
  const prBase = head
248
248
  const branch = reconciliationPullRequestBranch(base, head)
249
249
  const title = reconciliationPullRequestTitle({ targetHead: head, sourceBase: base })
250
+ const prepared = prepareExactTreeReconciliationHead(target, state.stagingSha, targetSha, title)
251
+ if (prepared.status !== 0 || !prepared.sha) {
252
+ return failResult(`Failed to prepare the exact-tree reconciliation head ${branch}: ${prepared.message}`)
253
+ }
254
+ const deliverySha = prepared.sha
250
255
  const body = buildReconciliationPullRequestBody({
251
256
  sourceBase: base,
252
257
  targetHead: head,
253
258
  mainSha: state.mainSha,
254
259
  stagingSha: state.stagingSha,
255
260
  targetSha,
261
+ deliverySha,
256
262
  action: state.plan.action,
257
263
  pushError,
258
264
  })
@@ -263,9 +269,9 @@ function deliverReconciliationPullRequest(target, base, head, state, targetSha,
263
269
  if (selection.error) return failResult(selection.error)
264
270
  if (!selection.create) {
265
271
  if (!selection.reuse) return failResult(`No reusable reconciliation pull request for ${branch}.`)
266
- return reuseReconciliationPullRequest(target, repository, selection.reuse, targetSha, body)
272
+ return reuseReconciliationPullRequest(target, repository, selection.reuse, deliverySha, body)
267
273
  }
268
- const pushed = pushReconciliationHead(target, branch, targetSha)
274
+ const pushed = pushReconciliationHead(target, branch, deliverySha)
269
275
  if (pushed.status !== 0) {
270
276
  const classification = classifyPushFailure(branch, pushed.message)
271
277
  if (classification.category === 'authentication') {
@@ -279,7 +285,7 @@ function deliverReconciliationPullRequest(target, base, head, state, targetSha,
279
285
  if (selection.error) return failResult(selection.error)
280
286
  if (!selection.create) {
281
287
  if (!selection.reuse) return failResult(`No reusable reconciliation pull request for ${branch} after pushing its head.`)
282
- return reuseReconciliationPullRequest(target, repository, selection.reuse, targetSha, body)
288
+ return reuseReconciliationPullRequest(target, repository, selection.reuse, deliverySha, body)
283
289
  }
284
290
  const created = ghSpawn(target, ['pr', 'create', '--repo', repository, '--base', prBase, '--head', branch, '--title', title, '--body', body])
285
291
  if (created.status !== 0) {
@@ -293,7 +299,7 @@ function deliverReconciliationPullRequest(target, base, head, state, targetSha,
293
299
  return failResult(reconciliationPullRequestCreateError(branch, created.stderr))
294
300
  }
295
301
  if (!selection.reuse) return failResult(`No reusable reconciliation pull request for ${branch} after gh pr create failed.`)
296
- return reuseReconciliationPullRequest(target, repository, selection.reuse, targetSha, body)
302
+ return reuseReconciliationPullRequest(target, repository, selection.reuse, deliverySha, body)
297
303
  }
298
304
  const number = parsePullRequestNumber(created.stdout)
299
305
  if (!number) return failResult(`Created the reconciliation pull request for ${branch} but could not parse its number.`)
@@ -308,6 +314,46 @@ function deliverReconciliationPullRequest(target, base, head, state, targetSha,
308
314
  }
309
315
  }
310
316
 
317
+ /**
318
+ * Build a deterministic single-commit delivery head whose parent is the
319
+ * protected staging tip and whose tree is the fully reconciled target tree.
320
+ * GitHub therefore reviews only the real content delta even when main and
321
+ * staging have divergent squash/rebase ancestry.
322
+ * @param {string} target
323
+ * @param {string} stagingSha
324
+ * @param {string} targetSha
325
+ * @param {string} title
326
+ * @returns {{ status: number | null, sha: string, message: string }}
327
+ */
328
+ function prepareExactTreeReconciliationHead(target, stagingSha, targetSha, title) {
329
+ const tree = git(target, ['rev-parse', `${targetSha}^{tree}`])
330
+ const timestamp = git(target, ['show', '-s', '--format=%cI', targetSha])
331
+ if (!tree || !timestamp) {
332
+ return { status: 1, sha: '', message: `could not resolve target tree or timestamp for ${targetSha}.` }
333
+ }
334
+ const message = `${title}\n\nReconciled from target snapshot ${targetSha.slice(0, 12)}.`
335
+ const result = spawnSync('git', ['commit-tree', tree, '-p', stagingSha, '-m', message], {
336
+ cwd: target,
337
+ encoding: 'utf8',
338
+ env: {
339
+ ...process.env,
340
+ GIT_AUTHOR_NAME: 'github-actions[bot]',
341
+ GIT_AUTHOR_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com',
342
+ GIT_AUTHOR_DATE: timestamp,
343
+ GIT_COMMITTER_NAME: 'github-actions[bot]',
344
+ GIT_COMMITTER_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com',
345
+ GIT_COMMITTER_DATE: timestamp,
346
+ },
347
+ })
348
+ const sha = result.status === 0 ? result.stdout.trim() : ''
349
+ const detail = `${result.stdout?.trim() || ''}\n${result.stderr?.trim() || ''}`.trim()
350
+ return {
351
+ status: result.status,
352
+ sha,
353
+ message: result.status === 0 && sha ? '' : sanitizeReconcileOutput(detail) || 'git commit-tree failed.',
354
+ }
355
+ }
356
+
311
357
  /** @param {string} branch @param {string} stderr */
312
358
  function reconciliationPullRequestCreateError(branch, stderr) {
313
359
  const detail = sanitizeReconcileOutput(stderr) || 'unknown error'
@@ -318,22 +364,22 @@ function reconciliationPullRequestCreateError(branch, stderr) {
318
364
  }
319
365
 
320
366
  /**
321
- * Refresh an existing reconciliation pull request to the exact target tip and
322
- * body. The head branch is pushed under an exact lease when its tip is stale,
367
+ * Refresh an existing reconciliation pull request to the exact-tree delivery
368
+ * head and body. The head branch is pushed under an exact lease when stale,
323
369
  * so a concurrent move fails closed instead of being clobbered.
324
370
  * @param {string} target
325
371
  * @param {string} repository
326
372
  * @param {{number?: number, title?: string, headRefName?: string, headRefOid?: string, baseRefName?: string, url?: string}} pr
327
- * @param {string} targetSha
373
+ * @param {string} deliverySha
328
374
  * @param {string} body
329
375
  * @returns {{ success: false, retry: false, error: string } | { success: true, result: ReconciliationMutationResult }}
330
376
  */
331
- function reuseReconciliationPullRequest(target, repository, pr, targetSha, body) {
377
+ function reuseReconciliationPullRequest(target, repository, pr, deliverySha, body) {
332
378
  const number = Number(pr.number)
333
- if (String(pr.headRefOid ?? '') !== targetSha) {
334
- const pushed = pushReconciliationHead(target, /** @type {string} */ (pr.headRefName), targetSha)
379
+ if (String(pr.headRefOid ?? '') !== deliverySha) {
380
+ const pushed = pushReconciliationHead(target, /** @type {string} */ (pr.headRefName), deliverySha)
335
381
  if (pushed.status !== 0) {
336
- return failResult(`Failed to refresh the reconciliation branch ${pr.headRefName} to the target tip: ${sanitizeReconcileOutput(pushed.message)}`)
382
+ return failResult(`Failed to refresh the reconciliation branch ${pr.headRefName} to the exact-tree delivery head: ${sanitizeReconcileOutput(pushed.message)}`)
337
383
  }
338
384
  const edited = ghSpawn(target, ['pr', 'edit', String(number), '--repo', repository, '--body', body])
339
385
  if (edited.status !== 0) {
@@ -358,20 +404,21 @@ function reuseReconciliationPullRequest(target, repository, pr, targetSha, body)
358
404
  }
359
405
 
360
406
  /**
361
- * Push the exact target tip to the deterministic reconciliation head branch,
362
- * creating it when absent and refreshing it under an exact lease otherwise.
363
- * @param {string} target @param {string} branch @param {string} targetSha
407
+ * Push the exact-tree delivery commit to the deterministic reconciliation
408
+ * head branch, creating it when absent and refreshing it under an exact lease
409
+ * otherwise.
410
+ * @param {string} target @param {string} branch @param {string} deliverySha
364
411
  */
365
- function pushReconciliationHead(target, branch, targetSha) {
412
+ function pushReconciliationHead(target, branch, deliverySha) {
366
413
  const existingTip = remoteRefSha(target, branch)
367
414
  if (existingTip === null) return { status: 1, message: `could not resolve the remote reconciliation branch ${branch} before pushing.` }
368
- if (existingTip === targetSha) return { status: 0, message: '' }
415
+ if (existingTip === deliverySha) return { status: 0, message: '' }
369
416
  if (!existingTip) {
370
- const result = spawnSync('git', ['push', 'origin', `${targetSha}:refs/heads/${branch}`], { cwd: target, encoding: 'utf8' })
417
+ const result = spawnSync('git', ['push', 'origin', `${deliverySha}:refs/heads/${branch}`], { cwd: target, encoding: 'utf8' })
371
418
  const message = `${result.stdout?.trim() || ''}\n${result.stderr?.trim() || ''}`.trim() || `failed to create reconciliation branch ${branch}.`
372
419
  return { status: result.status, message: sanitizeReconcileOutput(message) }
373
420
  }
374
- return pushWithLease(target, branch, existingTip, targetSha)
421
+ return pushWithLease(target, branch, existingTip, deliverySha)
375
422
  }
376
423
 
377
424
  /**
@@ -412,27 +412,29 @@ export function reconciliationPullRequestTitle({ targetHead, sourceBase }) {
412
412
  * mainSha: string,
413
413
  * stagingSha: string,
414
414
  * targetSha: string,
415
+ * deliverySha?: string,
415
416
  * action: string,
416
417
  * pushError?: string,
417
418
  * }} input
418
419
  */
419
420
  export function buildReconciliationPullRequestBody(input) {
420
- const { sourceBase, targetHead, mainSha, stagingSha, targetSha, action, pushError = '' } = input
421
+ const { sourceBase, targetHead, mainSha, stagingSha, targetSha, deliverySha = '', action, pushError = '' } = input
421
422
  const lines = [
422
423
  '## Automated release reconciliation',
423
424
  '',
424
- `This pull request was generated by Code Foundry release reconciliation. The protected \`${targetHead}\` branch rejected the exact-lease synchronization push that carries release metadata published on \`${sourceBase}\` (branch policy, ruleset, or required pull request review), so this pull request delivers the exact target tip through the normal review flow instead.`,
425
+ `This pull request was generated by Code Foundry release reconciliation. The protected \`${targetHead}\` branch rejected the exact-lease synchronization push that carries release metadata published on \`${sourceBase}\` (branch policy, ruleset, or required pull request review), so this pull request delivers the exact reconciled tree through the normal review flow instead.`,
425
426
  '',
426
427
  `- Base \`${targetHead}\`: \`${stagingSha}\``,
427
428
  `- \`${sourceBase}\` tip: \`${mainSha}\``,
428
- `- Target tip in this branch: \`${targetSha}\``,
429
+ `- Reconciled target snapshot: \`${targetSha}\``,
430
+ ...(deliverySha ? [`- Exact-tree pull request head: \`${deliverySha}\``] : []),
429
431
  `- Synchronization: \`${action}\``,
430
432
  ]
431
433
  const reason = pushError.replace(/\s+/g, ' ').trim()
432
434
  if (reason) lines.push(`- Direct push rejection: \`${reason}\``)
433
435
  lines.push(
434
436
  '',
435
- `Merging this pull request synchronizes \`${targetHead}\` with the exact target tip above; no other commits are introduced.`,
437
+ `Merging this pull request synchronizes \`${targetHead}\` with the exact reconciled tree above; no unrelated commits are introduced.`,
436
438
  '',
437
439
  `Code Foundry treats validated \`${sourceBase}\` as the shipped source of truth and preserves unpromoted \`${targetHead}\` work by replaying it on top. Indeterminate history, replay conflicts, authentication failures, and ambiguous or stale state fail the release job closed and are never pushed around branch protection. Later releases retry the direct push first; when branch policy still rejects it, this pull request is updated instead of duplicated.`,
438
440
  )