## Summary Wave 17 organizes Fusion into **domain folders** (stacks on #2397). ### Layout - **core/types/** — board, task, agents, settings, merge, workflow, mesh, … - **core/src/** — agents, ai, async-stores, workflows, tasks, config, db, … - **dashboard/app/api/** — client, tasks, agents, git, missions, planning, … - **engine/src/** — agents, auth, execution, merge, missions, overseer, worktree, … Root keepers retained for large entrypoints (`store.ts`, `executor.ts`, `merger.ts`, …). Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy) stay stable. ## Test plan - [x] `@fusion/core` typecheck - [x] `@fusion/engine` typecheck (pre-existing playwright-core noise only) - [ ] CI merge gate **Stack:** #2394 → #2397 → **this PR**
107 lines
4.3 KiB
TypeScript
107 lines
4.3 KiB
TypeScript
import { execSync, spawnSync } from "node:child_process";
|
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { resolveCapturedBaseCommitSha } from "../execution/base-commit-capture.js";
|
|
|
|
const hasGit = spawnSync("git", ["--version"], { stdio: "pipe" }).status === 0;
|
|
const describeIfGit = hasGit ? describe : describe.skip;
|
|
|
|
function git(repo: string, command: string): string {
|
|
return execSync(command, { cwd: repo, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
}
|
|
|
|
describeIfGit("resolveCapturedBaseCommitSha real-git scenarios", { timeout: 30_000 }, () => {
|
|
const dirs: string[] = [];
|
|
afterEach(() => {
|
|
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
function tmp(prefix: string): string {
|
|
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
dirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
function originFixture(): string {
|
|
const origin = tmp("fusion-base-capture-origin-");
|
|
git(origin, "git init -b main");
|
|
git(origin, 'git config user.email "test@example.com"');
|
|
git(origin, 'git config user.name "Test User"');
|
|
writeFileSync(join(origin, "README.md"), "init\n");
|
|
git(origin, "git add README.md && git commit -m 'init'");
|
|
return origin;
|
|
}
|
|
|
|
function cloneFixture(origin: string): string {
|
|
const clone = tmp("fusion-base-capture-clone-");
|
|
git(clone, `git clone ${JSON.stringify(origin)} .`);
|
|
git(clone, 'git config user.email "test@example.com"');
|
|
git(clone, 'git config user.name "Test User"');
|
|
return clone;
|
|
}
|
|
|
|
it("captures the local-main fork point when local main is ahead of origin/main (unpushed merges)", async () => {
|
|
// Models the FN-5937 regression: the merger lands other tasks' commits on
|
|
// LOCAL main first; new task branches fork from that tip before the
|
|
// rebase-and-push rewrites those SHAs. Capturing merge-base against
|
|
// origin/main rewinds past the unpushed merges, so the dashboard diff
|
|
// (baseCommitSha..HEAD) later surfaces the predecessors' files as this
|
|
// task's "files changed".
|
|
const origin = originFixture();
|
|
const clone = cloneFixture(origin);
|
|
|
|
// Local main advances by a merged-but-unpushed predecessor task commit.
|
|
writeFileSync(join(clone, "predecessor.txt"), "FN-5936 work\n");
|
|
git(clone, "git add predecessor.txt && git commit -m 'FN-5936: predecessor task'");
|
|
const localMainTip = git(clone, "git rev-parse HEAD");
|
|
|
|
// New task branch forks from local main (prepareForTask behavior).
|
|
git(clone, "git checkout -B fusion/fn-5937-test main");
|
|
|
|
const captured = await resolveCapturedBaseCommitSha(clone);
|
|
expect(captured).toBe(localMainTip);
|
|
});
|
|
|
|
it("captures the merge-base with main for a branch with its own commits", async () => {
|
|
const origin = originFixture();
|
|
const clone = cloneFixture(origin);
|
|
const forkPoint = git(clone, "git rev-parse HEAD");
|
|
|
|
git(clone, "git checkout -B fusion/fn-100-test main");
|
|
writeFileSync(join(clone, "feature.txt"), "feature\n");
|
|
git(clone, "git add feature.txt && git commit -m 'FN-100: feature'");
|
|
|
|
const captured = await resolveCapturedBaseCommitSha(clone);
|
|
expect(captured).toBe(forkPoint);
|
|
});
|
|
|
|
it("falls back to origin/main when no local main branch exists", async () => {
|
|
const origin = originFixture();
|
|
const clone = cloneFixture(origin);
|
|
const originMainSha = git(clone, "git rev-parse origin/main");
|
|
|
|
// Detach and delete local main so only origin/main can resolve.
|
|
git(clone, "git checkout --detach origin/main");
|
|
git(clone, "git branch -D main");
|
|
git(clone, "git checkout -B fusion/fn-200-test");
|
|
|
|
const captured = await resolveCapturedBaseCommitSha(clone);
|
|
expect(captured).toBe(originMainSha);
|
|
});
|
|
|
|
it("falls back to HEAD when neither main nor origin/main resolves", async () => {
|
|
const repo = tmp("fusion-base-capture-nomain-");
|
|
git(repo, "git init -b trunk");
|
|
git(repo, 'git config user.email "test@example.com"');
|
|
git(repo, 'git config user.name "Test User"');
|
|
writeFileSync(join(repo, "README.md"), "init\n");
|
|
git(repo, "git add README.md && git commit -m 'init'");
|
|
const head = git(repo, "git rev-parse HEAD");
|
|
|
|
const captured = await resolveCapturedBaseCommitSha(repo);
|
|
expect(captured).toBe(head);
|
|
});
|
|
});
|