feat: attribute Fusion as Co-authored-by trailer instead of primary author

Switch commits Fusion produces (both executor step commits and merger squash
commits) from setting `--author="Fusion <noreply@runfusion.ai>"` to appending
`-m "Co-authored-by: Fusion <noreply@runfusion.ai>"`. The user's configured
git identity now stays as the primary author/committer, and Fusion is recorded
as a co-author (recognized by GitHub for shared attribution). The
`commitAuthorEnabled` toggle and `commitAuthorName`/`commitAuthorEmail`
settings keep their existing keys; the dashboard settings UI relabels them
from "Author" to "Co-author" to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-22 20:38:11 -07:00
parent f58fb8955a
commit e291d86444
7 changed files with 53 additions and 38 deletions

View File

@@ -580,55 +580,57 @@ describe("buildExecutionPrompt", () => {
});
});
describe("commit author attribution", () => {
it("includes default author in commit instruction when commitAuthorEnabled is true", () => {
describe("commit co-author attribution", () => {
it("includes default co-author trailer in commit instruction when commitAuthorEnabled is true", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
commitAuthorEnabled: true,
} as any);
expect(result).toContain('--author="Fusion <noreply@runfusion.ai>"');
expect(result).toContain('-m "Co-authored-by: Fusion <noreply@runfusion.ai>"');
expect(result).not.toContain("--author=");
});
it("includes custom author name and email in commit instruction", () => {
it("includes custom co-author name and email in commit instruction", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
commitAuthorEnabled: true,
commitAuthorName: "CustomBot",
commitAuthorEmail: "bot@example.com",
} as any);
expect(result).toContain('--author="CustomBot <bot@example.com>"');
expect(result).toContain('-m "Co-authored-by: CustomBot <bot@example.com>"');
});
it("omits author from commit instruction when commitAuthorEnabled is false", () => {
it("omits co-author trailer from commit instruction when commitAuthorEnabled is false", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
commitAuthorEnabled: false,
} as any);
expect(result).not.toContain("Co-authored-by");
expect(result).not.toContain("--author");
// Should still contain commit instruction without author
// Should still contain commit instruction without co-author
expect(result).toContain("git commit -m");
});
it("uses default author when commitAuthorEnabled is true but name/email are undefined", () => {
it("uses default co-author when commitAuthorEnabled is true but name/email are undefined", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
commitAuthorEnabled: true,
commitAuthorName: undefined,
commitAuthorEmail: undefined,
} as any);
expect(result).toContain('--author="Fusion <noreply@runfusion.ai>"');
expect(result).toContain('-m "Co-authored-by: Fusion <noreply@runfusion.ai>"');
});
it("uses default author when settings is undefined", () => {
it("uses default co-author when settings is undefined", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project");
expect(result).toContain('--author="Fusion <noreply@runfusion.ai>"');
expect(result).toContain('-m "Co-authored-by: Fusion <noreply@runfusion.ai>"');
});
it("uses default author when settings is empty object", () => {
it("uses default co-author when settings is empty object", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {} as any);
expect(result).toContain('--author="Fusion <noreply@runfusion.ai>"');
expect(result).toContain('-m "Co-authored-by: Fusion <noreply@runfusion.ai>"');
});
});
});

View File

@@ -789,7 +789,7 @@ describe("buildMergePrompt — truncation behavior", () => {
expect(prompt).toContain(shortDiffStat);
});
it("includes author arg in no-conflicts commit instruction", async () => {
it("includes co-author trailer arg in no-conflicts commit instruction", async () => {
const { buildMergePrompt } = await import("../merger.js");
const prompt = buildMergePrompt({
@@ -798,13 +798,13 @@ describe("buildMergePrompt — truncation behavior", () => {
commitLog: "- feat: something",
diffStat: "1 file changed",
hasConflicts: false,
authorArg: ' --author="Fusion <noreply@runfusion.ai>"',
authorArg: ' -m "Co-authored-by: Fusion <noreply@runfusion.ai>"',
});
expect(prompt).toContain('Be sure to include `--author="Fusion <noreply@runfusion.ai>"` in the commit command');
expect(prompt).toContain('Be sure to append `-m "Co-authored-by: Fusion <noreply@runfusion.ai>"` to the commit command');
});
it("includes author arg in conflicts commit instruction", async () => {
it("includes co-author trailer arg in conflicts commit instruction", async () => {
const { buildMergePrompt } = await import("../merger.js");
const prompt = buildMergePrompt({
@@ -813,10 +813,10 @@ describe("buildMergePrompt — truncation behavior", () => {
commitLog: "- feat: something",
diffStat: "1 file changed",
hasConflicts: true,
authorArg: ' --author="CustomBot <bot@example.com>"',
authorArg: ' -m "Co-authored-by: CustomBot <bot@example.com>"',
});
expect(prompt).toContain('Be sure to include `--author="CustomBot <bot@example.com>"` in the commit command');
expect(prompt).toContain('Be sure to append `-m "Co-authored-by: CustomBot <bot@example.com>"` to the commit command');
});
it("omits author instruction when authorArg is not provided", async () => {