code-foundry 0.34.11 → 0.34.12

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.34.12](https://github.com/0xPlayerOne/code-foundry/compare/v0.34.11...v0.34.12) (2026-08-02)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **reconcile:** classify final tree deltas ([#353](https://github.com/0xPlayerOne/code-foundry/issues/353)) ([18d23e4](https://github.com/0xPlayerOne/code-foundry/commit/18d23e4847ffbcfc274bc11c80244ecac64395ed))
9
+
3
10
  ## [0.34.11](https://github.com/0xPlayerOne/code-foundry/compare/v0.34.10...v0.34.11) (2026-08-02)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.34.11",
3
+ "version": "0.34.12",
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",
@@ -83,14 +83,16 @@ function resolveReconciliationState(target, base, head, allowed, requireRemote)
83
83
  try {
84
84
  const mainOnlyCommits = divergentCommits(target, stagingSha, mainSha)
85
85
  const stagingOnlyCommits = divergentCommits(target, mainSha, stagingSha)
86
+ const directChangedPaths = treeChangedPaths(target, stagingSha, mainSha)
86
87
  const plan = classifyReconciliation({
87
88
  mainSha,
88
89
  stagingSha,
89
90
  mainOnlyCommits,
90
91
  stagingOnlyCommits,
92
+ directChangedPaths,
91
93
  allowed,
92
94
  })
93
- return { plan, mainSha, stagingSha, mainOnlyCommits, stagingOnlyCommits }
95
+ return { plan, mainSha, stagingSha, mainOnlyCommits, stagingOnlyCommits, directChangedPaths }
94
96
  } catch (error) {
95
97
  const message = error instanceof Error ? error.message : String(error)
96
98
  return {
@@ -102,6 +104,7 @@ function resolveReconciliationState(target, base, head, allowed, requireRemote)
102
104
  stagingSha,
103
105
  mainOnlyCommits: [],
104
106
  stagingOnlyCommits: [],
107
+ directChangedPaths: [],
105
108
  }
106
109
  }
107
110
  }
@@ -570,6 +573,15 @@ function commitChangedPaths(root, commitSha) {
570
573
  return result.stdout.split(/\r?\n/).filter(Boolean)
571
574
  }
572
575
 
576
+ /** @param {string} root @param {string} from @param {string} to @returns {string[]} */
577
+ function treeChangedPaths(root, from, to) {
578
+ const result = spawnSync('git', ['diff', '--name-only', from, to], { cwd: root, encoding: 'utf8' })
579
+ if (result.status !== 0) {
580
+ throw new Error(`Failed to compare branch trees between ${from} and ${to}. ${result.stderr?.trim() || result.stdout?.trim()}`)
581
+ }
582
+ return result.stdout.split(/\r?\n/).filter(Boolean)
583
+ }
584
+
573
585
  /** @param {string} root @param {string[]} args @returns {{ status: number | null, stdout: string, stderr: string }} */
574
586
  function ghSpawn(root, args) {
575
587
  const token = process.env.RELEASE_PLEASE_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN
@@ -217,12 +217,16 @@ function compareVersions(a, b) {
217
217
  }
218
218
 
219
219
  /**
220
- * Classify the relationship between main and staging using commit-level divergence.
221
- * Any non-release main-only commit fails. Any staging-only commit triggers a replay
222
- * path so no staging-only work can be silently discarded.
220
+ * Classify the relationship between main and staging using the final tree delta
221
+ * when available. Historical commit paths can include equivalent workflow or
222
+ * configuration changes that are no longer present in the branch delta, so the
223
+ * final trees are the source of truth for release-only reconciliation. Any
224
+ * staging-only commit still triggers a replay path so no staging-only work can
225
+ * be silently discarded.
223
226
  * @param {{
224
227
  * mainSha: string,
225
228
  * stagingSha: string,
229
+ * directChangedPaths?: string[],
226
230
  * mainOnlyCommits?: Array<{ sha?: string, changedPaths?: string[] }>,
227
231
  * stagingOnlyCommits?: Array<{ sha?: string, changedPaths?: string[] }>,
228
232
  * allowed?: Set<string>,
@@ -232,6 +236,7 @@ export function classifyReconciliation(input) {
232
236
  const {
233
237
  mainSha,
234
238
  stagingSha,
239
+ directChangedPaths,
235
240
  mainOnlyCommits = [],
236
241
  stagingOnlyCommits = [],
237
242
  allowed = approvedReleaseFiles(),
@@ -248,6 +253,29 @@ export function classifyReconciliation(input) {
248
253
  action: 'fail',
249
254
  reason: 'Unable to inspect staging-only commit metadata.',
250
255
  }
256
+ if (Array.isArray(directChangedPaths)) {
257
+ const paths = [...new Set(directChangedPaths.map((path) => path.trim()).filter(Boolean))]
258
+ const unexpected = unexpectedReleasePaths(paths, allowed)
259
+ const stagingOnlyReleaseOnly = stagingOnlyCommits.length > 0 && stagingOnlyCommits.every((commit) =>
260
+ Array.isArray(commit.changedPaths) && commit.changedPaths.length > 0 && unexpectedReleasePaths(commit.changedPaths, allowed).length === 0,
261
+ )
262
+ if (!unexpected.length && !stagingOnlyReleaseOnly) {
263
+ return {
264
+ action: 'fast-forward',
265
+ targetSha: mainSha,
266
+ reason: paths.length
267
+ ? 'Branches differ only by approved release metadata.'
268
+ : 'Branches have different history but identical content.',
269
+ }
270
+ }
271
+ if (!stagingOnlyCommits.length) {
272
+ return {
273
+ action: 'fail',
274
+ reason: 'branch content differs outside release metadata.',
275
+ unexpected,
276
+ }
277
+ }
278
+ }
251
279
  const unexpectedMain = unexpectedReleasePaths(
252
280
  [...new Set(mainOnlyCommits.flatMap((commit) => commit.changedPaths || []))],
253
281
  allowed,