feat(FN-4184): defer untitled issues in github-tracking until they receive

Add changeset and implementation to defer untitled GitHub tracking issues, with corresponding tests covering the no-title case.

Fusion-Task-Id: FN-4184
This commit is contained in:
Fusion
2026-05-12 14:05:32 -07:00
committed by gsxdsm
parent d49e0bae3d
commit a159e9772d
3 changed files with 83 additions and 0 deletions

View File

@@ -264,6 +264,68 @@ describe("maybeCreateTrackingIssue", () => {
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("AI title summarizer failed"));
});
it("returns no_title_available and records activity when no title can be derived", async () => {
const recordActivity = vi.fn();
const logger = { warn: vi.fn(), info: vi.fn() };
const result = await maybeCreateTrackingIssue(buildTask({ title: " ", description: "\n```ts\nconst hidden = true;\n```\n\n ", githubTracking: { enabled: true } }), {
taskStore: { recordActivity } as any,
projectSettings: {},
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
rootDir,
logger,
});
expect(result).toEqual({ created: false, reason: "no_title_available" });
expect(createIssueMock).not.toHaveBeenCalled();
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
type: "task:updated",
details: "GitHub tracking issue not created: task has no title yet",
metadata: expect.objectContaining({ type: "github-tracking-no-title" }),
}));
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("deferred — no usable title"));
});
it("returns no_title_available when the summarizer yields no title and no fallback exists", async () => {
const recordActivity = vi.fn();
const result = await maybeCreateTrackingIssue(buildTask({
title: "",
description: `${" ".repeat(MIN_DESCRIPTION_LENGTH)}\n` + "```md\nignored\n```",
githubTracking: { enabled: true },
}), {
taskStore: { recordActivity, updateTask: vi.fn() } as any,
projectSettings: { titleSummarizerProvider: "anthropic", titleSummarizerModelId: "claude" } as any,
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
rootDir,
logger: { warn: vi.fn(), info: vi.fn() },
});
expect(summarizeTitleMock).toHaveBeenCalledTimes(1);
expect(result).toEqual({ created: false, reason: "no_title_available" });
expect(createIssueMock).not.toHaveBeenCalled();
expect(recordActivity).toHaveBeenCalledWith(expect.objectContaining({
metadata: expect.objectContaining({ type: "github-tracking-no-title" }),
}));
});
it("still creates an issue when the summarizer fails but a description fallback exists", async () => {
const logger = { warn: vi.fn(), info: vi.fn() };
summarizeTitleMock.mockRejectedValue(new AiServiceError("model unavailable"));
const result = await maybeCreateTrackingIssue(buildTask({ title: "", description: longDescription, githubTracking: { enabled: true } }), {
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn(), updateTask: vi.fn() } as any,
projectSettings: { titleSummarizerProvider: "anthropic", titleSummarizerModelId: "claude" } as any,
globalSettings: { githubTrackingDefaultRepo: "o/r" } as any,
rootDir,
logger,
});
expect(result.created).toBe(true);
expect(createIssueMock).toHaveBeenCalledWith(expect.objectContaining({ title: "[FN-1] Derived fallback title." }));
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("AI title summarizer failed"));
});
it("does not invoke the summarizer when the description is too short", async () => {
await maybeCreateTrackingIssue(buildTask({ title: "", description: "Short title fallback", githubTracking: { enabled: true } }), {
taskStore: { linkGithubIssue: vi.fn(), recordActivity: vi.fn(), updateTask: vi.fn() } as any,