From 7ae22a7ee5cb9a5432a94ed47d33ffea85f2748e Mon Sep 17 00:00:00 2001 From: Fusion Date: Mon, 4 May 2026 10:56:30 -0700 Subject: [PATCH] feat(FN-3410): finalize planning verification and launcher updates - Rework planning subtask descriptions and update planning logic/tests for clearer execution guidance - Add launcher/update notice handling in cli-alias with package-config coverage and release script updates - Refine agent detail and agents list UI/CSS for compressed mobile header behavior with updated dashboard tests - Add changesets and related docs/readme touch-ups for release tracking - Ref: runfusion/fusion#33 --- docs/task-management.md | 1 + packages/dashboard/README.md | 1 + .../dashboard/src/__tests__/planning.test.ts | 19 ++++++-- packages/dashboard/src/planning.ts | 44 ++++++++++++++++--- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/docs/task-management.md b/docs/task-management.md index 0bb3b3084..d2bb568a3 100644 --- a/docs/task-management.md +++ b/docs/task-management.md @@ -22,6 +22,7 @@ Use the 💡 button to open planning mode: - AI reasoning (thinking output) is preserved and visible throughout the session — expand the reasoning toggle to review the model's analysis before answering each question or accepting the summary - Produces summary + key deliverables - Create one task or **Break into Tasks** (multi-task generation with dependencies) +- Break-into-tasks descriptions are structured with subtask-specific guidance first, then a separate larger-plan context section (plus `## Planning Interview Context` when interview history exists) - Sessions persist when the modal is closed — resume from the sidebar list at any time; reasoning context is restored automatically ### 3) Todo item → Plan Mode diff --git a/packages/dashboard/README.md b/packages/dashboard/README.md index d8d400aa5..c5e8e83b8 100644 --- a/packages/dashboard/README.md +++ b/packages/dashboard/README.md @@ -21,6 +21,7 @@ AI-guided interactive planning for creating well-specified tasks from high-level - Suggested dependencies from existing tasks - Key deliverables checklist 5. Create the task directly from the summary +6. Or use **Break into Tasks** to generate multiple subtasks where each description starts with subtask-specific implementation guidance, followed by a separate larger-plan context section (including planning interview context when available) **Features**: - **Rate Limiting**: Maximum 5 planning sessions per hour per IP diff --git a/packages/dashboard/src/__tests__/planning.test.ts b/packages/dashboard/src/__tests__/planning.test.ts index eace3c203..c7fd1eb90 100644 --- a/packages/dashboard/src/__tests__/planning.test.ts +++ b/packages/dashboard/src/__tests__/planning.test.ts @@ -1974,13 +1974,18 @@ describe("planning module", () => { }); }); - it("appends planning interview context to subtask descriptions when history exists", async () => { + 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"); const result = generateSubtasksFromPlanning(sessionId); - expect(result.length).toBeGreaterThan(0); + expect(result.length).toBe(3); + expect(result[0]?.description).toContain('Implement "Implementation" as this subtask\'s primary outcome.'); + expect(result[1]?.description).toContain('Implement "Tests" as this subtask\'s primary outcome.'); + expect(result[2]?.description).toContain('Implement "Documentation" as this subtask\'s primary outcome.'); + + expect(result[0]?.description).toContain("## Larger Plan Context"); expect(result[0]?.description).toContain("## Planning Interview Context"); expect(result[0]?.description).toContain("**Q: What is the scope of this plan?**"); expect(result[0]?.description).toContain("A: Medium"); @@ -1990,7 +1995,7 @@ describe("planning module", () => { expect(result[0]?.description).toContain("A: Yes"); }); - it("keeps subtask descriptions unchanged when history is empty", async () => { + it("keeps larger-plan context section when history is empty", async () => { const mockIp = getUniqueIp(); const sessionId = await createCompletedSession(mockIp, "Build auth without context"); @@ -2005,7 +2010,9 @@ describe("planning module", () => { const result = generateSubtasksFromPlanning(sessionId); expect(result.length).toBeGreaterThan(0); for (const subtask of result) { - expect(subtask.description).toBe(session.summary.description); + expect(subtask.description).toContain("## Larger Plan Context"); + expect(subtask.description).toContain(session.summary.description); + expect(subtask.description).not.toContain("## Planning Interview Context"); } }); @@ -2049,6 +2056,10 @@ describe("planning module", () => { suggestedSize: "S", dependsOn: ["subtask-2"], }); + expect(result[0]?.description).toContain("Define the implementation approach for the plan"); + expect(result[1]?.description).toContain("Implement the core code changes described by the plan"); + expect(result[2]?.description).toContain("Verify the implementation end-to-end"); + expect(result[0]?.description).toContain("## Larger Plan Context"); }); it("assigns correct sizes based on deliverable position", async () => { diff --git a/packages/dashboard/src/planning.ts b/packages/dashboard/src/planning.ts index 4a6a010fe..6be2842b1 100644 --- a/packages/dashboard/src/planning.ts +++ b/packages/dashboard/src/planning.ts @@ -2143,6 +2143,23 @@ export function getSummary(sessionId: string): PlanningSummary | undefined { * @param sessionId - The planning session ID * @returns Array of SubtaskItem with titles derived from keyDeliverables, or fallback */ +function buildPlanningSubtaskDescription(input: { + taskGuidance: string; + summaryDescription: string; + qaSection: string; +}): string { + const contextSections = [ + "## Larger Plan Context", + input.summaryDescription, + ]; + + if (input.qaSection) { + contextSections.push(input.qaSection); + } + + return `${input.taskGuidance}\n\n${contextSections.join("\n\n")}`; +} + export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] { const session = sessions.get(sessionId); if (!session) return []; @@ -2150,9 +2167,6 @@ export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] { const { summary } = session; const qaSection = formatInterviewQA(session.history); - const descriptionWithContext = qaSection - ? `${summary.description}\n\n${qaSection}` - : summary.description; // If key deliverables exist, create one subtask per deliverable if (summary.keyDeliverables.length > 0) { @@ -2162,7 +2176,11 @@ export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] { return { id, title: deliverable, - description: descriptionWithContext, + description: buildPlanningSubtaskDescription({ + taskGuidance: `Implement "${deliverable}" as this subtask's primary outcome. Focus only on the concrete changes needed to deliver this item.`, + summaryDescription: summary.description, + qaSection, + }), suggestedSize: index === 0 ? "S" as const : index === summary.keyDeliverables.length - 1 ? "S" as const : "M" as const, dependsOn, }; @@ -2174,21 +2192,33 @@ export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] { { id: "subtask-1", title: "Define implementation approach", - description: descriptionWithContext, + description: buildPlanningSubtaskDescription({ + taskGuidance: "Define the implementation approach for the plan, including architecture and sequencing decisions needed before coding.", + summaryDescription: summary.description, + qaSection, + }), suggestedSize: "S" as const, dependsOn: [], }, { id: "subtask-2", title: "Implement core changes", - description: descriptionWithContext, + description: buildPlanningSubtaskDescription({ + taskGuidance: "Implement the core code changes described by the plan, using the agreed approach from the prior subtask.", + summaryDescription: summary.description, + qaSection, + }), suggestedSize: "M" as const, dependsOn: ["subtask-1"], }, { id: "subtask-3", title: "Verify and polish", - description: descriptionWithContext, + description: buildPlanningSubtaskDescription({ + taskGuidance: "Verify the implementation end-to-end, then polish quality items like tests, docs, and edge-case handling.", + summaryDescription: summary.description, + qaSection, + }), suggestedSize: "S" as const, dependsOn: ["subtask-2"], },