Skip to content

feat(cli): structured merge for package.json/.env.example in bfs update#313

Merged
Marve10s merged 7 commits into
mainfrom
feat/bfs-update-structured-merge
Jul 10, 2026
Merged

feat(cli): structured merge for package.json/.env.example in bfs update#313
Marve10s merged 7 commits into
mainfrom
feat/bfs-update-structured-merge

Conversation

@Marve10s

Copy link
Copy Markdown
Owner

Summary

Closes the deferred TODO in scaffold-upgrade.ts: package.json and *.env.example are no longer always routed to manual review during bfs update — they get a 3-way structured merge reusing stack-update's existing mergePackageJson/mergeEnvExample (exported, not forked).

  • Template-side additions/changes to deps/scripts/env keys fold into the user's file when the user didn't touch that key
  • User wins on conflict: keys changed on both sides block the file as a conflict entry naming the blocked keys (stack-update's blocker semantics)
  • Lockfiles and .env (secrets) stay conservative — never auto-patched
  • bts.lock.json now records optional baselines (pure-template render of structured files) at create time, via update --record-baseline, and advances them on apply; old hash-only manifests gracefully fall back to manual review

Bonus root-cause fix

processCatalogs skipped deps whose peers were already catalog:, so graph-mode's repeated post-process passes left db-package deps at literal versions — every fresh graph project would have reported a spurious package.json merge. Later passes now resolve existing catalog references when counting duplicates.

Verification

  • scaffold-upgrade tests 14/14 (merge union, conflict, legacy-manifest fallback, .env conservative), stack-update 68/68, template-generator 140/140, full bun run test:release green
  • E2E with built CLI: fresh scaffold plans clean (actionable: []); simulated older baseline + user edits → update --apply produced the union (template dep+script restored, user dep+script kept); re-plan stable

Generated with Claude Code (Fable 5 implementation via background agent)

Implements the deferred follow-up in scaffold-upgrade's isStructuredMergeFile:
instead of always routing package.json and .env.example to manual review,
`bfs update` now folds template-side changes into the user's file, reusing
stack-update's mergePackageJson / mergeEnvExample (now exported).

- bts.lock.json gains optional `baselines`: the pure-template render content
  of package.json / *.env.example, recorded at create (CLI + MCP), by
  `update --record-baseline`, and advanced on every reconciling apply
  (scaffold upgrade + stack update).
- Merge semantics match stack-update: template additions/changes to deps,
  scripts, and env keys are applied when the user (or create-time
  post-processing) didn't touch the key; user-changed keys always win — if the
  template also changed one, the whole file becomes a conflict naming the
  blocked keys. Manifests without baselines fall back to manual review.
- Lockfiles and .env (secrets) stay conservative: manual review, never patched.
- New "merged" plan category is actionable, reported by `update`, and
  written on --apply with hashes + baselines refreshed.
- fix(template-generator): later processCatalogs passes now resolve existing
  "catalog:" references when counting duplicates, so graph-mode db-package
  deps get catalogued like solo mode; previously create/update renders
  disagreed and a fresh project reported spurious package.json changes.
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
better-fullstack-web Ready Ready Preview, Comment Jul 10, 2026 1:26pm

@github-actions github-actions Bot added size:L 100-499 effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jul 10, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c60e77cda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/helpers/core/scaffold-upgrade.ts
Comment thread apps/cli/src/helpers/core/scaffold-upgrade.ts
Comment thread apps/cli/src/helpers/core/scaffold-upgrade.ts
Marve10s and others added 3 commits July 10, 2026 14:49
…hanges, env baseline

Addresses three review findings on the bfs update structured merge:

- diffJsonSection: a user-side deletion of a key the baseline had now blocks
  the key like any other user edit when the template also changed it, instead
  of silently re-adding it (applies to stack-update merges too).
- package.json: template changes mergePackageJson cannot express — section key
  removals and changed top-level fields (exports, workspaces, ...) — route the
  file to manual review naming the changes, instead of being silently dropped
  under a user-edited label or partially merged. Comparison is key-order
  insensitive so legitimate render reordering does not false-positive.
- *.env.example without a recorded content baseline now falls back to manual
  review like package.json; merging with an undefined previous side would
  mistake every proposed key for a template addition and re-append keys the
  user deliberately removed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bed781ed11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

await refreshScaffoldManifestFiles(
plan.projectDir,
plan.operations.map((operation) => operation.path),
collectStructuredBaselines(proposedTree),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't advance baselines for untouched structured files

When a stack update changes a generated package.json in a way mergePackageJson does not write (for example removing a frontend so the generated root package drops scripts.dev:web, or changing top-level fields), the plan produces no operation for that file and leaves the on-disk package untouched. This call still records proposed structured baselines for every package.json / *.env.example, so the scaffold baseline moves past an unapplied template change; subsequent bfs update treats the stale disk content as a local edit instead of surfacing the pending template change for manual review. Limit the baseline update to structured files that were actually written or proven unchanged.

Useful? React with 👍 / 👎.

}

// *.env.example: append template-added keys; existing keys are never touched.
const merged = mergeEnvExample(existingContent, baselineContent, proposedContent);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface unmerged env.example key changes

When a template changes or removes an existing key in *.env.example and the user's file still matches the recorded baseline for that key, mergeEnvExample ignores it because it only appends keys that were absent from the baseline. This path then reports the file as merged/user-edited (and --apply can advance the content baseline), so the template-side env change is never surfaced for manual review unlike the previous conservative handling.

Useful? React with 👍 / 👎.

}
for (const key of new Set([...Object.keys(previous), ...Object.keys(proposed)])) {
if (mergedSections.has(key)) continue;
if (!deepEqualUnordered(previous[key], proposed[key])) changes.push(key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore generated packageManager bumps during package merges

When the bundled virtual package-manager version changes, this top-level scan reports packageManager as an unmergeable template change, even though the create path rewrites that field to the user's local package-manager version after generation. In that scenario otherwise mergeable dependency/script updates in every existing package.json fall back to manual review for a value the user should not apply, so structured package merges stop working until the baseline is hand-adjusted.

Useful? React with 👍 / 👎.

@Marve10s Marve10s merged commit 0f26185 into main Jul 10, 2026
22 checks passed
@Marve10s Marve10s deleted the feat/bfs-update-structured-merge branch July 10, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant