feat(FN-4978): merge fusion/fn-4978
This commit is contained in:
@@ -305,6 +305,13 @@ describe("ai-summarize", () => {
|
||||
expect(sanitizeTitle("Why?")).toBe("Why");
|
||||
});
|
||||
|
||||
it("strips empty placeholder groups (FN-4978 regression)", () => {
|
||||
expect(sanitizeTitle("Fix executor.ts stale session deadlock ( )")).toBe("Fix executor.ts stale session deadlock");
|
||||
expect(sanitizeTitle("Add feature []")).toBe("Add feature");
|
||||
expect(sanitizeTitle("Refactor (hotfix)")).toBe("Refactor (hotfix)");
|
||||
expect(sanitizeTitle("( )")).toBeNull();
|
||||
});
|
||||
|
||||
it("hard-caps at MAX_TITLE_LENGTH", () => {
|
||||
const long = "x".repeat(100);
|
||||
const out = sanitizeTitle(long)!;
|
||||
|
||||
@@ -48,9 +48,36 @@ describe("task-title-id-drift", () => {
|
||||
expect(normalizeTitleForTaskId("Foo FN-100:", "FN-999")).toEqual({ title: "Foo", changed: true });
|
||||
});
|
||||
|
||||
it("strips empty placeholder left by fn-token removal (FN-4978 regression)", () => {
|
||||
const normalized = normalizeTitleForTaskId("Fix executor.ts stale session deadlock (FN-1234)", "FN-9999");
|
||||
expect(normalized).toEqual({ title: "Fix executor.ts stale session deadlock", changed: true });
|
||||
expect(normalized.title).not.toContain("(");
|
||||
expect(normalized.title).not.toContain(")");
|
||||
});
|
||||
|
||||
it("strips empty square and curly bracket placeholders", () => {
|
||||
expect(normalizeTitleForTaskId("Bug [FN-1] thing", "FN-9")).toEqual({ title: "Bug thing", changed: true });
|
||||
expect(normalizeTitleForTaskId("Bug {FN-1} thing", "FN-9")).toEqual({ title: "Bug thing", changed: true });
|
||||
});
|
||||
|
||||
it("preserves valid qualifiers and matching row id token", () => {
|
||||
expect(normalizeTitleForTaskId("Refactor parser (hotfix)", "FN-9")).toEqual({
|
||||
title: "Refactor parser (hotfix)",
|
||||
changed: false,
|
||||
});
|
||||
expect(normalizeTitleForTaskId("Add docs (FN-9)", "FN-9")).toEqual({
|
||||
title: "Add docs (FN-9)",
|
||||
changed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("is idempotent", () => {
|
||||
const first = normalizeTitleForTaskId("Refinement: FN-100: foo", "FN-200");
|
||||
const second = normalizeTitleForTaskId(first.title ?? "", "FN-200");
|
||||
expect(second.changed).toBe(false);
|
||||
|
||||
const firstPlaceholderPass = normalizeTitleForTaskId("Fix executor.ts stale session deadlock (FN-1234)", "FN-9999");
|
||||
const secondPlaceholderPass = normalizeTitleForTaskId(firstPlaceholderPass.title ?? "", "FN-9999");
|
||||
expect(secondPlaceholderPass.changed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { getFnAgent, type AgentMessage } from "./ai-engine-loader.js";
|
||||
import { stripEmptyPlaceholders } from "./task-title-id-drift.js";
|
||||
|
||||
// ── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -880,6 +881,8 @@ export function sanitizeTitle(raw: string | undefined | null): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
title = stripEmptyPlaceholders(title);
|
||||
|
||||
// Drop trailing punctuation that summary-like sentences leave behind.
|
||||
title = title.replace(/[.!?,;:]+$/, "").trim();
|
||||
if (!title) return null;
|
||||
|
||||
@@ -3,6 +3,31 @@ import { MAX_TITLE_LENGTH } from "./ai-summarize.js";
|
||||
export const TASK_ID_TOKEN_RE = /\bFN-(\d+)\b/gi;
|
||||
|
||||
const CONNECTOR_RE = /[:\-—–]/;
|
||||
const EMPTY_PLACEHOLDER_CONTENT_RE = /^[\s,:;\-—–.!?]*$/;
|
||||
|
||||
export function stripEmptyPlaceholders(text: string): string {
|
||||
let normalized = text;
|
||||
|
||||
// Remove empty bracketed placeholders ((), [], {}) that contain only
|
||||
// whitespace or punctuation residue/connectors.
|
||||
normalized = normalized.replace(/\(([^)]*)\)|\[([^\]]*)\]|\{([^}]*)\}/g, (match, paren, square, brace) => {
|
||||
const content = (paren ?? square ?? brace ?? "").trim();
|
||||
return EMPTY_PLACEHOLDER_CONTENT_RE.test(content) ? " " : match;
|
||||
});
|
||||
|
||||
normalized = normalized
|
||||
.replace(/\s+([,:;.!?])/g, "$1")
|
||||
.replace(/([:\-—–])\s*(?=[:\-—–])/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
normalized = normalized
|
||||
.replace(new RegExp(`(?:\\s*${CONNECTOR_RE.source}\\s*)+$`), "")
|
||||
.replace(/[,:;.!?]+$/g, "")
|
||||
.trim();
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function extractTaskIdTokens(title: string): string[] {
|
||||
const tokens = new Set<string>();
|
||||
@@ -36,15 +61,7 @@ export function normalizeTitleForTaskId(
|
||||
}
|
||||
|
||||
let normalized = initial.replace(/\bFN-\d+\b\s*([:\-—–])?\s*/gi, " ");
|
||||
normalized = normalized
|
||||
.replace(/\s+([,:;.!?])/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
normalized = normalized
|
||||
.replace(new RegExp(`(?:\\s*${CONNECTOR_RE.source}\\s*)+$`), "")
|
||||
.replace(/[,:;.!?]+$/g, "")
|
||||
.trim();
|
||||
normalized = stripEmptyPlaceholders(normalized);
|
||||
|
||||
if (normalized.length > MAX_TITLE_LENGTH) {
|
||||
normalized = normalized.slice(0, MAX_TITLE_LENGTH).trim();
|
||||
|
||||
Reference in New Issue
Block a user