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
This commit is contained in:
Fusion
2026-05-04 10:56:30 -07:00
committed by gsxdsm
parent d06475b056
commit 7ae22a7ee5
4 changed files with 54 additions and 11 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 () => {

View File

@@ -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"],
},