feat(FN-3716): add planning mode priority controls for task routes and subt
Adds per-subtask priority controls to planning mode with three implementation steps (route priority support, UI controls, and design-token polish), gates research tools behind an experimental feature flag with a new core infrastructure module, and introduces a WhatsApp chat plugin with discovery and Fusion-Task-Id: FN-3716
This commit is contained in:
@@ -2068,6 +2068,7 @@ describe("planning module", () => {
|
||||
title: "Implementation",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "S",
|
||||
priority: "normal",
|
||||
dependsOn: [],
|
||||
});
|
||||
|
||||
@@ -2077,6 +2078,7 @@ describe("planning module", () => {
|
||||
title: "Tests",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "M",
|
||||
priority: "normal",
|
||||
dependsOn: ["subtask-1"],
|
||||
});
|
||||
|
||||
@@ -2086,10 +2088,26 @@ describe("planning module", () => {
|
||||
title: "Documentation",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "S",
|
||||
priority: "normal",
|
||||
dependsOn: ["subtask-2"],
|
||||
});
|
||||
});
|
||||
|
||||
it("inherits summary priority for generated subtasks", async () => {
|
||||
const mockIp = getUniqueIp();
|
||||
const sessionId = await createCompletedSession(mockIp, "Build auth with urgent priority");
|
||||
|
||||
const session = getSession(sessionId);
|
||||
if (!session?.summary) {
|
||||
throw new Error("Expected summary to exist for completed session");
|
||||
}
|
||||
session.summary.priority = "urgent";
|
||||
|
||||
const result = generateSubtasksFromPlanning(sessionId);
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(result.every((subtask) => subtask.priority === "urgent")).toBe(true);
|
||||
});
|
||||
|
||||
it("generates deliverable subtasks with distinct lead guidance plus separate plan context", async () => {
|
||||
const mockIp = getUniqueIp();
|
||||
const sessionId = await createCompletedSession(mockIp, "Build auth system with context");
|
||||
@@ -2156,6 +2174,7 @@ describe("planning module", () => {
|
||||
title: "Define implementation approach",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "S",
|
||||
priority: "normal",
|
||||
dependsOn: [],
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
@@ -2163,6 +2182,7 @@ describe("planning module", () => {
|
||||
title: "Implement core changes",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "M",
|
||||
priority: "normal",
|
||||
dependsOn: ["subtask-1"],
|
||||
});
|
||||
expect(result[2]).toEqual({
|
||||
@@ -2170,6 +2190,7 @@ describe("planning module", () => {
|
||||
title: "Verify and polish",
|
||||
description: expect.any(String),
|
||||
suggestedSize: "S",
|
||||
priority: "normal",
|
||||
dependsOn: ["subtask-2"],
|
||||
});
|
||||
expect(result[0]?.description).toContain("Define the implementation approach for the plan");
|
||||
|
||||
@@ -1400,6 +1400,7 @@ describe("Planning Mode Routes", () => {
|
||||
title: "Edited auth task",
|
||||
description: "Edited description from summary view",
|
||||
dependencies: ["FN-500"],
|
||||
priority: "normal",
|
||||
}),
|
||||
);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-099", { size: "S" });
|
||||
@@ -1473,6 +1474,130 @@ describe("Planning Mode Routes", () => {
|
||||
expect(mockAiSessionStore.delete).toHaveBeenCalledWith(sessionId);
|
||||
});
|
||||
|
||||
it("creates task with explicit summary priority", async () => {
|
||||
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
id: "FN-100",
|
||||
description: "Priority task",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({});
|
||||
(store.logEntry as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||
|
||||
const startRes = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/planning/start",
|
||||
JSON.stringify({ initialPlan: "Build a user auth system" }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
const sessionId = startRes.body.sessionId;
|
||||
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId, responses: { scope: "medium" } }), { "Content-Type": "application/json" });
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId, responses: { requirements: "Must have login" } }), { "Content-Type": "application/json" });
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId, responses: { confirm: true } }), { "Content-Type": "application/json" });
|
||||
|
||||
const res = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/planning/create-task",
|
||||
JSON.stringify({
|
||||
sessionId,
|
||||
summary: {
|
||||
title: "Priority auth task",
|
||||
description: "High-priority planning output",
|
||||
suggestedSize: "M",
|
||||
priority: "high",
|
||||
suggestedDependencies: [],
|
||||
keyDeliverables: ["Login flow"],
|
||||
},
|
||||
}),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(store.createTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: "Priority auth task",
|
||||
priority: "high",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("creates multiple planning tasks with per-subtask priorities and defaults", async () => {
|
||||
(store.createTask as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
id: "FN-201",
|
||||
description: "First",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
id: "FN-202",
|
||||
description: "Second",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({});
|
||||
(store.logEntry as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||
|
||||
const startRes = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/planning/start",
|
||||
JSON.stringify({ initialPlan: "Build a user auth system" }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
const planningSessionId = startRes.body.sessionId;
|
||||
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId: planningSessionId, responses: { scope: "medium" } }), { "Content-Type": "application/json" });
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId: planningSessionId, responses: { requirements: "Must have login" } }), { "Content-Type": "application/json" });
|
||||
await REQUEST(buildApp(), "POST", "/api/planning/respond", JSON.stringify({ sessionId: planningSessionId, responses: { confirm: true } }), { "Content-Type": "application/json" });
|
||||
|
||||
const res = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/planning/create-tasks",
|
||||
JSON.stringify({
|
||||
planningSessionId,
|
||||
subtasks: [
|
||||
{
|
||||
id: "subtask-1",
|
||||
title: "Auth backend",
|
||||
description: "Implement backend",
|
||||
suggestedSize: "M",
|
||||
priority: "urgent",
|
||||
dependsOn: [],
|
||||
},
|
||||
{
|
||||
id: "subtask-2",
|
||||
title: "Auth UI",
|
||||
description: "Implement UI",
|
||||
suggestedSize: "S",
|
||||
dependsOn: ["subtask-1"],
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(store.createTask).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ title: "Auth backend", priority: "urgent" }),
|
||||
);
|
||||
expect(store.createTask).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ title: "Auth UI", priority: "normal" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns 400 if session is not complete", async () => {
|
||||
// Create a session but don't complete it
|
||||
const startRes = await REQUEST(
|
||||
|
||||
Reference in New Issue
Block a user