feat(FN-5220): guard explicit duplicate markers in triage and self-healing
Adds an explicit duplicate-marker guard (FN-5220) spanning core helper, dashboard API endpoint, triage short-circuit, and self-healing sweep to detect and handle duplicate task creation attempts; includes comprehensive test coverage across unit, API, and integration layers plus documentation. Fusion-Task-Id: FN-5220
This commit is contained in:
committed by
gsxdsm
parent
9eea9f48f0
commit
8f2d5e7e61
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseExplicitDuplicateMarker } from "../explicit-duplicate-marker.js";
|
||||
|
||||
const FULL_PROMPT = `# Task: FN-5211 - Example
|
||||
|
||||
## Mission
|
||||
This is a duplicate-handling task, but it is a full spec body.
|
||||
|
||||
- Implement the fix
|
||||
- Verify the result
|
||||
`;
|
||||
|
||||
describe("parseExplicitDuplicateMarker", () => {
|
||||
it("parses a canonical marker", () => {
|
||||
expect(parseExplicitDuplicateMarker("DUPLICATE: FN-5211")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes case and surrounding whitespace", () => {
|
||||
expect(parseExplicitDuplicateMarker(" duplicate: fn-5211\n")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a backtick-wrapped marker", () => {
|
||||
expect(parseExplicitDuplicateMarker("`DUPLICATE: FN-5211`")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a bold-wrapped marker", () => {
|
||||
expect(parseExplicitDuplicateMarker("**DUPLICATE: FN-5211**")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a marker padded by blank lines", () => {
|
||||
expect(parseExplicitDuplicateMarker("\n\n\nDUPLICATE: FN-5211\n\n")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a fenced marker", () => {
|
||||
expect(parseExplicitDuplicateMarker("```text\nDUPLICATE: FN-5211\n```")).toEqual({
|
||||
canonicalId: "FN-5211",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a full prompt body that merely mentions duplicate", () => {
|
||||
expect(parseExplicitDuplicateMarker(FULL_PROMPT)).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects extra prose after the marker", () => {
|
||||
expect(parseExplicitDuplicateMarker("DUPLICATE: FN-5211\n\nSee also FN-5212")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects multiple markers", () => {
|
||||
expect(parseExplicitDuplicateMarker("DUPLICATE: FN-5211\nDUPLICATE: FN-5212")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects empty input", () => {
|
||||
expect(parseExplicitDuplicateMarker(" ")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects non-FN identifiers", () => {
|
||||
expect(parseExplicitDuplicateMarker("DUPLICATE: NOT-1234")).toBeNull();
|
||||
});
|
||||
});
|
||||
52
packages/core/src/explicit-duplicate-marker.ts
Normal file
52
packages/core/src/explicit-duplicate-marker.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export interface ExplicitDuplicateMarker {
|
||||
canonicalId: string;
|
||||
}
|
||||
|
||||
function stripCodeFenceLayer(content: string): string {
|
||||
const fenceMatch = content.match(/^```(?:[\t ]*(?:text|markdown))?[\t ]*\n([\s\S]*?)\n```$/i);
|
||||
if (!fenceMatch) {
|
||||
return content;
|
||||
}
|
||||
return fenceMatch[1] ?? "";
|
||||
}
|
||||
|
||||
function stripSingleWrapper(line: string): string {
|
||||
if (line.startsWith("`") && line.endsWith("`") && line.length >= 2) {
|
||||
return line.slice(1, -1).trim();
|
||||
}
|
||||
if (line.startsWith("**") && line.endsWith("**") && line.length >= 4) {
|
||||
return line.slice(2, -2).trim();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the canonical triage "redirect" marker emitted by the planning
|
||||
* agent when the new task duplicates an existing one.
|
||||
*/
|
||||
export function parseExplicitDuplicateMarker(content: string): ExplicitDuplicateMarker | null {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const withoutFence = stripCodeFenceLayer(trimmed).trim();
|
||||
const nonBlankLines = withoutFence
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
|
||||
if (nonBlankLines.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidate = stripSingleWrapper(nonBlankLines[0] ?? "");
|
||||
const match = candidate.match(/^DUPLICATE:\s*(FN-\d+)\s*$/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
canonicalId: match[1].toUpperCase(),
|
||||
};
|
||||
}
|
||||
@@ -156,6 +156,10 @@ export {
|
||||
type NearDuplicateMatch,
|
||||
} from "./near-duplicate.js";
|
||||
export { getTaskDuplicateLineage } from "./duplicate-lineage.js";
|
||||
export {
|
||||
parseExplicitDuplicateMarker,
|
||||
type ExplicitDuplicateMarker,
|
||||
} from "./explicit-duplicate-marker.js";
|
||||
export {
|
||||
__getDeterministicGuardMutexSize,
|
||||
deterministicGuardLocks,
|
||||
|
||||
Reference in New Issue
Block a user