From 42c3d7a5bc6b363e95eb142deab156222f094811 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 30 Apr 2026 11:57:13 -0400 Subject: [PATCH 01/47] Fix merge-fix workflow to handle conflict markers before install (#16539) --- .agents/skills/merge/fix-tests.md | 22 ++++++++++++++++------ .agents/skills/merge/resolve-conflicts.md | 6 +++++- .flue/workflows/merge-fix/WORKFLOW.ts | 15 +++++++++++---- .github/workflows/merge-fix.yml | 15 +++++++-------- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.agents/skills/merge/fix-tests.md b/.agents/skills/merge/fix-tests.md index 150183227818..52f0d5813adc 100644 --- a/.agents/skills/merge/fix-tests.md +++ b/.agents/skills/merge/fix-tests.md @@ -17,7 +17,17 @@ This skill follows a "fix and push" approach. After pushing, CI will re-run auto ## Steps -### Step 1: Get CI failure logs +### Step 1: Build all packages + +Before analyzing test failures, ensure all packages are built: + +```bash +pnpm build +``` + +Fix any build errors first — these may be the cause of test failures downstream. + +### Step 2: Get CI failure logs Use the GitHub CLI to find the failed CI run and download its logs: @@ -35,7 +45,7 @@ Parse the output to identify: - The specific test names that failed - The error messages and assertion diffs -### Step 2: Analyze failures +### Step 3: Analyze failures For each failure, determine the cause: @@ -47,7 +57,7 @@ For each failure, determine the cause: 4. **Configuration mismatches** — Test fixtures using config options that changed on `next`. Fix by updating the fixture config. -### Step 3: Fix the failures +### Step 4: Fix the failures For each failure: @@ -68,7 +78,7 @@ For each failure: - Smoke tests — these are allowed to fail - `astro check` failures — these are permitted to fail -### Step 4: Verify fixes locally (targeted only) +### Step 5: Verify fixes locally (targeted only) Only run the specific tests you fixed, not the full suite: @@ -78,7 +88,7 @@ pnpm -C exec astro-scripts test "test/.test.j This is a quick sanity check, not a replacement for CI. CI will do the full validation after you push. -### Step 5: Rebuild if source files were modified +### Step 6: Rebuild if source files were modified If you modified any source files in `packages/` (not just test files), rebuild the affected package: @@ -88,7 +98,7 @@ pnpm -C packages/ build Then re-run the specific affected tests to confirm. -### Step 6: Ensure dependencies are up to date +### Step 7: Ensure dependencies are up to date If the merge introduced new dependencies or changed versions, run: diff --git a/.agents/skills/merge/resolve-conflicts.md b/.agents/skills/merge/resolve-conflicts.md index f891a609eb94..81bdd77794e4 100644 --- a/.agents/skills/merge/resolve-conflicts.md +++ b/.agents/skills/merge/resolve-conflicts.md @@ -50,7 +50,11 @@ When resolving conflicts, follow these rules based on file type: 2. For each conflicted file, read the file and resolve the conflict following the strategy above. 3. After resolving each file, run `git add ` to mark it as resolved. 4. After all files are resolved, verify no conflict markers remain: `grep -r "<<<<<<< " --include="*.ts" --include="*.js" --include="*.json" --include="*.yaml" --include="*.yml" --include="*.md" .` -5. Do NOT commit — the orchestrator will handle committing. +5. After conflicts are resolved, regenerate the lockfile and install dependencies: + ```bash + pnpm install --no-frozen-lockfile + ``` +6. Do NOT commit — the orchestrator will handle committing. ## Output diff --git a/.flue/workflows/merge-fix/WORKFLOW.ts b/.flue/workflows/merge-fix/WORKFLOW.ts index eb77a3637974..58b596eb5aeb 100644 --- a/.flue/workflows/merge-fix/WORKFLOW.ts +++ b/.flue/workflows/merge-fix/WORKFLOW.ts @@ -49,7 +49,14 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu }); } - // Step 3: Remove stale changesets that were already released on main + // Step 3: Ensure dependencies are installed + // If conflicts were resolved, the resolve-conflicts skill already ran pnpm install. + // If not, we still need to install deps before proceeding. + if (!hasConflicts) { + await flue.shell('pnpm install --no-frozen-lockfile'); + } + + // Step 4: Remove stale changesets that were already released on main await flue.skill('merge/clean-changesets.md', { args: { prNumber }, result: v.object({ @@ -60,7 +67,7 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu }), }); - // Step 4: Run tests and fix failures + // Step 5: Run tests and fix failures const fixResult = await flue.skill('merge/fix-tests.md', { args: { prNumber }, result: v.object({ @@ -78,7 +85,7 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu }), }); - // Step 5: Commit and push all changes + // Step 6: Commit and push all changes const status = await flue.shell('git status --porcelain'); if (status.stdout.trim()) { await flue.shell('git add -A'); @@ -100,7 +107,7 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu } } - // Step 6: Post a summary comment on the PR + // Step 7: Post a summary comment on the PR const summaryParts = []; if (hasConflicts) summaryParts.push('- Resolved merge conflicts'); if (fixResult.fixedFiles.length > 0) { diff --git a/.github/workflows/merge-fix.yml b/.github/workflows/merge-fix.yml index 239235776aa7..461f5bd9b9de 100644 --- a/.github/workflows/merge-fix.yml +++ b/.github/workflows/merge-fix.yml @@ -102,15 +102,14 @@ jobs: node-version: 24.15.0 cache: pnpm - - name: Install deps + - name: Install root deps only if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' - # Use --no-frozen-lockfile because the merge may have introduced - # lockfile conflicts or new dependencies that need resolving - run: pnpm install --no-frozen-lockfile - - - name: Build - if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' - run: pnpm build + # Only install root workspace deps (where @flue/cli lives). + # Cannot install the full monorepo here because merge conflicts + # in package.json files would break pnpm install. + # Full install and build happen inside the Flue sandbox after + # conflicts are resolved. + run: pnpm install --no-frozen-lockfile --filter . - name: Log in to GHCR if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' From 425fa8b67ce1dff29d93e224e393c1dc54578abb Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 30 Apr 2026 14:04:11 -0400 Subject: [PATCH 02/47] Handle merge conflicts in merge-fix workflow by stripping JSON/YAML markers and verifying via AI (#16541) --- .agents/skills/merge/resolve-conflicts.md | 77 +++++++++++++---------- .flue/workflows/merge-fix/WORKFLOW.ts | 64 +++++++++---------- .github/workflows/merge-fix.yml | 45 ++++++++++--- 3 files changed, 108 insertions(+), 78 deletions(-) diff --git a/.agents/skills/merge/resolve-conflicts.md b/.agents/skills/merge/resolve-conflicts.md index 81bdd77794e4..8c240754fddc 100644 --- a/.agents/skills/merge/resolve-conflicts.md +++ b/.agents/skills/merge/resolve-conflicts.md @@ -1,6 +1,6 @@ -# Resolve Merge Conflicts +# Verify Merge Conflict Resolutions -Resolve merge conflicts from merging `main` into the `next` branch. +Verify that the automated conflict resolution (which kept the "ours"/next side for JSON and YAML files) was correct, and fix any issues. **SCOPE: Do not spawn tasks/sub-agents.** @@ -8,54 +8,63 @@ Resolve merge conflicts from merging `main` into the `next` branch. - **`prNumber`** — The PR number for the merge PR. - **`branch`** — The branch name (e.g., `ci/merge-main-to-next`). -- The working directory is the repo root, checked out on the merge branch with conflict markers present. +- The working directory is the repo root, checked out on the merge branch. +- Dependencies are installed and packages are built. +- Conflict markers in JSON/YAML files have already been stripped by the GitHub Action, keeping the "ours" (next) side. Source code files (`.ts`, `.js`, `.md`, `.astro`) may still have conflict markers. -## Strategy +## Background -When resolving conflicts, follow these rules based on file type: +The GitHub Action strips conflict markers from JSON/YAML files before `pnpm install` can run, always keeping the `next` branch side. This is usually correct (next has pre-release versions that must be preserved), but it may discard important changes from `main` — like new dependencies, updated dependency versions, or config changes from bug fixes. -### Changeset files (`.changeset/*.md`) +## Steps -- If a changeset file exists on `main` but not on `next`, it was likely already released. **Delete it.** The clean-changesets skill will handle this more thoroughly, but removing obvious ones here unblocks the merge. -- If both branches modified the same changeset, prefer the `next` version since it's the one targeting the upcoming major. +### Step 1: Resolve any remaining conflict markers -### `package.json` and version files +Check for conflict markers in source code files: -- **Prefer `next` branch versions.** The `next` branch has the pre-release versions (alpha/beta) which should not be overwritten by `main`'s stable versions. -- For dependency version updates from `main`, accept those — they are typically patches/minors that should be forward-ported. +```bash +grep -r "<<<<<<< " --include="*.ts" --include="*.js" --include="*.mjs" --include="*.cjs" --include="*.md" --include="*.astro" . 2>/dev/null | grep -v node_modules +``` -### `pnpm-lock.yaml` +If any exist, resolve them following these rules: +- **Prefer `main` for bug fixes.** If `main` fixed a bug, that fix should carry over to `next`. +- **Prefer `next` for API changes.** If `next` changed an API, keep the `next` version and adapt the `main` fix if needed. +- When in doubt, prefer `next` — it's the forward-looking branch. -- Do not try to manually resolve lockfile conflicts. Instead: - 1. Accept the `next` version: `git checkout --theirs pnpm-lock.yaml` - 2. The lockfile will be regenerated after `pnpm install` later. +After resolving, `git add` each file. -### Source code (`packages/**/*.ts`, `packages/**/*.js`) +### Step 2: Review what was lost from main -- **Prefer `main` for bug fixes.** If `main` fixed a bug, that fix should carry over to `next`. -- **Prefer `next` for API changes.** If `next` changed an API (new major version features), keep the `next` version and adapt the `main` fix to work with it if needed. -- When in doubt, prefer `next` — it's the forward-looking branch. +Compare what `main` had in the conflicted files against what we kept: -### Test files +```bash +# List files that were modified on both branches (potential conflict sites) +git diff --name-only origin/next...origin/main -- '*.json' '*.yaml' '*.yml' +``` -- If tests conflict, prefer `next` and adapt. Tests on `next` may test new major-version behavior. +For each file, compare the `main` version to the current version: -### Configuration files (`.changeset/config.json`, `tsconfig.json`, etc.) +```bash +git diff origin/main -- +``` -- Prefer `next` unless `main` has a clear fix that should be forward-ported. +Look for: +- **New dependencies** added on `main` that are missing from `next` — these should be added +- **Dependency version bumps** on `main` that are higher than what's on `next` — these should typically be accepted (they're patches/minors) +- **New scripts or config entries** added on `main` — these should be forward-ported +- **Version fields** (`"version":`) — keep `next`'s pre-release versions, do NOT use `main`'s stable versions -## Steps +### Step 3: Apply corrections + +If you find changes from `main` that should have been kept: +1. Apply those specific changes to the current files +2. Run `pnpm install --no-frozen-lockfile` if you modified any `package.json` files +3. `git add` the modified files + +### Step 4: Do NOT commit -1. Run `git diff --name-only --diff-filter=U` to list all conflicted files. -2. For each conflicted file, read the file and resolve the conflict following the strategy above. -3. After resolving each file, run `git add ` to mark it as resolved. -4. After all files are resolved, verify no conflict markers remain: `grep -r "<<<<<<< " --include="*.ts" --include="*.js" --include="*.json" --include="*.yaml" --include="*.yml" --include="*.md" .` -5. After conflicts are resolved, regenerate the lockfile and install dependencies: - ```bash - pnpm install --no-frozen-lockfile - ``` -6. Do NOT commit — the orchestrator will handle committing. +The orchestrator will handle committing. ## Output -Return the list of files that were resolved and whether all conflicts were successfully handled. +Return whether the conflict resolutions were correct and what files (if any) needed corrections. diff --git a/.flue/workflows/merge-fix/WORKFLOW.ts b/.flue/workflows/merge-fix/WORKFLOW.ts index 58b596eb5aeb..e230191e75b2 100644 --- a/.flue/workflows/merge-fix/WORKFLOW.ts +++ b/.flue/workflows/merge-fix/WORKFLOW.ts @@ -26,37 +26,25 @@ export const args = v.object({ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOutput) { const branch = 'ci/merge-main-to-next'; - // Step 1: Check for merge conflicts (unresolved conflict markers in files) - const conflictCheck = await flue.shell( - 'git diff --check HEAD 2>&1 || grep -r "<<<<<<< " --include="*.ts" --include="*.js" --include="*.json" --include="*.yaml" --include="*.yml" --include="*.md" --include="*.mjs" --include="*.cjs" . 2>/dev/null | head -20', - ); - const hasConflicts = conflictCheck.stdout.includes('<<<<<<<'); - - // Step 2: Resolve conflicts if any - if (hasConflicts) { - await flue.skill('merge/resolve-conflicts.md', { - args: { prNumber, branch }, - result: v.object({ - resolved: v.pipe( - v.boolean(), - v.description('true if all merge conflicts were resolved successfully'), - ), - filesResolved: v.pipe( - v.array(v.string()), - v.description('List of files where conflicts were resolved'), - ), - }), - }); - } - - // Step 3: Ensure dependencies are installed - // If conflicts were resolved, the resolve-conflicts skill already ran pnpm install. - // If not, we still need to install deps before proceeding. - if (!hasConflicts) { - await flue.shell('pnpm install --no-frozen-lockfile'); - } + // Step 1: Verify conflict resolutions and resolve any remaining source code conflicts. + // JSON/YAML conflicts were pre-stripped by the GitHub Action (keeping next side). + // This skill checks that nothing important from main was lost, and resolves + // any remaining conflict markers in .ts/.js/.md/.astro files. + const verifyResult = await flue.skill('merge/resolve-conflicts.md', { + args: { prNumber, branch }, + result: v.object({ + correct: v.pipe( + v.boolean(), + v.description('true if all conflict resolutions were correct or have been fixed'), + ), + correctedFiles: v.pipe( + v.array(v.string()), + v.description('List of files that needed corrections after verification'), + ), + }), + }); - // Step 4: Remove stale changesets that were already released on main + // Step 2: Remove stale changesets that were already released on main await flue.skill('merge/clean-changesets.md', { args: { prNumber }, result: v.object({ @@ -67,7 +55,7 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu }), }); - // Step 5: Run tests and fix failures + // Step 3: Run tests and fix failures const fixResult = await flue.skill('merge/fix-tests.md', { args: { prNumber }, result: v.object({ @@ -85,13 +73,13 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu }), }); - // Step 6: Commit and push all changes + // Step 4: Commit and push all changes const status = await flue.shell('git status --porcelain'); if (status.stdout.trim()) { await flue.shell('git add -A'); const commitParts = []; - if (hasConflicts) commitParts.push('resolve merge conflicts'); + if (verifyResult.correctedFiles.length > 0) commitParts.push('fix merge conflict resolutions'); if (fixResult.fixedFiles.length > 0) commitParts.push('fix test failures'); const commitMsg = commitParts.length > 0 @@ -107,9 +95,13 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu } } - // Step 7: Post a summary comment on the PR + // Step 5: Post a summary comment on the PR const summaryParts = []; - if (hasConflicts) summaryParts.push('- Resolved merge conflicts'); + if (verifyResult.correctedFiles.length > 0) { + summaryParts.push( + `- Fixed conflict resolutions in: ${verifyResult.correctedFiles.join(', ')}`, + ); + } if (fixResult.fixedFiles.length > 0) { summaryParts.push(`- Fixed test failures in: ${fixResult.fixedFiles.join(', ')}`); } @@ -141,7 +133,7 @@ ${fixResult.testsPass ? 'All tests pass — this PR should be ready for review.' return { pushed: true, testsPass: fixResult.testsPass, - hasConflicts, + correctedFiles: verifyResult.correctedFiles, remainingFailures: fixResult.remainingFailures, }; } diff --git a/.github/workflows/merge-fix.yml b/.github/workflows/merge-fix.yml index 461f5bd9b9de..e5840acc434c 100644 --- a/.github/workflows/merge-fix.yml +++ b/.github/workflows/merge-fix.yml @@ -16,7 +16,6 @@ on: env: IMAGE: ghcr.io/${{ github.repository }}/flue-sandbox - PNPM_STORE_DIR: .pnpm-store concurrency: group: merge-fix @@ -91,6 +90,37 @@ jobs: echo "skip=false" >> "$GITHUB_OUTPUT" fi + - name: Strip conflict markers from JSON/YAML files + if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' + # Merge conflicts in package.json files make pnpm install impossible. + # Strip conflict markers (keeping "ours"/next side) so pnpm can parse + # the workspace. The Flue verify skill will check via git diff whether + # anything important from main was lost and patch it back in. + run: | + node -e ' + const fs = require("fs"); + const path = require("path"); + let count = 0; + function fix(dir) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") { + fix(full); + } else if (/\.(json|yaml|yml)$/.test(entry.name)) { + let c = fs.readFileSync(full, "utf8"); + if (c.includes("<<<<<<<")) { + c = c.replace(/^<{7}.*\n([\s\S]*?)^={7}\n[\s\S]*?^>{7}.*\n/gm, "$1"); + fs.writeFileSync(full, c); + console.log("Stripped conflicts:", full); + count++; + } + } + } + } + fix("."); + console.log(count + " file(s) fixed"); + ' + - name: Setup PNPM if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 @@ -102,14 +132,13 @@ jobs: node-version: 24.15.0 cache: pnpm - - name: Install root deps only + - name: Install deps + if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' + run: pnpm install --no-frozen-lockfile + + - name: Build if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' - # Only install root workspace deps (where @flue/cli lives). - # Cannot install the full monorepo here because merge conflicts - # in package.json files would break pnpm install. - # Full install and build happen inside the Flue sandbox after - # conflicts are resolved. - run: pnpm install --no-frozen-lockfile --filter . + run: pnpm build - name: Log in to GHCR if: steps.pr.outputs.number != '' && steps.attempts.outputs.skip != 'true' From c1fe8327b13240e9554623a02d5d68ddd9de982e Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 30 Apr 2026 18:05:26 +0000 Subject: [PATCH 03/47] [ci] format --- .agents/skills/merge/resolve-conflicts.md | 3 +++ .flue/workflows/merge-fix/WORKFLOW.ts | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.agents/skills/merge/resolve-conflicts.md b/.agents/skills/merge/resolve-conflicts.md index 8c240754fddc..ac3f1f06b866 100644 --- a/.agents/skills/merge/resolve-conflicts.md +++ b/.agents/skills/merge/resolve-conflicts.md @@ -27,6 +27,7 @@ grep -r "<<<<<<< " --include="*.ts" --include="*.js" --include="*.mjs" --include ``` If any exist, resolve them following these rules: + - **Prefer `main` for bug fixes.** If `main` fixed a bug, that fix should carry over to `next`. - **Prefer `next` for API changes.** If `next` changed an API, keep the `next` version and adapt the `main` fix if needed. - When in doubt, prefer `next` — it's the forward-looking branch. @@ -49,6 +50,7 @@ git diff origin/main -- ``` Look for: + - **New dependencies** added on `main` that are missing from `next` — these should be added - **Dependency version bumps** on `main` that are higher than what's on `next` — these should typically be accepted (they're patches/minors) - **New scripts or config entries** added on `main` — these should be forward-ported @@ -57,6 +59,7 @@ Look for: ### Step 3: Apply corrections If you find changes from `main` that should have been kept: + 1. Apply those specific changes to the current files 2. Run `pnpm install --no-frozen-lockfile` if you modified any `package.json` files 3. `git add` the modified files diff --git a/.flue/workflows/merge-fix/WORKFLOW.ts b/.flue/workflows/merge-fix/WORKFLOW.ts index e230191e75b2..754b49dea01b 100644 --- a/.flue/workflows/merge-fix/WORKFLOW.ts +++ b/.flue/workflows/merge-fix/WORKFLOW.ts @@ -98,9 +98,7 @@ export default async function mergeFix(flue: FlueClient, { prNumber }: v.InferOu // Step 5: Post a summary comment on the PR const summaryParts = []; if (verifyResult.correctedFiles.length > 0) { - summaryParts.push( - `- Fixed conflict resolutions in: ${verifyResult.correctedFiles.join(', ')}`, - ); + summaryParts.push(`- Fixed conflict resolutions in: ${verifyResult.correctedFiles.join(', ')}`); } if (fixResult.fixedFiles.length > 0) { summaryParts.push(`- Fixed test failures in: ${fixResult.fixedFiles.join(', ')}`); From 86fd80dd17cf896e5eaa185b70576d839d789978 Mon Sep 17 00:00:00 2001 From: Chan <101856681+enjoyandlove@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:58:01 -0700 Subject: [PATCH 04/47] =?UTF-8?q?fix(render):=20avoid=20script=20dedup=20s?= =?UTF-8?q?tate=20consumption=20in=20inert=20template=20c=E2=80=A6=20(#165?= =?UTF-8?q?27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(render): avoid script dedup state consumption in inert template contexts #16525 * fix(render): normalize inert script dedup test outputs to strings * fix: add missing hydration metadata fields in inert-script-dedup tests * test(app): add component-level inert template script dedup coverage * test(app): stabilize inert script dedup regression coverage * fix(astro): move inert template dedup logic into hydration/directive script guards --- .changeset/short-snails-admire.md | 5 + .../astro/src/runtime/server/render/common.ts | 14 +- packages/astro/src/runtime/server/scripts.ts | 8 ++ .../test/units/app/inert-script-dedup.test.ts | 120 ++++++++++++++++++ .../units/render/inert-script-dedup.test.ts | 106 ++++++++++++++++ 5 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-snails-admire.md create mode 100644 packages/astro/test/units/app/inert-script-dedup.test.ts create mode 100644 packages/astro/test/units/render/inert-script-dedup.test.ts diff --git a/.changeset/short-snails-admire.md b/.changeset/short-snails-admire.md new file mode 100644 index 000000000000..333532d90316 --- /dev/null +++ b/.changeset/short-snails-admire.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Prevents script deduplication state from being consumed while rendering inert `