code-foundry 0.30.4 → 0.31.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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.1](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.0...v0.31.1) (2026-07-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **release:** build clean staging sync branches ([03a7c19](https://github.com/0xPlayerOne/code-foundry/commit/03a7c191d7a5a6b95cce639e6ed9201b374f1010))
9
+
10
+ ## [0.31.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.30.4...v0.31.0) (2026-07-29)
11
+
12
+
13
+ ### Features
14
+
15
+ * **release:** open safe staging sync PRs ([6adb1dc](https://github.com/0xPlayerOne/code-foundry/commit/6adb1dc63966e5bfe70deb3ccd3c3aa5e93ccfad))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **release:** compare promoted branch tips ([081666b](https://github.com/0xPlayerOne/code-foundry/commit/081666b774418ab333d34eb9a3ca2a8c3bd60c78))
21
+
3
22
  ## [0.30.4](https://github.com/0xPlayerOne/code-foundry/compare/v0.30.3...v0.30.4) (2026-07-29)
4
23
 
5
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.30.4",
3
+ "version": "0.31.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",
@@ -22,8 +22,12 @@ export function reconcileRelease(root, options) {
22
22
  const mainSha = git(target, ['rev-parse', `origin/${base}`]) || git(target, ['rev-parse', base])
23
23
  const stagingSha = git(target, ['rev-parse', `origin/${head}`]) || git(target, ['rev-parse', head])
24
24
  const mergeBaseSha = git(target, ['merge-base', mainSha, stagingSha])
25
- const mainChangedPaths = diffNames(target, mergeBaseSha, mainSha)
26
- const stagingChangedPaths = diffNames(target, mergeBaseSha, stagingSha)
25
+ // Compare the branch tips directly. Release Please may rebase or recreate
26
+ // commits during promotion, leaving equivalent code with different ancestry.
27
+ // Comparing both tips preserves the fail-closed behavior for real content
28
+ // differences without mistaking that normal history rewrite for a change.
29
+ const mainChangedPaths = diffNames(target, stagingSha, mainSha)
30
+ const stagingChangedPaths = diffNames(target, mainSha, stagingSha)
27
31
  const plan = classifyReconciliation({
28
32
  mainSha,
29
33
  stagingSha,
@@ -34,6 +38,17 @@ export function reconcileRelease(root, options) {
34
38
  })
35
39
  console.log(JSON.stringify({ base, head, ...plan }, null, 2))
36
40
  if (plan.action === 'fail') throw new Error(plan.reason + (plan.unexpected?.length ? ` Unexpected paths: ${plan.unexpected.join(', ')}` : ''))
41
+ if (plan.action === 'pull-request' && options.github && !options.dryRun) {
42
+ if (!process.env.GITHUB_REPOSITORY) throw new Error('GITHUB_REPOSITORY is required for synchronization PRs.')
43
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN
44
+ if (!token) throw new Error('GH_TOKEN or GITHUB_TOKEN is required for synchronization PRs.')
45
+ const existing = ghJson(target, ['pr', 'list', '--repo', process.env.GITHUB_REPOSITORY, '--state', 'open', '--base', head, '--search', `chore(release): synchronize ${base} -> ${head} in:title`, '--json', 'number,url,headRefName'])
46
+ if (Array.isArray(existing) && existing.length) {
47
+ return { ...plan, synchronizationPullRequest: existing[0].url ?? existing[0].number }
48
+ }
49
+ const synchronizationPullRequest = createSynchronizationPullRequest(target, process.env.GITHUB_REPOSITORY, base, head, mainSha, stagingSha, mainChangedPaths, token)
50
+ return { ...plan, synchronizationPullRequest }
51
+ }
37
52
  if (plan.action !== 'fast-forward' || !options.github || options.dryRun) return plan
38
53
  if (!process.env.GITHUB_REPOSITORY) throw new Error('GITHUB_REPOSITORY is required for --github reconciliation.')
39
54
  const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN
@@ -46,6 +61,34 @@ export function reconcileRelease(root, options) {
46
61
  return plan
47
62
  }
48
63
 
64
+ /** @param {string} root @param {string} repository @param {string} base @param {string} head @param {string} mainSha @param {string} stagingSha @param {string[]} changedPaths @param {string} token */
65
+ function createSynchronizationPullRequest(root, repository, base, head, mainSha, stagingSha, changedPaths, token) {
66
+ const branch = `codex/release-sync-${head}-${mainSha.slice(0, 12)}`
67
+ const remote = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', `refs/heads/${branch}`], { cwd: root, encoding: 'utf8' })
68
+ if (remote.status !== 0) {
69
+ const checkout = spawnSync('git', ['switch', '--create', branch, `origin/${head}`], { cwd: root, encoding: 'utf8' })
70
+ if (checkout.status !== 0) throw new Error(`Failed to create synchronization branch: ${checkout.stderr.trim()}`)
71
+ const patch = spawnSync('git', ['diff', '--binary', stagingSha, mainSha, '--', ...changedPaths], { cwd: root, encoding: 'utf8' })
72
+ if (patch.status !== 0) throw new Error(`Failed to prepare synchronization patch: ${patch.stderr.trim()}`)
73
+ const apply = spawnSync('git', ['apply', '--whitespace=nowarn'], { cwd: root, input: patch.stdout, encoding: 'utf8' })
74
+ if (apply.status !== 0) throw new Error(`Synchronization patch did not apply cleanly: ${apply.stderr.trim()}`)
75
+ const add = spawnSync('git', ['add', '--', ...changedPaths], { cwd: root, encoding: 'utf8' })
76
+ if (add.status !== 0) throw new Error(`Failed to stage synchronization metadata: ${add.stderr.trim()}`)
77
+ const commit = spawnSync('git', ['commit', '-m', `chore(release): synchronize ${base} -> ${head}`], { cwd: root, encoding: 'utf8' })
78
+ if (commit.status !== 0) throw new Error(`Failed to commit synchronization metadata: ${commit.stderr.trim()}`)
79
+ const push = spawnSync('git', ['push', '--set-upstream', 'origin', branch], { cwd: root, encoding: 'utf8', env: { ...process.env, GH_TOKEN: token } })
80
+ if (push.status !== 0) throw new Error(`Failed to publish synchronization branch: ${push.stderr.trim()}`)
81
+ }
82
+ const result = spawnSync('gh', [
83
+ 'pr', 'create', '--repo', repository,
84
+ '--base', head, '--head', branch,
85
+ '--title', `chore(release): synchronize ${base} -> ${head}`,
86
+ '--body', `Synchronize ${head} with ${base} after Release Please publication.\n\nCode Foundry verified that only approved release metadata differs between the branch tips, then applied that metadata to an isolated synchronization branch.`,
87
+ ], { cwd: resolve(root), encoding: 'utf8', env: { ...process.env, GH_TOKEN: token } })
88
+ if (result.status !== 0) throw new Error(`Failed to create synchronization PR: ${result.stderr.trim()}`)
89
+ return result.stdout.trim()
90
+ }
91
+
49
92
  /**
50
93
  * Dispatch a configured post-release workflow at most once for a tag.
51
94
  * @param {string} root