feat(FN-5584): merge fusion/fn-5584
This commit is contained in:
@@ -132,6 +132,7 @@ Create a task via AI-guided planning mode — interactive conversation to refine
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `description` | string | — | Initial plan description (optional) — the AI will ask clarifying questions if not provided |
|
||||
| `baseBranch` | string | — | Optional base branch for the task created from this planning session |
|
||||
|
||||
## GitHub Tools
|
||||
|
||||
@@ -177,6 +178,7 @@ Create a new mission — a high-level objective that can span multiple milestone
|
||||
| `title` | string | ✓ | Mission title — brief but descriptive |
|
||||
| `description` | string | — | Detailed mission objectives and context |
|
||||
| `autoAdvance` | boolean | — | Automatically activate the next pending slice when the current slice completes |
|
||||
| `baseBranch` | string | — | Optional integration base branch for tasks triaged from this mission |
|
||||
|
||||
### fn_mission_list
|
||||
|
||||
|
||||
@@ -511,6 +511,7 @@ describe("bin command routing and fallbacks", () => {
|
||||
"Test Mission",
|
||||
"Detailed mission description",
|
||||
"demo",
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(runTaskPlan).toHaveBeenCalledWith("Plan a project task", true);
|
||||
expect(runTaskPlan).toHaveBeenCalledWith("Plan a project task", true, undefined, undefined);
|
||||
expect(result.details.taskId).toBe("PROJ-042");
|
||||
expect(result.content[0].text).toContain("Task PROJ-042");
|
||||
});
|
||||
@@ -1031,7 +1031,7 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
const tool = api.tools.get("fn_mission_create")!;
|
||||
const result = await tool.execute(
|
||||
"call-1",
|
||||
{ title: "Test Mission", description: "Test description", autoAdvance: true },
|
||||
{ title: "Test Mission", description: "Test description", autoAdvance: true, baseBranch: "develop" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
@@ -1043,6 +1043,11 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
expect(result.content[0].text).toContain("Created");
|
||||
expect(result.content[0].text).toContain("Test Mission");
|
||||
expect(result.content[0].text).toContain("Auto-advance: enabled");
|
||||
|
||||
const store = new TaskStore(tmpDir);
|
||||
await store.init();
|
||||
const mission = store.getMissionStore().getMission(result.details.missionId);
|
||||
expect(mission?.baseBranch).toBe("develop");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1029,16 +1029,20 @@ async function main() {
|
||||
case "plan": {
|
||||
const planArgs = args.slice(2);
|
||||
const yesFlag = planArgs.includes("--yes");
|
||||
let baseBranch: string | undefined;
|
||||
const descParts: string[] = [];
|
||||
for (let i = 0; i < planArgs.length; i++) {
|
||||
if (planArgs[i] === "--yes") {
|
||||
continue; // skip flag
|
||||
} else if (planArgs[i] === "--base-branch" && i + 1 < planArgs.length) {
|
||||
baseBranch = planArgs[i + 1];
|
||||
i++;
|
||||
} else {
|
||||
descParts.push(planArgs[i]);
|
||||
}
|
||||
}
|
||||
const initialPlan = descParts.join(" ");
|
||||
await runTaskPlan(initialPlan || undefined, yesFlag, projectName);
|
||||
await runTaskPlan(initialPlan || undefined, yesFlag, projectName, baseBranch);
|
||||
break;
|
||||
}
|
||||
case "list":
|
||||
@@ -1283,9 +1287,20 @@ async function main() {
|
||||
const subcommand = args[1];
|
||||
switch (subcommand) {
|
||||
case "create": {
|
||||
const title = args[2];
|
||||
const description = args.length > 3 ? args.slice(3).join(" ") : undefined;
|
||||
await runMissionCreate(title, description, projectName);
|
||||
const createArgs = args.slice(2);
|
||||
let baseBranch: string | undefined;
|
||||
const positional: string[] = [];
|
||||
for (let i = 0; i < createArgs.length; i++) {
|
||||
if (createArgs[i] === "--base-branch" && i + 1 < createArgs.length) {
|
||||
baseBranch = createArgs[i + 1];
|
||||
i++;
|
||||
} else {
|
||||
positional.push(createArgs[i]);
|
||||
}
|
||||
}
|
||||
const title = positional[0];
|
||||
const description = positional.length > 1 ? positional.slice(1).join(" ") : undefined;
|
||||
await runMissionCreate(title, description, projectName, baseBranch);
|
||||
break;
|
||||
}
|
||||
case "list":
|
||||
|
||||
@@ -199,6 +199,7 @@ describe("mission commands", () => {
|
||||
expect(mockMissionStore.createMission).toHaveBeenCalledWith({
|
||||
title: "Test Mission",
|
||||
description: "Test description",
|
||||
baseBranch: undefined,
|
||||
});
|
||||
expect(consoleCapture.logs).toContain(" ✓ Created M-001: Test Mission");
|
||||
} finally {
|
||||
@@ -206,6 +207,27 @@ describe("mission commands", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("passes baseBranch when provided", async () => {
|
||||
const mockMissionStore = createMockMissionStore();
|
||||
vi.mocked(getStore).mockResolvedValue({
|
||||
getMissionStore: () => mockMissionStore,
|
||||
} as any);
|
||||
|
||||
const consoleCapture = captureConsole();
|
||||
|
||||
try {
|
||||
await runMissionCreate("Test Mission", "Test description", undefined, "develop");
|
||||
|
||||
expect(mockMissionStore.createMission).toHaveBeenCalledWith({
|
||||
title: "Test Mission",
|
||||
description: "Test description",
|
||||
baseBranch: "develop",
|
||||
});
|
||||
} finally {
|
||||
consoleCapture.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("creates mission with title only (no description)", async () => {
|
||||
const mockMissionStore = createMockMissionStore();
|
||||
vi.mocked(getStore).mockResolvedValue({
|
||||
@@ -220,6 +242,7 @@ describe("mission commands", () => {
|
||||
expect(mockMissionStore.createMission).toHaveBeenCalledWith({
|
||||
title: "Test Mission",
|
||||
description: undefined,
|
||||
baseBranch: undefined,
|
||||
});
|
||||
} finally {
|
||||
consoleCapture.restore();
|
||||
@@ -250,6 +273,7 @@ describe("mission commands", () => {
|
||||
expect(mockMissionStore.createMission).toHaveBeenCalledWith({
|
||||
title: "Interactive Title",
|
||||
description: "Interactive Description",
|
||||
baseBranch: undefined,
|
||||
});
|
||||
} finally {
|
||||
consoleCapture.restore();
|
||||
|
||||
@@ -67,7 +67,12 @@ async function promptForTitleAndDescription(
|
||||
* Create a new mission with optional title and description.
|
||||
* If arguments are omitted, prompts interactively.
|
||||
*/
|
||||
export async function runMissionCreate(titleArg?: string, descriptionArg?: string, projectName?: string) {
|
||||
export async function runMissionCreate(
|
||||
titleArg?: string,
|
||||
descriptionArg?: string,
|
||||
projectName?: string,
|
||||
baseBranch?: string,
|
||||
) {
|
||||
const store = await getStore({ project: projectName });
|
||||
const missionStore = store.getMissionStore();
|
||||
|
||||
@@ -82,6 +87,7 @@ export async function runMissionCreate(titleArg?: string, descriptionArg?: strin
|
||||
const mission = missionStore.createMission({
|
||||
title,
|
||||
description,
|
||||
baseBranch: baseBranch?.trim() || undefined,
|
||||
});
|
||||
|
||||
console.log();
|
||||
|
||||
@@ -1815,7 +1815,12 @@ function wrapText(text: string, width: number): string[] {
|
||||
}
|
||||
|
||||
/** Run the planning mode */
|
||||
export async function runTaskPlan(initialPlanArg?: string, yesFlag = false, projectName?: string): Promise<string | undefined> {
|
||||
export async function runTaskPlan(
|
||||
initialPlanArg?: string,
|
||||
yesFlag = false,
|
||||
projectName?: string,
|
||||
baseBranch?: string,
|
||||
): Promise<string | undefined> {
|
||||
let initialPlan = initialPlanArg;
|
||||
|
||||
// If no initial plan, prompt interactively
|
||||
@@ -1954,6 +1959,7 @@ export async function runTaskPlan(initialPlanArg?: string, yesFlag = false, proj
|
||||
description: result.data.description,
|
||||
column: "triage",
|
||||
dependencies: result.data.suggestedDependencies,
|
||||
baseBranch: baseBranch?.trim() || undefined,
|
||||
source: { sourceType: "cli" },
|
||||
});
|
||||
|
||||
|
||||
@@ -1502,6 +1502,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
description: "Initial plan description (optional) — the AI will ask clarifying questions if not provided",
|
||||
})
|
||||
),
|
||||
baseBranch: Type.Optional(Type.String({ description: "Optional base branch for the task created from this planning session" })),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
||||
@@ -1526,7 +1527,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
|
||||
let taskId: string | undefined;
|
||||
try {
|
||||
taskId = await runTaskPlan(params.description, true); // Use --yes flag for non-interactive
|
||||
taskId = await runTaskPlan(params.description, true, undefined, params.baseBranch); // Use --yes flag for non-interactive
|
||||
} catch (err) {
|
||||
console.error = originalError;
|
||||
console.log = originalLog;
|
||||
@@ -2258,6 +2259,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
autoAdvance: Type.Optional(
|
||||
Type.Boolean({ description: "Automatically activate the next pending slice when the current slice completes" })
|
||||
),
|
||||
baseBranch: Type.Optional(Type.String({ description: "Optional integration base branch for tasks triaged from this mission" })),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
@@ -2267,6 +2269,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
const mission = missionStore.createMission({
|
||||
title: params.title.trim(),
|
||||
description: params.description?.trim(),
|
||||
baseBranch: params.baseBranch?.trim() || undefined,
|
||||
});
|
||||
|
||||
if (params.autoAdvance !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user