feat: expose GSD planning tools over MCP
This commit is contained in:
parent
7cc2d11d34
commit
f7008107fb
7 changed files with 400 additions and 72 deletions
|
|
@ -42,14 +42,14 @@ function makeMockServer() {
|
|||
}
|
||||
|
||||
describe("workflow MCP tools", () => {
|
||||
it("registers the three workflow tools", () => {
|
||||
it("registers the five workflow tools", () => {
|
||||
const server = makeMockServer();
|
||||
registerWorkflowTools(server as any);
|
||||
|
||||
assert.equal(server.tools.length, 3);
|
||||
assert.equal(server.tools.length, 5);
|
||||
assert.deepEqual(
|
||||
server.tools.map((t) => t.name),
|
||||
["gsd_summary_save", "gsd_task_complete", "gsd_milestone_status"],
|
||||
["gsd_plan_milestone", "gsd_plan_slice", "gsd_summary_save", "gsd_task_complete", "gsd_milestone_status"],
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -124,4 +124,68 @@ describe("workflow MCP tools", () => {
|
|||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("gsd_plan_milestone and gsd_plan_slice work end-to-end", async () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const server = makeMockServer();
|
||||
registerWorkflowTools(server as any);
|
||||
const milestoneTool = server.tools.find((t) => t.name === "gsd_plan_milestone");
|
||||
const sliceTool = server.tools.find((t) => t.name === "gsd_plan_slice");
|
||||
assert.ok(milestoneTool, "milestone planning tool should be registered");
|
||||
assert.ok(sliceTool, "slice planning tool should be registered");
|
||||
|
||||
const milestoneResult = await milestoneTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M001",
|
||||
title: "Workflow MCP planning",
|
||||
vision: "Plan milestone over MCP.",
|
||||
slices: [
|
||||
{
|
||||
sliceId: "S01",
|
||||
title: "Bridge planning",
|
||||
risk: "medium",
|
||||
depends: [],
|
||||
demo: "Milestone plan persists through MCP.",
|
||||
goal: "Persist roadmap state.",
|
||||
successCriteria: "ROADMAP.md renders from DB.",
|
||||
proofLevel: "integration",
|
||||
integrationClosure: "Prompts and MCP call the same handler.",
|
||||
observabilityImpact: "Executor tests cover output paths.",
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.match((milestoneResult as any).content[0].text as string, /Planned milestone M001/);
|
||||
|
||||
const sliceResult = await sliceTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M001",
|
||||
sliceId: "S01",
|
||||
goal: "Persist slice plan over MCP.",
|
||||
tasks: [
|
||||
{
|
||||
taskId: "T01",
|
||||
title: "Add planning bridge",
|
||||
description: "Implement the shared executor path.",
|
||||
estimate: "15m",
|
||||
files: ["src/resources/extensions/gsd/tools/workflow-tool-executors.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["ROADMAP.md"],
|
||||
expectedOutput: ["S01-PLAN.md", "T01-PLAN.md"],
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.match((sliceResult as any).content[0].text as string, /Planned slice S01/);
|
||||
assert.ok(
|
||||
existsSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md")),
|
||||
"slice plan should exist on disk",
|
||||
);
|
||||
assert.ok(
|
||||
existsSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-PLAN.md")),
|
||||
"task plan should exist on disk",
|
||||
);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,61 @@ const SUMMARY_ARTIFACT_TYPES = ["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT",
|
|||
type WorkflowToolExecutors = {
|
||||
SUPPORTED_SUMMARY_ARTIFACT_TYPES: readonly string[];
|
||||
executeMilestoneStatus: (params: { milestoneId: string }) => Promise<unknown>;
|
||||
executePlanMilestone: (
|
||||
params: {
|
||||
milestoneId: string;
|
||||
title: string;
|
||||
vision: string;
|
||||
slices: Array<{
|
||||
sliceId: string;
|
||||
title: string;
|
||||
risk: string;
|
||||
depends: string[];
|
||||
demo: string;
|
||||
goal: string;
|
||||
successCriteria: string;
|
||||
proofLevel: string;
|
||||
integrationClosure: string;
|
||||
observabilityImpact: string;
|
||||
}>;
|
||||
status?: string;
|
||||
dependsOn?: string[];
|
||||
successCriteria?: string[];
|
||||
keyRisks?: Array<{ risk: string; whyItMatters: string }>;
|
||||
proofStrategy?: Array<{ riskOrUnknown: string; retireIn: string; whatWillBeProven: string }>;
|
||||
verificationContract?: string;
|
||||
verificationIntegration?: string;
|
||||
verificationOperational?: string;
|
||||
verificationUat?: string;
|
||||
definitionOfDone?: string[];
|
||||
requirementCoverage?: string;
|
||||
boundaryMapMarkdown?: string;
|
||||
},
|
||||
basePath?: string,
|
||||
) => Promise<unknown>;
|
||||
executePlanSlice: (
|
||||
params: {
|
||||
milestoneId: string;
|
||||
sliceId: string;
|
||||
goal: string;
|
||||
tasks: Array<{
|
||||
taskId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
estimate: string;
|
||||
files: string[];
|
||||
verify: string;
|
||||
inputs: string[];
|
||||
expectedOutput: string[];
|
||||
observabilityImpact?: string;
|
||||
}>;
|
||||
successCriteria?: string;
|
||||
proofLevel?: string;
|
||||
integrationClosure?: string;
|
||||
observabilityImpact?: string;
|
||||
},
|
||||
basePath?: string,
|
||||
) => Promise<unknown>;
|
||||
executeSummarySave: (
|
||||
params: {
|
||||
milestone_id: string;
|
||||
|
|
@ -72,6 +127,84 @@ async function withProjectDir<T>(projectDir: string, fn: () => Promise<T>): Prom
|
|||
}
|
||||
|
||||
export function registerWorkflowTools(server: McpToolServer): void {
|
||||
server.tool(
|
||||
"gsd_plan_milestone",
|
||||
"Write milestone planning state to the GSD database and render ROADMAP.md from DB.",
|
||||
{
|
||||
projectDir: z.string().describe("Absolute path to the project directory"),
|
||||
milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
|
||||
title: z.string().describe("Milestone title"),
|
||||
vision: z.string().describe("Milestone vision"),
|
||||
slices: z.array(z.object({
|
||||
sliceId: z.string(),
|
||||
title: z.string(),
|
||||
risk: z.string(),
|
||||
depends: z.array(z.string()),
|
||||
demo: z.string(),
|
||||
goal: z.string(),
|
||||
successCriteria: z.string(),
|
||||
proofLevel: z.string(),
|
||||
integrationClosure: z.string(),
|
||||
observabilityImpact: z.string(),
|
||||
})).describe("Planned slices for the milestone"),
|
||||
status: z.string().optional().describe("Milestone status"),
|
||||
dependsOn: z.array(z.string()).optional().describe("Milestone dependencies"),
|
||||
successCriteria: z.array(z.string()).optional().describe("Top-level success criteria bullets"),
|
||||
keyRisks: z.array(z.object({
|
||||
risk: z.string(),
|
||||
whyItMatters: z.string(),
|
||||
})).optional().describe("Structured risk entries"),
|
||||
proofStrategy: z.array(z.object({
|
||||
riskOrUnknown: z.string(),
|
||||
retireIn: z.string(),
|
||||
whatWillBeProven: z.string(),
|
||||
})).optional().describe("Structured proof strategy entries"),
|
||||
verificationContract: z.string().optional(),
|
||||
verificationIntegration: z.string().optional(),
|
||||
verificationOperational: z.string().optional(),
|
||||
verificationUat: z.string().optional(),
|
||||
definitionOfDone: z.array(z.string()).optional(),
|
||||
requirementCoverage: z.string().optional(),
|
||||
boundaryMapMarkdown: z.string().optional(),
|
||||
},
|
||||
async (args: Record<string, unknown>) => {
|
||||
const { projectDir, ...params } = args as { projectDir: string } & Record<string, unknown>;
|
||||
const { executePlanMilestone } = await getWorkflowToolExecutors();
|
||||
return withProjectDir(projectDir, () => executePlanMilestone(params as any, projectDir));
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"gsd_plan_slice",
|
||||
"Write slice/task planning state to the GSD database and render plan artifacts from DB.",
|
||||
{
|
||||
projectDir: z.string().describe("Absolute path to the project directory"),
|
||||
milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
|
||||
sliceId: z.string().describe("Slice ID (e.g. S01)"),
|
||||
goal: z.string().describe("Slice goal"),
|
||||
tasks: z.array(z.object({
|
||||
taskId: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
estimate: z.string(),
|
||||
files: z.array(z.string()),
|
||||
verify: z.string(),
|
||||
inputs: z.array(z.string()),
|
||||
expectedOutput: z.array(z.string()),
|
||||
observabilityImpact: z.string().optional(),
|
||||
})).describe("Planned tasks for the slice"),
|
||||
successCriteria: z.string().optional(),
|
||||
proofLevel: z.string().optional(),
|
||||
integrationClosure: z.string().optional(),
|
||||
observabilityImpact: z.string().optional(),
|
||||
},
|
||||
async (args: Record<string, unknown>) => {
|
||||
const { projectDir, ...params } = args as { projectDir: string } & Record<string, unknown>;
|
||||
const { executePlanSlice } = await getWorkflowToolExecutors();
|
||||
return withProjectDir(projectDir, () => executePlanSlice(params as any, projectDir));
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"gsd_summary_save",
|
||||
"Save a GSD summary/research/context/assessment artifact to the database and disk.",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { StringEnum } from "@gsd/pi-ai";
|
|||
import { logError } from "../workflow-logger.js";
|
||||
import { getErrorMessage } from "../error-utils.js";
|
||||
import {
|
||||
executePlanMilestone,
|
||||
executePlanSlice,
|
||||
executeSummarySave,
|
||||
executeTaskComplete,
|
||||
} from "../tools/workflow-tool-executors.js";
|
||||
|
|
@ -414,38 +416,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|||
// ─── gsd_plan_milestone (gsd_milestone_plan alias) ─────────────────────
|
||||
|
||||
const planMilestoneExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot plan milestone." }],
|
||||
details: { operation: "plan_milestone", error: "db_unavailable" } as any,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const { handlePlanMilestone } = await import("../tools/plan-milestone.js");
|
||||
const result = await handlePlanMilestone(params, process.cwd());
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error planning milestone: ${result.error}` }],
|
||||
details: { operation: "plan_milestone", error: result.error } as any,
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Planned milestone ${result.milestoneId}` }],
|
||||
details: {
|
||||
operation: "plan_milestone",
|
||||
milestoneId: result.milestoneId,
|
||||
roadmapPath: result.roadmapPath,
|
||||
} as any,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "gsd_plan_milestone", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error planning milestone: ${msg}` }],
|
||||
details: { operation: "plan_milestone", error: msg } as any,
|
||||
};
|
||||
}
|
||||
return executePlanMilestone(params, process.cwd());
|
||||
};
|
||||
|
||||
const planMilestoneTool = {
|
||||
|
|
@ -507,40 +478,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|||
// ─── gsd_plan_slice (gsd_slice_plan alias) ─────────────────────────────
|
||||
|
||||
const planSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot plan slice." }],
|
||||
details: { operation: "plan_slice", error: "db_unavailable" } as any,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const { handlePlanSlice } = await import("../tools/plan-slice.js");
|
||||
const result = await handlePlanSlice(params, process.cwd());
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error planning slice: ${result.error}` }],
|
||||
details: { operation: "plan_slice", error: result.error } as any,
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Planned slice ${result.sliceId} (${result.milestoneId})` }],
|
||||
details: {
|
||||
operation: "plan_slice",
|
||||
milestoneId: result.milestoneId,
|
||||
sliceId: result.sliceId,
|
||||
planPath: result.planPath,
|
||||
taskPlanPaths: result.taskPlanPaths,
|
||||
} as any,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `plan_slice tool failed: ${msg}`, { tool: "gsd_plan_slice", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error planning slice: ${msg}` }],
|
||||
details: { operation: "plan_slice", error: msg } as any,
|
||||
};
|
||||
}
|
||||
return executePlanSlice(params, process.cwd());
|
||||
};
|
||||
|
||||
const planSliceTool = {
|
||||
|
|
|
|||
|
|
@ -107,18 +107,18 @@ test("transport compatibility fails cleanly when MCP server is unavailable", ()
|
|||
test("transport compatibility fails cleanly when unit requires unsupported tools", () => {
|
||||
const error = getWorkflowTransportSupportError(
|
||||
"claude-code",
|
||||
["gsd_plan_slice"],
|
||||
["gsd_complete_task"],
|
||||
{
|
||||
projectRoot: "/tmp/project",
|
||||
env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
|
||||
surface: "auto-mode",
|
||||
unitType: "plan-slice",
|
||||
unitType: "execute-task",
|
||||
authMode: "externalCli",
|
||||
baseUrl: "local://claude-code",
|
||||
},
|
||||
);
|
||||
|
||||
assert.match(error ?? "", /requires gsd_plan_slice/);
|
||||
assert.match(error ?? "", /requires gsd_complete_task/);
|
||||
assert.match(error ?? "", /currently exposes only/);
|
||||
});
|
||||
|
||||
|
|
@ -139,6 +139,23 @@ test("transport compatibility ignores API-backed providers", () => {
|
|||
assert.equal(error, null);
|
||||
});
|
||||
|
||||
test("transport compatibility now allows plan-slice over workflow MCP surface", () => {
|
||||
const error = getWorkflowTransportSupportError(
|
||||
"claude-code",
|
||||
["gsd_plan_slice"],
|
||||
{
|
||||
projectRoot: "/tmp/project",
|
||||
env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
|
||||
surface: "auto-mode",
|
||||
unitType: "plan-slice",
|
||||
authMode: "externalCli",
|
||||
baseUrl: "local://claude-code",
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(error, null);
|
||||
});
|
||||
|
||||
test("guided-flow source enforces workflow compatibility preflight", () => {
|
||||
const src = readSrc("guided-flow.ts");
|
||||
assert.match(src, /getRequiredWorkflowToolsForGuidedUnit/);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
executeSummarySave,
|
||||
executeTaskComplete,
|
||||
executeMilestoneStatus,
|
||||
executePlanMilestone,
|
||||
executePlanSlice,
|
||||
} from "../tools/workflow-tool-executors.ts";
|
||||
|
||||
function makeTmpBase(): string {
|
||||
|
|
@ -143,3 +145,92 @@ test("executeMilestoneStatus returns milestone metadata and slice counts", async
|
|||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("executePlanMilestone writes roadmap state and rendered roadmap path", async () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
openTestDb(base);
|
||||
|
||||
const result = await inProjectDir(base, () => executePlanMilestone({
|
||||
milestoneId: "M001",
|
||||
title: "Workflow MCP planning",
|
||||
vision: "Plan milestone over shared executors.",
|
||||
slices: [
|
||||
{
|
||||
sliceId: "S01",
|
||||
title: "Bridge planning",
|
||||
risk: "medium",
|
||||
depends: [],
|
||||
demo: "Milestone plan persists through MCP.",
|
||||
goal: "Persist roadmap state.",
|
||||
successCriteria: "ROADMAP.md renders from DB.",
|
||||
proofLevel: "integration",
|
||||
integrationClosure: "Prompts and MCP call the same handler.",
|
||||
observabilityImpact: "Executor tests cover output paths.",
|
||||
},
|
||||
],
|
||||
}, base));
|
||||
|
||||
assert.equal(result.details.operation, "plan_milestone");
|
||||
assert.equal(result.details.milestoneId, "M001");
|
||||
const roadmapPath = String(result.details.roadmapPath);
|
||||
assert.ok(existsSync(roadmapPath), "roadmap should be rendered to disk");
|
||||
assert.match(readFileSync(roadmapPath, "utf-8"), /Workflow MCP planning/);
|
||||
} finally {
|
||||
closeDatabase();
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("executePlanSlice writes task planning state and rendered plan artifacts", async () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
openTestDb(base);
|
||||
await inProjectDir(base, () => executePlanMilestone({
|
||||
milestoneId: "M001",
|
||||
title: "Workflow MCP planning",
|
||||
vision: "Plan milestone over shared executors.",
|
||||
slices: [
|
||||
{
|
||||
sliceId: "S01",
|
||||
title: "Bridge planning",
|
||||
risk: "medium",
|
||||
depends: [],
|
||||
demo: "Milestone plan persists through MCP.",
|
||||
goal: "Persist roadmap state.",
|
||||
successCriteria: "ROADMAP.md renders from DB.",
|
||||
proofLevel: "integration",
|
||||
integrationClosure: "Prompts and MCP call the same handler.",
|
||||
observabilityImpact: "Executor tests cover output paths.",
|
||||
},
|
||||
],
|
||||
}, base));
|
||||
|
||||
const result = await inProjectDir(base, () => executePlanSlice({
|
||||
milestoneId: "M001",
|
||||
sliceId: "S01",
|
||||
goal: "Persist slice plan over MCP.",
|
||||
tasks: [
|
||||
{
|
||||
taskId: "T01",
|
||||
title: "Add planning bridge",
|
||||
description: "Implement the shared executor path.",
|
||||
estimate: "15m",
|
||||
files: ["src/resources/extensions/gsd/tools/workflow-tool-executors.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["ROADMAP.md"],
|
||||
expectedOutput: ["S01-PLAN.md", "T01-PLAN.md"],
|
||||
},
|
||||
],
|
||||
}, base));
|
||||
|
||||
assert.equal(result.details.operation, "plan_slice");
|
||||
assert.equal(result.details.sliceId, "S01");
|
||||
const planPath = String(result.details.planPath);
|
||||
assert.ok(existsSync(planPath), "slice plan should be rendered to disk");
|
||||
assert.match(readFileSync(planPath, "utf-8"), /Persist slice plan over MCP/);
|
||||
} finally {
|
||||
closeDatabase();
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import {
|
|||
} from "../gsd-db.js";
|
||||
import { saveArtifactToDb } from "../db-writer.js";
|
||||
import { handleCompleteTask } from "./complete-task.js";
|
||||
import type { PlanMilestoneParams } from "./plan-milestone.js";
|
||||
import { handlePlanMilestone } from "./plan-milestone.js";
|
||||
import type { PlanSliceParams } from "./plan-slice.js";
|
||||
import { handlePlanSlice } from "./plan-slice.js";
|
||||
import { logError, logWarning } from "../workflow-logger.js";
|
||||
|
||||
export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = ["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT"] as const;
|
||||
|
|
@ -118,6 +122,9 @@ export interface TaskCompleteParams {
|
|||
verificationEvidence?: VerificationEvidenceInput[];
|
||||
}
|
||||
|
||||
export type PlanMilestoneExecutorParams = PlanMilestoneParams;
|
||||
export type PlanSliceExecutorParams = PlanSliceParams;
|
||||
|
||||
export async function executeTaskComplete(
|
||||
params: TaskCompleteParams,
|
||||
basePath: string = process.cwd(),
|
||||
|
|
@ -162,6 +169,82 @@ export async function executeTaskComplete(
|
|||
}
|
||||
}
|
||||
|
||||
export async function executePlanMilestone(
|
||||
params: PlanMilestoneExecutorParams,
|
||||
basePath: string = process.cwd(),
|
||||
): Promise<ToolExecutionResult> {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Error: GSD database is not available. Cannot plan milestone." }],
|
||||
details: { operation: "plan_milestone", error: "db_unavailable" },
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await handlePlanMilestone(params, basePath);
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error planning milestone: ${result.error}` }],
|
||||
details: { operation: "plan_milestone", error: result.error },
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Planned milestone ${result.milestoneId}` }],
|
||||
details: {
|
||||
operation: "plan_milestone",
|
||||
milestoneId: result.milestoneId,
|
||||
roadmapPath: result.roadmapPath,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "gsd_plan_milestone", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text", text: `Error planning milestone: ${msg}` }],
|
||||
details: { operation: "plan_milestone", error: msg },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function executePlanSlice(
|
||||
params: PlanSliceExecutorParams,
|
||||
basePath: string = process.cwd(),
|
||||
): Promise<ToolExecutionResult> {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Error: GSD database is not available. Cannot plan slice." }],
|
||||
details: { operation: "plan_slice", error: "db_unavailable" },
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await handlePlanSlice(params, basePath);
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error planning slice: ${result.error}` }],
|
||||
details: { operation: "plan_slice", error: result.error },
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Planned slice ${result.sliceId} (${result.milestoneId})` }],
|
||||
details: {
|
||||
operation: "plan_slice",
|
||||
milestoneId: result.milestoneId,
|
||||
sliceId: result.sliceId,
|
||||
planPath: result.planPath,
|
||||
taskPlanPaths: result.taskPlanPaths,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `plan_slice tool failed: ${msg}`, { tool: "gsd_plan_slice", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text", text: `Error planning slice: ${msg}` }],
|
||||
details: { operation: "plan_slice", error: msg },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface MilestoneStatusParams {
|
||||
milestoneId: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export interface WorkflowCapabilityOptions {
|
|||
|
||||
const MCP_WORKFLOW_TOOL_SURFACE = new Set([
|
||||
"gsd_milestone_status",
|
||||
"gsd_plan_milestone",
|
||||
"gsd_plan_slice",
|
||||
"gsd_summary_save",
|
||||
"gsd_task_complete",
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue