FN-5642: add AI-generated merge commit body summaries
Fusion-Task-Id: FN-5642 Fusion-Task-Lineage: 27701749-6107-4ffe-a83e-42873b06473b
This commit is contained in:
7
.changeset/fn-5642-merge-commit-body.md
Normal file
7
.changeset/fn-5642-merge-commit-body.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
When `useAiMergeCommitSummary` is enabled, AI-authored merge commits now include a richer body: the short narrative headline plus an AI-generated bullet summary of changed modules/files, followed by a `Files changed` diff stat block.
|
||||||
|
|
||||||
|
`mergeDetails.mergeCommitMessage` remains the short headline summary so dashboard UI consumers keep their existing concise display behavior.
|
||||||
47
packages/engine/src/__tests__/merger-ai-merge-body.test.ts
Normal file
47
packages/engine/src/__tests__/merger-ai-merge-body.test.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("../pi.js", () => ({
|
||||||
|
createFnAgent: vi.fn(),
|
||||||
|
describeModel: vi.fn(() => "mock-provider/mock-model"),
|
||||||
|
promptWithFallback: vi.fn(),
|
||||||
|
compactSessionContext: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("node:child_process", () => ({
|
||||||
|
execSync: vi.fn(() => ""),
|
||||||
|
exec: vi.fn(),
|
||||||
|
execFile: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { composeMergeCommitBody } from "../merger.js";
|
||||||
|
|
||||||
|
describe("composeMergeCommitBody", () => {
|
||||||
|
const commitLog = "- feat: one";
|
||||||
|
const diffStat = "1 file changed";
|
||||||
|
|
||||||
|
it("uses deterministic fallback when AI summary and AI body are absent", () => {
|
||||||
|
expect(composeMergeCommitBody({ branch: "fusion/FN-1", commitLog, diffStat })).toBe(
|
||||||
|
"Commits merged:\n- feat: one\n\nFiles changed:\n1 file changed",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("combines AI narrative + bullets + files changed", () => {
|
||||||
|
expect(composeMergeCommitBody({
|
||||||
|
branch: "fusion/FN-1",
|
||||||
|
commitLog,
|
||||||
|
diffStat,
|
||||||
|
aiSummary: "Narrative summary.",
|
||||||
|
aiBody: "- bullet one\n- bullet two",
|
||||||
|
})).toBe("Narrative summary.\n\n- bullet one\n- bullet two\n\nFiles changed:\n1 file changed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps AI narrative with files changed when bullets are absent", () => {
|
||||||
|
expect(composeMergeCommitBody({ branch: "fusion/FN-1", commitLog, diffStat, aiSummary: "Narrative summary." }))
|
||||||
|
.toBe("Narrative summary.\n\nFiles changed:\n1 file changed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps AI bullet body with files changed when narrative is absent", () => {
|
||||||
|
expect(composeMergeCommitBody({ branch: "fusion/FN-1", commitLog, diffStat, aiBody: "- bullet one" }))
|
||||||
|
.toBe("- bullet one\n\nFiles changed:\n1 file changed");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -861,6 +861,7 @@ describe("aiMergeTask — merge details collection", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(core, "summarizeMergeCommit").mockResolvedValue("AI summary of merged work.");
|
vi.spyOn(core, "summarizeMergeCommit").mockResolvedValue("AI summary of merged work.");
|
||||||
|
vi.spyOn(core, "summarizeCommitBody").mockResolvedValue("- touched merger.ts\n- added merge-body tests");
|
||||||
|
|
||||||
mockedExecSync.mockImplementation((cmd: any) => {
|
mockedExecSync.mockImplementation((cmd: any) => {
|
||||||
const cmdStr = String(cmd);
|
const cmdStr = String(cmd);
|
||||||
@@ -885,8 +886,14 @@ describe("aiMergeTask — merge details collection", () => {
|
|||||||
const updateCalls = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls;
|
const updateCalls = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls;
|
||||||
const mergeDetailsCall = updateCalls.find((call: any[]) => call[1]?.mergeDetails !== undefined);
|
const mergeDetailsCall = updateCalls.find((call: any[]) => call[1]?.mergeDetails !== undefined);
|
||||||
expect(mergeDetailsCall?.[1].mergeDetails.mergeCommitMessage).toBe("AI summary of merged work.");
|
expect(mergeDetailsCall?.[1].mergeDetails.mergeCommitMessage).toBe("AI summary of merged work.");
|
||||||
});
|
|
||||||
|
|
||||||
|
const commitCommand = mockedExecSync.mock.calls
|
||||||
|
.map((call) => String(call[0]))
|
||||||
|
.find((cmd) => cmd.includes("git commit"));
|
||||||
|
expect(commitCommand).toContain("AI summary of merged work.");
|
||||||
|
expect(commitCommand).toContain("- touched merger.ts");
|
||||||
|
expect(commitCommand).toContain("Files changed:\n1 file changed");
|
||||||
|
});
|
||||||
it("emits task:merged once when completeTask finalizes a successful merge", async () => {
|
it("emits task:merged once when completeTask finalizes a successful merge", async () => {
|
||||||
const store = createMockStore(
|
const store = createMockStore(
|
||||||
{ id: "FN-777", worktree: "/tmp/root/.worktrees/FN-777" },
|
{ id: "FN-777", worktree: "/tmp/root/.worktrees/FN-777" },
|
||||||
@@ -907,7 +914,7 @@ describe("aiMergeTask — merge details collection", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to raw commit log when AI merge summary returns null", async () => {
|
it("falls back to deterministic body when both AI merge summary and AI merge body return null", async () => {
|
||||||
const store = createMockStore(
|
const store = createMockStore(
|
||||||
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||||
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||||
@@ -919,6 +926,7 @@ describe("aiMergeTask — merge details collection", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
vi.spyOn(core, "summarizeMergeCommit").mockResolvedValue(null);
|
vi.spyOn(core, "summarizeMergeCommit").mockResolvedValue(null);
|
||||||
|
vi.spyOn(core, "summarizeCommitBody").mockResolvedValue(null);
|
||||||
|
|
||||||
mockedExecSync.mockImplementation((cmd: any) => {
|
mockedExecSync.mockImplementation((cmd: any) => {
|
||||||
const cmdStr = String(cmd);
|
const cmdStr = String(cmd);
|
||||||
@@ -943,6 +951,57 @@ describe("aiMergeTask — merge details collection", () => {
|
|||||||
const updateCalls = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls;
|
const updateCalls = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls;
|
||||||
const mergeDetailsCall = updateCalls.find((call: any[]) => call[1]?.mergeDetails !== undefined);
|
const mergeDetailsCall = updateCalls.find((call: any[]) => call[1]?.mergeDetails !== undefined);
|
||||||
expect(mergeDetailsCall?.[1].mergeDetails.mergeCommitMessage).toBe("- feat: something");
|
expect(mergeDetailsCall?.[1].mergeDetails.mergeCommitMessage).toBe("- feat: something");
|
||||||
|
|
||||||
|
const commitCommand = mockedExecSync.mock.calls
|
||||||
|
.map((call) => String(call[0]))
|
||||||
|
.find((cmd) => cmd.includes("git commit"));
|
||||||
|
expect(commitCommand).toContain("- feat: something");
|
||||||
|
expect(commitCommand).toContain("Files changed:\n1 file changed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses AI bullet body when AI merge summary is null", async () => {
|
||||||
|
const store = createMockStore(
|
||||||
|
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||||
|
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||||
|
);
|
||||||
|
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||||
|
...DEFAULT_SETTINGS,
|
||||||
|
mergeIntegrationWorktree: "cwd-main" as const,
|
||||||
|
useAiMergeCommitSummary: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.spyOn(core, "summarizeMergeCommit").mockResolvedValue(null);
|
||||||
|
vi.spyOn(core, "summarizeCommitBody").mockResolvedValue("- bullet one\n- bullet two");
|
||||||
|
|
||||||
|
mockedExecSync.mockImplementation((cmd: any) => {
|
||||||
|
const cmdStr = String(cmd);
|
||||||
|
if (cmdStr.includes("rev-parse --verify")) return Buffer.from("abc123");
|
||||||
|
if (cmdStr === "git rev-parse HEAD" || cmdStr.startsWith("git rev-parse HEAD ")) return "mergedcommit123456789";
|
||||||
|
if (cmdStr.includes("git log")) return "- feat: something";
|
||||||
|
if (cmdStr.includes("merge-base")) return Buffer.from("abc123");
|
||||||
|
if (cmdStr.includes("--stat")) return "1 file changed";
|
||||||
|
if (cmdStr.includes("merge --squash")) return Buffer.from("");
|
||||||
|
if (cmdStr.includes("diff --name-only --diff-filter=U")) return "";
|
||||||
|
if (cmdStr.includes("diff --cached --quiet")) return "1";
|
||||||
|
if (cmdStr.includes("git commit")) return Buffer.from("");
|
||||||
|
if (cmdStr.includes("show --shortstat")) return "1 file changed, 1 insertion(+)";
|
||||||
|
if (cmdStr.includes("branch -d") || cmdStr.includes("branch -D")) return Buffer.from("");
|
||||||
|
if (cmdStr.includes("worktree remove")) return Buffer.from("");
|
||||||
|
return Buffer.from("");
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await aiMergeTask(store, "/tmp/root", "FN-050");
|
||||||
|
expect(result.merged).toBe(true);
|
||||||
|
|
||||||
|
const updateCalls = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls;
|
||||||
|
const mergeDetailsCall = updateCalls.find((call: any[]) => call[1]?.mergeDetails !== undefined);
|
||||||
|
expect(mergeDetailsCall?.[1].mergeDetails.mergeCommitMessage).toBe("- feat: something");
|
||||||
|
|
||||||
|
const commitCommand = mockedExecSync.mock.calls
|
||||||
|
.map((call) => String(call[0]))
|
||||||
|
.find((cmd) => cmd.includes("git commit"));
|
||||||
|
expect(commitCommand).toContain("- bullet one");
|
||||||
|
expect(commitCommand).toContain("Files changed:\n1 file changed");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("recovers owned landed commit when branch is not found", async () => {
|
it("recovers owned landed commit when branch is not found", async () => {
|
||||||
|
|||||||
@@ -3754,6 +3754,33 @@ async function generateAiMergeSummary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateAiMergeBody(
|
||||||
|
commitLog: string,
|
||||||
|
diffStat: string,
|
||||||
|
settings: Settings,
|
||||||
|
rootDir: string,
|
||||||
|
branch: string,
|
||||||
|
taskId: string,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<string | null> {
|
||||||
|
const cleanStat = diffStat.trim();
|
||||||
|
if (!cleanStat) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
||||||
|
return await summarizeCommitBody(cleanStat, rootDir, resolved.provider, resolved.modelId, {
|
||||||
|
branch,
|
||||||
|
taskId,
|
||||||
|
commitLog,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
mergerLog.warn(`AI merge body failed; using deterministic fallback (${message})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function generateAiMergeSubject(
|
async function generateAiMergeSubject(
|
||||||
commitLog: string,
|
commitLog: string,
|
||||||
diffStat: string,
|
diffStat: string,
|
||||||
@@ -3881,6 +3908,41 @@ export function deriveDeterministicSubjectSummary(commitLog: string): string | n
|
|||||||
* 2. First step commit subject (with conventional prefix stripped) + `(+N more)`
|
* 2. First step commit subject (with conventional prefix stripped) + `(+N more)`
|
||||||
* 3. `merge <branch>` (last-resort, only when no step commits exist)
|
* 3. `merge <branch>` (last-resort, only when no step commits exist)
|
||||||
*/
|
*/
|
||||||
|
export function composeMergeCommitBody(params: {
|
||||||
|
branch: string;
|
||||||
|
commitLog: string;
|
||||||
|
diffStat?: string;
|
||||||
|
aiSummary?: string | null;
|
||||||
|
aiBody?: string | null;
|
||||||
|
}): string {
|
||||||
|
const { branch, commitLog, diffStat, aiSummary, aiBody } = params;
|
||||||
|
const trimmedSummary = aiSummary?.trim() ?? "";
|
||||||
|
const trimmedAiBody = aiBody?.trim() ?? "";
|
||||||
|
const trimmedCommitLog = commitLog?.trim() ?? "";
|
||||||
|
const trimmedDiffStat = diffStat?.trim() ?? "";
|
||||||
|
|
||||||
|
const commitsSection = trimmedCommitLog.length > 0
|
||||||
|
? trimmedCommitLog
|
||||||
|
: `- merge ${branch}`;
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (trimmedSummary.length > 0) parts.push(trimmedSummary);
|
||||||
|
if (trimmedAiBody.length > 0) parts.push(trimmedAiBody);
|
||||||
|
|
||||||
|
const deterministicFallback = [
|
||||||
|
`Commits merged:\n${commitsSection}`,
|
||||||
|
trimmedDiffStat.length > 0 ? `Files changed:\n${trimmedDiffStat}` : "",
|
||||||
|
].filter(Boolean).join("\n\n");
|
||||||
|
|
||||||
|
if (parts.length === 0) return deterministicFallback;
|
||||||
|
|
||||||
|
if (trimmedDiffStat.length > 0) {
|
||||||
|
parts.push(`Files changed:\n${trimmedDiffStat}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
async function buildDeterministicMergeMessage(params: {
|
async function buildDeterministicMergeMessage(params: {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
@@ -3888,9 +3950,10 @@ async function buildDeterministicMergeMessage(params: {
|
|||||||
diffStat?: string;
|
diffStat?: string;
|
||||||
includeTaskId: boolean;
|
includeTaskId: boolean;
|
||||||
aiSummary?: string | null;
|
aiSummary?: string | null;
|
||||||
|
aiBody?: string | null;
|
||||||
aiSubject?: string | null;
|
aiSubject?: string | null;
|
||||||
}): Promise<{ subjectArg: string; bodyArg: string }> {
|
}): Promise<{ subjectArg: string; bodyArg: string }> {
|
||||||
const { taskId, branch, commitLog, diffStat, includeTaskId, aiSummary, aiSubject } = params;
|
const { taskId, branch, commitLog, diffStat, includeTaskId, aiSummary, aiBody, aiSubject } = params;
|
||||||
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
||||||
const trimmedAiSubject = aiSubject?.trim() ?? "";
|
const trimmedAiSubject = aiSubject?.trim() ?? "";
|
||||||
const derived = trimmedAiSubject.length === 0
|
const derived = trimmedAiSubject.length === 0
|
||||||
@@ -3901,19 +3964,13 @@ async function buildDeterministicMergeMessage(params: {
|
|||||||
: (derived ?? `merge ${branch}`);
|
: (derived ?? `merge ${branch}`);
|
||||||
const subject = `${prefix}: ${subjectSummary}`;
|
const subject = `${prefix}: ${subjectSummary}`;
|
||||||
|
|
||||||
const trimmedCommitLog = commitLog?.trim() ?? "";
|
const body = composeMergeCommitBody({
|
||||||
const trimmedDiffStat = diffStat?.trim() ?? "";
|
branch,
|
||||||
|
commitLog,
|
||||||
const commitsSection = trimmedCommitLog.length > 0
|
diffStat,
|
||||||
? trimmedCommitLog
|
aiSummary,
|
||||||
: `- merge ${branch}`;
|
aiBody,
|
||||||
|
});
|
||||||
const body = aiSummary?.trim().length
|
|
||||||
? aiSummary.trim()
|
|
||||||
: [
|
|
||||||
`Commits merged:\n${commitsSection}`,
|
|
||||||
trimmedDiffStat.length > 0 ? `Files changed:\n${trimmedDiffStat}` : "",
|
|
||||||
].filter(Boolean).join("\n\n");
|
|
||||||
|
|
||||||
// -m args are double-quoted in the shell command, so escape backslashes,
|
// -m args are double-quoted in the shell command, so escape backslashes,
|
||||||
// double quotes, dollar signs, and backticks.
|
// double quotes, dollar signs, and backticks.
|
||||||
@@ -4448,6 +4505,7 @@ export async function commitOrAmendMergeWithFixes(
|
|||||||
diffStat: messageDiffStat,
|
diffStat: messageDiffStat,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
aiSummary,
|
aiSummary,
|
||||||
|
aiBody: undefined,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
});
|
});
|
||||||
let lineageId: string | undefined;
|
let lineageId: string | undefined;
|
||||||
@@ -8894,12 +8952,13 @@ export async function aiMergeTask(
|
|||||||
await store.appendAgentLog(taskId, routeMessage, "text", undefined, "merger");
|
await store.appendAgentLog(taskId, routeMessage, "text", undefined, "merger");
|
||||||
}
|
}
|
||||||
|
|
||||||
const aiMergeSummary = settings.useAiMergeCommitSummary
|
const [aiMergeSummary, aiMergeBody, aiMergeSubject] = settings.useAiMergeCommitSummary
|
||||||
? await generateAiMergeSummary(commitLog, diffStat, settings, rootDir)
|
? await Promise.all([
|
||||||
: null;
|
generateAiMergeSummary(commitLog, diffStat, settings, rootDir),
|
||||||
const aiMergeSubject = settings.useAiMergeCommitSummary
|
generateAiMergeBody(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal),
|
||||||
? await generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal)
|
generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal),
|
||||||
: null;
|
])
|
||||||
|
: [null, null, null] as const;
|
||||||
|
|
||||||
// 4b. Validate diff scope against task's declared File Scope
|
// 4b. Validate diff scope against task's declared File Scope
|
||||||
try {
|
try {
|
||||||
@@ -9008,6 +9067,7 @@ export async function aiMergeTask(
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
aiSummary: aiMergeSummary,
|
aiSummary: aiMergeSummary,
|
||||||
|
aiBody: aiMergeBody,
|
||||||
aiSubject: aiMergeSubject,
|
aiSubject: aiMergeSubject,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
sourceIssueRef,
|
sourceIssueRef,
|
||||||
@@ -9771,6 +9831,8 @@ export async function aiMergeTask(
|
|||||||
noOpVerifiedShortCircuit,
|
noOpVerifiedShortCircuit,
|
||||||
landedFilesAttributionRestricted,
|
landedFilesAttributionRestricted,
|
||||||
landedFilesCaptureFallback,
|
landedFilesCaptureFallback,
|
||||||
|
// Keep mergeDetails headline-only for dashboard cards; rich bullet body
|
||||||
|
// is used in the actual git commit message composition path.
|
||||||
mergeCommitMessage: aiMergeSummary || commitLog,
|
mergeCommitMessage: aiMergeSummary || commitLog,
|
||||||
mergedAt: new Date().toISOString(),
|
mergedAt: new Date().toISOString(),
|
||||||
mergeConfirmed: mergeConfirmedAtThisPoint,
|
mergeConfirmed: mergeConfirmedAtThisPoint,
|
||||||
@@ -10375,6 +10437,7 @@ interface MergeAttemptParams {
|
|||||||
commitLog: string;
|
commitLog: string;
|
||||||
diffStat: string;
|
diffStat: string;
|
||||||
aiSummary?: string | null;
|
aiSummary?: string | null;
|
||||||
|
aiBody?: string | null;
|
||||||
aiSubject?: string | null;
|
aiSubject?: string | null;
|
||||||
includeTaskId: boolean;
|
includeTaskId: boolean;
|
||||||
sourceIssueRef?: string;
|
sourceIssueRef?: string;
|
||||||
@@ -10432,6 +10495,7 @@ export async function executeMergeAttempt(
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
aiSummary,
|
aiSummary,
|
||||||
|
aiBody,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
sourceIssueRef,
|
sourceIssueRef,
|
||||||
@@ -10752,6 +10816,7 @@ export async function executeMergeAttempt(
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
aiSummary,
|
aiSummary,
|
||||||
|
aiBody,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
hasConflicts,
|
hasConflicts,
|
||||||
@@ -10977,7 +11042,7 @@ async function finalizeSideStrategyAttempt(
|
|||||||
side: "theirs" | "ours",
|
side: "theirs" | "ours",
|
||||||
aiTracker?: AiInvocationTracker,
|
aiTracker?: AiInvocationTracker,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { rootDir, branch, commitLog, diffStat, aiSummary, aiSubject, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand, testSource, buildSource } = params;
|
const { rootDir, branch, commitLog, diffStat, aiSummary, aiBody, aiSubject, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand, testSource, buildSource } = params;
|
||||||
|
|
||||||
const staged = execSyncText("git diff --cached --quiet 2>&1; echo $?", {
|
const staged = execSyncText("git diff --cached --quiet 2>&1; echo $?", {
|
||||||
cwd: rootDir,
|
cwd: rootDir,
|
||||||
@@ -11021,7 +11086,8 @@ async function finalizeSideStrategyAttempt(
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
aiSummary: aiSummary?.trim().length ? aiSummary : safeBody,
|
aiSummary,
|
||||||
|
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
});
|
});
|
||||||
await enforceSquashFileScopeInvariant({
|
await enforceSquashFileScopeInvariant({
|
||||||
@@ -11082,6 +11148,7 @@ interface AiAgentParams {
|
|||||||
commitLog: string;
|
commitLog: string;
|
||||||
diffStat: string;
|
diffStat: string;
|
||||||
aiSummary?: string | null;
|
aiSummary?: string | null;
|
||||||
|
aiBody?: string | null;
|
||||||
aiSubject?: string | null;
|
aiSubject?: string | null;
|
||||||
includeTaskId: boolean;
|
includeTaskId: boolean;
|
||||||
hasConflicts: boolean;
|
hasConflicts: boolean;
|
||||||
@@ -11128,6 +11195,7 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
aiSummary,
|
aiSummary,
|
||||||
|
aiBody,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
hasConflicts,
|
hasConflicts,
|
||||||
@@ -11404,7 +11472,8 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
|||||||
commitLog,
|
commitLog,
|
||||||
diffStat,
|
diffStat,
|
||||||
includeTaskId,
|
includeTaskId,
|
||||||
aiSummary: aiSummary?.trim().length ? aiSummary : safeBody,
|
aiSummary,
|
||||||
|
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
||||||
aiSubject,
|
aiSubject,
|
||||||
});
|
});
|
||||||
await runDiffVolumeGate({
|
await runDiffVolumeGate({
|
||||||
|
|||||||
Reference in New Issue
Block a user