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:
@@ -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>"');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -10389,9 +10389,11 @@ export function buildExecutionPrompt(
|
||||
const prompt = scopePromptToWorktree(task.prompt, rootDir, worktreePath);
|
||||
const reviewLevel = parseReviewLevelFromPrompt(prompt);
|
||||
|
||||
// Build author arg for git commits based on settings
|
||||
// Build co-author trailer arg for git commits based on settings. The user's
|
||||
// configured git identity remains the primary author; Fusion is appended as
|
||||
// a `Co-authored-by` trailer for shared credit (recognized by GitHub).
|
||||
const authorArg = settings?.commitAuthorEnabled !== false
|
||||
? ` --author="${settings?.commitAuthorName || "Fusion"} <${settings?.commitAuthorEmail || "noreply@runfusion.ai"}>"`
|
||||
? ` -m "Co-authored-by: ${settings?.commitAuthorName || "Fusion"} <${settings?.commitAuthorEmail || "noreply@runfusion.ai"}>"`
|
||||
: "";
|
||||
|
||||
const sourceIssueRef = buildSourceIssueRef(task.sourceIssue);
|
||||
|
||||
@@ -5112,7 +5112,10 @@ async function applyBranchCommitsPreservingHistory(params: {
|
||||
};
|
||||
}
|
||||
|
||||
/** Build the --author flag for git commits based on project settings. */
|
||||
/** Build the `-m "Co-authored-by: ..."` trailer arg for git commits based on
|
||||
* project settings. The user's configured git identity remains the primary
|
||||
* author/committer; Fusion (or whatever name/email is configured) is appended
|
||||
* as a co-author trailer that GitHub recognizes for shared attribution. */
|
||||
function getCommitAuthorArg(settings: {
|
||||
commitAuthorEnabled?: boolean;
|
||||
commitAuthorName?: string;
|
||||
@@ -5121,7 +5124,7 @@ function getCommitAuthorArg(settings: {
|
||||
if (settings.commitAuthorEnabled === false) return "";
|
||||
const name = settings.commitAuthorName || "Fusion";
|
||||
const email = settings.commitAuthorEmail || "noreply@runfusion.ai";
|
||||
return ` --author="${name} <${email}>"`;
|
||||
return ` -m "Co-authored-by: ${name} <${email}>"`;
|
||||
}
|
||||
|
||||
export function buildSourceIssueRef(sourceIssue?: TaskSourceIssue | null): string {
|
||||
@@ -5151,7 +5154,7 @@ Message format:
|
||||
- **Summary:** one line describing what the squash brings in (imperative mood)
|
||||
- **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
|
||||
- **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
|
||||
${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
|
||||
${authorArg ? `- **Co-author:** Always include the \`Co-authored-by\` trailer as shown in the example above so Fusion is credited alongside your git identity.` : ""}
|
||||
|
||||
Example:
|
||||
\`\`\`
|
||||
@@ -5170,7 +5173,7 @@ Message format:
|
||||
- **Summary:** one line describing what the squash brings in (imperative mood)
|
||||
- **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
|
||||
- **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
|
||||
${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
|
||||
${authorArg ? `- **Co-author:** Always include the \`Co-authored-by\` trailer as shown in the example above so Fusion is credited alongside your git identity.` : ""}
|
||||
Do NOT include a scope in the commit message type.
|
||||
|
||||
Example:
|
||||
@@ -10764,14 +10767,14 @@ export function buildMergePrompt(params: MergePromptParams): string {
|
||||
"## ⚠️ There are merge conflicts",
|
||||
"Run `git diff --name-only --diff-filter=U` to see which files.",
|
||||
"Resolve each conflict, then `git add` the resolved files.",
|
||||
`After resolving all conflicts, write and run the commit command.${authorArg ? ` Be sure to include \`${authorArg.trim()}\` in the commit command.` : ""}`,
|
||||
`After resolving all conflicts, write and run the commit command.${authorArg ? ` Be sure to append \`${authorArg.trim()}\` to the commit command so Fusion is recorded as a co-author.` : ""}`,
|
||||
);
|
||||
} else {
|
||||
parts.push(
|
||||
"",
|
||||
"## No conflicts",
|
||||
"The merge applied cleanly. All changes are staged.",
|
||||
`Write and run the \`git commit\` command with a good message summarizing the work.${authorArg ? ` Be sure to include \`${authorArg.trim()}\` in the commit command.` : ""}`,
|
||||
`Write and run the \`git commit\` command with a good message summarizing the work.${authorArg ? ` Be sure to append \`${authorArg.trim()}\` to the commit command so Fusion is recorded as a co-author.` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user