code-foundry 0.33.0 → 0.34.0

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.
@@ -300,8 +300,8 @@ jobs:
300
300
  if (!lines) process.exit(1)
301
301
  process.stdout.write(`${lines}\n`)
302
302
  NODE
303
- git remote set-url origin "git@github.com:${GITHUB_REPOSITORY}.git"
304
- echo "ssh_command=-F /dev/null -i $STAGING_KEY -o IdentitiesOnly=yes -o UserKnownHostsFile=$STAGING_HOSTS -o StrictHostKeyChecking=yes" >> "$GITHUB_OUTPUT"
303
+ git remote set-url --push origin "git@github.com:${GITHUB_REPOSITORY}.git"
304
+ echo "ssh_command=ssh -F /dev/null -i $STAGING_KEY -o IdentitiesOnly=yes -o UserKnownHostsFile=$STAGING_HOSTS -o StrictHostKeyChecking=yes" >> "$GITHUB_OUTPUT"
305
305
  - name: Configure git for trusted reconcile
306
306
  if: steps.staging.outputs.exists == 'true' && env.STAGING_DEPLOY_KEY_PRESENT != 'true'
307
307
  run: gh auth setup-git
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.34.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.33.0...v0.34.0) (2026-08-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * **release:** classify reconcile push failures and log sanitized details ([#307](https://github.com/0xPlayerOne/code-foundry/issues/307)) ([0651fca](https://github.com/0xPlayerOne/code-foundry/commit/0651fca3eb4f11eeb21b91462397eef865eee7cc))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **release:** include ssh executable in reconcile transport ([#308](https://github.com/0xPlayerOne/code-foundry/issues/308)) ([49713ed](https://github.com/0xPlayerOne/code-foundry/commit/49713edeaf5262a0ed872aa6d3fdf55927b6e1c5))
14
+ * **release:** keep reconcile fetch over HTTPS ([#305](https://github.com/0xPlayerOne/code-foundry/issues/305)) ([022afad](https://github.com/0xPlayerOne/code-foundry/commit/022afade430a00b9656dcfa50af3056bb27a54e3))
15
+
3
16
  ## [0.33.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.32.4...v0.33.0) (2026-08-01)
4
17
 
5
18
 
package/docs/WORKFLOWS.md CHANGED
@@ -62,8 +62,10 @@ a personal-repository ruleset for `staging` enforces a Deploy Key bypass for
62
62
  checkout + GitHub API calls.
63
63
 
64
64
  When used, the optional `STAGING_DEPLOY_KEY` is written at runtime only for
65
- reconcile, used to set `GIT_SSH_COMMAND` and trusted host settings for the
66
- `release reconcile` transport, then scrubbed after the step.
65
+ reconcile, then used to set `GIT_SSH_COMMAND` and trusted host settings for
66
+ `release reconcile` over SSH push operations only; regular `git fetch` reads still
67
+ use HTTPS from the checked-out origin configuration. The key material is scrubbed
68
+ after the step.
67
69
 
68
70
  When absent, the job keeps `GH_TOKEN = github.token` and runs `gh auth setup-git`
69
71
  so repositories without a Deploy Key ruleset bypass still reconcile successfully.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
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",
@@ -44,7 +44,8 @@ export function reconcileRelease(root, options) {
44
44
  const remoteSha = remoteRefSha(target, head)
45
45
  if (remoteSha === state.stagingSha) {
46
46
  throw new Error(`${head} synchronization was rejected by an exact lease while remote ${head} tip remained ${state.stagingSha}. ` +
47
- 'Update branch protection or remote policy to permit this mutation, then retry.')
47
+ 'Update branch protection or remote policy to permit this mutation, then retry. ' +
48
+ `Last failure detail: ${mutation.error}`)
48
49
  }
49
50
  }
50
51
  throw new Error(`Reconciliation of ${head} was retried but failed while the branch moved concurrently.`)
@@ -150,15 +151,55 @@ function remoteRefSha(target, branch) {
150
151
  function executeReconciliationMutation(target, head, state) {
151
152
  if (!state.plan.targetSha) return { success: false, retry: false, error: `No target SHA for ${head} reconciliation.` }
152
153
  if (state.plan.action === 'rebase-staging') {
153
- const replaySha = replayOntoMain(target, state.mainSha, state.stagingOnlyCommits)
154
+ let replaySha
155
+ try {
156
+ replaySha = replayOntoMain(target, state.mainSha, state.stagingOnlyCommits)
157
+ } catch (error) {
158
+ return {
159
+ success: false,
160
+ retry: false,
161
+ error: `Replay conflict while applying staging-only commits before replay: ${String(error instanceof Error ? error.message : error)}`,
162
+ }
163
+ }
164
+
154
165
  const push = pushWithLease(target, head, state.stagingSha, replaySha)
155
166
  if (push.status === 0) return { success: true, result: { synchronization: 'replay', replaySha } }
156
- return { success: false, retry: true, error: `Git push of replayed ${head} history failed: ${push.message}` }
167
+ const classification = classifyPushFailure(head, push.message)
168
+ if (classification.category === 'authentication') {
169
+ return {
170
+ success: false,
171
+ retry: false,
172
+ error: `${head} authentication failed during replay push: ${classification.message}`,
173
+ }
174
+ }
175
+ if (classification.category === 'policy') {
176
+ return {
177
+ success: false,
178
+ retry: false,
179
+ error: `${head} reconciliation was blocked by branch policy: ${classification.message}`,
180
+ }
181
+ }
182
+ return { success: false, retry: true, error: `${head} synchronization failed with lease: ${classification.message}` }
157
183
  }
158
184
  const targetSha = /** @type {string} */ (state.plan.targetSha)
159
185
  const leased = pushWithLease(target, head, state.stagingSha, targetSha)
160
186
  if (leased.status === 0) return { success: true, result: { synchronization: state.plan.action === 'aligned' ? 'synced' : 'leased' } }
161
- return { success: false, retry: true, error: `${head} synchronization failed with lease: ${leased.message}` }
187
+ const classification = classifyPushFailure(head, leased.message)
188
+ if (classification.category === 'authentication') {
189
+ return {
190
+ success: false,
191
+ retry: false,
192
+ error: `${head} authentication failed during push: ${classification.message}`,
193
+ }
194
+ }
195
+ if (classification.category === 'policy') {
196
+ return {
197
+ success: false,
198
+ retry: false,
199
+ error: `${head} reconciliation was blocked by branch policy: ${classification.message}`,
200
+ }
201
+ }
202
+ return { success: false, retry: true, error: `${head} synchronization failed with lease: ${classification.message}` }
162
203
  }
163
204
 
164
205
  /** @param {string} target @param {string} head @param {string} expectedSha @param {string} tipSha */
@@ -169,10 +210,38 @@ function pushWithLease(target, head, expectedSha, tipSha) {
169
210
  `--force-with-lease=refs/heads/${head}:${expectedSha}`,
170
211
  `${tipSha}:refs/heads/${head}`,
171
212
  ], { cwd: target, encoding: 'utf8' })
213
+ const message = `${result.stdout?.trim() || ''}\n${result.stderr?.trim() || ''}`.trim() || `push with lease failed for ${head}.`
172
214
  return {
173
215
  status: result.status,
174
- message: result.stdout?.trim() || result.stderr?.trim() || `push with lease failed for ${head}.`,
216
+ message: sanitizeReconcileOutput(message),
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Classify a push failure to avoid leaking sensitive details and provide useful
222
+ * diagnostics while preserving exact-lease behavior.
223
+ * @param {string} branch
224
+ * @param {string} raw
225
+ */
226
+ function classifyPushFailure(branch, raw) {
227
+ const message = sanitizeReconcileOutput(raw)
228
+ if (!message) return { category: 'other', message: `push with lease failed for ${branch}.` }
229
+ const lower = message.toLowerCase()
230
+ if (/permission denied|authentication failed|could not read from remote repository|publickey|not authorized|bad credentials/.test(lower)) {
231
+ return { category: 'authentication', message }
232
+ }
233
+ if (/remote:\s*error|protected branch|required status checks|pre-receive hook|branch policy|ruleset|gh006|gh007|gh008/.test(lower)) {
234
+ return { category: 'policy', message }
175
235
  }
236
+ return { category: 'other', message }
237
+ }
238
+
239
+ /** @param {string} value */
240
+ function sanitizeReconcileOutput(value) {
241
+ return value
242
+ .replace(/https?:\/\/[^\s@]+:[^\s@]*@/g, 'https://***:***@')
243
+ .replace(/https?:\/\/[^\s@]+@/g, 'https://***@')
244
+ .trim()
176
245
  }
177
246
 
178
247
  /**
@@ -198,7 +267,8 @@ function replayOntoMain(target, mainSha, commits) {
198
267
  const cherryPick = spawnSync('git', [...identityArgs, 'cherry-pick', sha], { cwd: workspace, encoding: 'utf8' })
199
268
  if (cherryPick.status !== 0) {
200
269
  spawnSync('git', ['cherry-pick', '--abort'], { cwd: workspace, encoding: 'utf8' })
201
- throw new Error(`Cherry-pick of ${sha} failed while replaying staging commits.`)
270
+ const details = [cherryPick.stdout?.trim(), cherryPick.stderr?.trim()].filter(Boolean).join('\n')
271
+ throw new Error(`Cherry-pick of ${sha} failed while replaying staging commits: ${sanitizeReconcileOutput(details)}`)
202
272
  }
203
273
  }
204
274
  const replayTip = git(workspace, ['rev-parse', 'HEAD'])