feat: expose slice replanning over workflow MCP
This commit is contained in:
parent
70458467ff
commit
d116cff601
7 changed files with 403 additions and 41 deletions
|
|
@ -48,16 +48,18 @@ function makeMockServer() {
|
|||
}
|
||||
|
||||
describe("workflow MCP tools", () => {
|
||||
it("registers the fifteen workflow tools", () => {
|
||||
it("registers the seventeen workflow tools", () => {
|
||||
const server = makeMockServer();
|
||||
registerWorkflowTools(server as any);
|
||||
|
||||
assert.equal(server.tools.length, 15);
|
||||
assert.equal(server.tools.length, 17);
|
||||
assert.deepEqual(
|
||||
server.tools.map((t) => t.name),
|
||||
[
|
||||
"gsd_plan_milestone",
|
||||
"gsd_plan_slice",
|
||||
"gsd_replan_slice",
|
||||
"gsd_slice_replan",
|
||||
"gsd_slice_complete",
|
||||
"gsd_complete_slice",
|
||||
"gsd_complete_milestone",
|
||||
|
|
@ -245,6 +247,152 @@ describe("workflow MCP tools", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("gsd_replan_slice and gsd_slice_replan 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");
|
||||
const taskTool = server.tools.find((t) => t.name === "gsd_task_complete");
|
||||
const canonicalTool = server.tools.find((t) => t.name === "gsd_replan_slice");
|
||||
const aliasTool = server.tools.find((t) => t.name === "gsd_slice_replan");
|
||||
assert.ok(milestoneTool, "milestone planning tool should be registered");
|
||||
assert.ok(sliceTool, "slice planning tool should be registered");
|
||||
assert.ok(taskTool, "task completion tool should be registered");
|
||||
assert.ok(canonicalTool, "slice replanning tool should be registered");
|
||||
assert.ok(aliasTool, "slice replanning alias should be registered");
|
||||
|
||||
await milestoneTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M099",
|
||||
title: "Slice replanning",
|
||||
vision: "Drive replan parity over MCP.",
|
||||
slices: [
|
||||
{
|
||||
sliceId: "S09",
|
||||
title: "Replan slice",
|
||||
risk: "medium",
|
||||
depends: [],
|
||||
demo: "Slice replans after a blocker task completes.",
|
||||
goal: "Prepare replan state.",
|
||||
successCriteria: "Plan and replan artifacts update over MCP.",
|
||||
proofLevel: "integration",
|
||||
integrationClosure: "Replan uses the shared executor path.",
|
||||
observabilityImpact: "Tests cover replan artifacts.",
|
||||
},
|
||||
],
|
||||
});
|
||||
await sliceTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M099",
|
||||
sliceId: "S09",
|
||||
goal: "Plan a slice that will be replanned.",
|
||||
tasks: [
|
||||
{
|
||||
taskId: "T09",
|
||||
title: "Blocker task",
|
||||
description: "Finish the blocker-discovery task.",
|
||||
estimate: "5m",
|
||||
files: ["src/blocker.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["M099-ROADMAP.md"],
|
||||
expectedOutput: ["T09-SUMMARY.md"],
|
||||
},
|
||||
{
|
||||
taskId: "T10",
|
||||
title: "Pending task",
|
||||
description: "Original follow-up task.",
|
||||
estimate: "10m",
|
||||
files: ["src/pending.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S09-PLAN.md"],
|
||||
expectedOutput: ["Updated plan"],
|
||||
},
|
||||
],
|
||||
});
|
||||
await taskTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M099",
|
||||
sliceId: "S09",
|
||||
taskId: "T09",
|
||||
oneLiner: "Completed blocker task",
|
||||
narrative: "Prepared the slice for replanning.",
|
||||
verification: "node --test",
|
||||
});
|
||||
|
||||
const canonicalResult = await canonicalTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M099",
|
||||
sliceId: "S09",
|
||||
blockerTaskId: "T09",
|
||||
blockerDescription: "Original approach is no longer viable.",
|
||||
whatChanged: "Updated the remaining task and added remediation work.",
|
||||
updatedTasks: [
|
||||
{
|
||||
taskId: "T10",
|
||||
title: "Pending task (updated)",
|
||||
description: "Updated follow-up task after replanning.",
|
||||
estimate: "15m",
|
||||
files: ["src/pending.ts", "src/replanned.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S09-PLAN.md"],
|
||||
expectedOutput: ["Updated plan"],
|
||||
},
|
||||
{
|
||||
taskId: "T11",
|
||||
title: "Remediation task",
|
||||
description: "New task introduced by the replan.",
|
||||
estimate: "20m",
|
||||
files: ["src/remediation.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S09-REPLAN.md"],
|
||||
expectedOutput: ["Remediation patch"],
|
||||
},
|
||||
],
|
||||
removedTaskIds: [],
|
||||
});
|
||||
assert.match((canonicalResult as any).content[0].text as string, /Replanned slice S09/);
|
||||
|
||||
const aliasResult = await aliasTool!.handler({
|
||||
projectDir: base,
|
||||
milestoneId: "M099",
|
||||
sliceId: "S09",
|
||||
blockerTaskId: "T09",
|
||||
blockerDescription: "Alias path confirms the same replan flow.",
|
||||
whatChanged: "Removed the remediation task after the alias check.",
|
||||
updatedTasks: [
|
||||
{
|
||||
taskId: "T10",
|
||||
title: "Pending task (updated again)",
|
||||
description: "Alias adjusted the remaining pending task.",
|
||||
estimate: "12m",
|
||||
files: ["src/pending.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S09-PLAN.md"],
|
||||
expectedOutput: ["Updated plan"],
|
||||
},
|
||||
],
|
||||
removedTaskIds: ["T11"],
|
||||
});
|
||||
assert.match((aliasResult as any).content[0].text as string, /Replanned slice S09/);
|
||||
assert.ok(
|
||||
existsSync(join(base, ".gsd", "milestones", "M099", "slices", "S09", "S09-REPLAN.md")),
|
||||
"replan artifact should exist on disk",
|
||||
);
|
||||
assert.ok(
|
||||
existsSync(join(base, ".gsd", "milestones", "M099", "slices", "S09", "S09-PLAN.md")),
|
||||
"updated plan should exist on disk",
|
||||
);
|
||||
const removedTask = _getAdapter()!.prepare(
|
||||
"SELECT id FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
|
||||
).get("M099", "S09", "T11");
|
||||
assert.equal(removedTask, undefined, "alias should remove the replanned task");
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
it("gsd_slice_complete and gsd_complete_slice work end-to-end", async () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,28 @@ type WorkflowToolExecutors = {
|
|||
},
|
||||
basePath?: string,
|
||||
) => Promise<unknown>;
|
||||
executeReplanSlice: (
|
||||
params: {
|
||||
milestoneId: string;
|
||||
sliceId: string;
|
||||
blockerTaskId: string;
|
||||
blockerDescription: string;
|
||||
whatChanged: string;
|
||||
updatedTasks: Array<{
|
||||
taskId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
estimate: string;
|
||||
files: string[];
|
||||
verify: string;
|
||||
inputs: string[];
|
||||
expectedOutput: string[];
|
||||
fullPlanMd?: string;
|
||||
}>;
|
||||
removedTaskIds: string[];
|
||||
},
|
||||
basePath?: string,
|
||||
) => Promise<unknown>;
|
||||
executeSliceComplete: (
|
||||
params: {
|
||||
sliceId: string;
|
||||
|
|
@ -287,6 +309,14 @@ async function handleSliceComplete(
|
|||
return withProjectDir(projectDir, () => executeSliceComplete(args as any, projectDir));
|
||||
}
|
||||
|
||||
async function handleReplanSlice(
|
||||
projectDir: string,
|
||||
args: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const { executeReplanSlice } = await getWorkflowToolExecutors();
|
||||
return withProjectDir(projectDir, () => executeReplanSlice(args as any, projectDir));
|
||||
}
|
||||
|
||||
async function handleCompleteMilestone(
|
||||
projectDir: string,
|
||||
args: Record<string, unknown>,
|
||||
|
|
@ -382,6 +412,27 @@ const saveGateResultSchema = {
|
|||
findings: z.string().optional().describe("Detailed markdown findings"),
|
||||
};
|
||||
|
||||
const replanSliceSchema = {
|
||||
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)"),
|
||||
blockerTaskId: z.string().describe("Task ID that discovered the blocker"),
|
||||
blockerDescription: z.string().describe("Description of the blocker"),
|
||||
whatChanged: z.string().describe("Summary of what changed in the plan"),
|
||||
updatedTasks: 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()),
|
||||
fullPlanMd: z.string().optional(),
|
||||
})).describe("Tasks to upsert into the replanned slice"),
|
||||
removedTaskIds: z.array(z.string()).describe("Task IDs to remove from the slice"),
|
||||
};
|
||||
|
||||
export function registerWorkflowTools(server: McpToolServer): void {
|
||||
server.tool(
|
||||
"gsd_plan_milestone",
|
||||
|
|
@ -461,6 +512,26 @@ export function registerWorkflowTools(server: McpToolServer): void {
|
|||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"gsd_replan_slice",
|
||||
"Replan a slice after a blocker is discovered, preserving completed tasks and re-rendering PLAN.md + REPLAN.md.",
|
||||
replanSliceSchema,
|
||||
async (args: Record<string, unknown>) => {
|
||||
const { projectDir, ...replanArgs } = args as { projectDir: string } & Record<string, unknown>;
|
||||
return handleReplanSlice(projectDir, replanArgs);
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"gsd_slice_replan",
|
||||
"Alias for gsd_replan_slice. Replan a slice after a blocker is discovered.",
|
||||
replanSliceSchema,
|
||||
async (args: Record<string, unknown>) => {
|
||||
const { projectDir, ...replanArgs } = args as { projectDir: string } & Record<string, unknown>;
|
||||
return handleReplanSlice(projectDir, replanArgs);
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"gsd_slice_complete",
|
||||
"Record a completed slice to the GSD database, render SUMMARY.md + UAT.md, and update roadmap projection.",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
executeCompleteMilestone,
|
||||
executePlanMilestone,
|
||||
executePlanSlice,
|
||||
executeReplanSlice,
|
||||
executeReassessRoadmap,
|
||||
executeSaveGateResult,
|
||||
executeSliceComplete,
|
||||
|
|
@ -916,40 +917,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|||
// ─── gsd_replan_slice (gsd_slice_replan alias) ─────────────────────────
|
||||
|
||||
const replanSliceExecute = 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 replan slice." }],
|
||||
details: { operation: "replan_slice", error: "db_unavailable" } as any,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const { handleReplanSlice } = await import("../tools/replan-slice.js");
|
||||
const result = await handleReplanSlice(params, process.cwd());
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error replanning slice: ${result.error}` }],
|
||||
details: { operation: "replan_slice", error: result.error } as any,
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Replanned slice ${result.sliceId} (${result.milestoneId})` }],
|
||||
details: {
|
||||
operation: "replan_slice",
|
||||
milestoneId: result.milestoneId,
|
||||
sliceId: result.sliceId,
|
||||
replanPath: result.replanPath,
|
||||
planPath: result.planPath,
|
||||
} as any,
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error replanning slice: ${msg}` }],
|
||||
details: { operation: "replan_slice", error: msg } as any,
|
||||
};
|
||||
}
|
||||
return executeReplanSlice(params, process.cwd());
|
||||
};
|
||||
|
||||
const replanSliceTool = {
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ test("transport compatibility now allows complete-milestone over workflow MCP su
|
|||
assert.equal(error, null);
|
||||
});
|
||||
|
||||
test("transport compatibility still blocks units whose MCP tools are not exposed", () => {
|
||||
test("transport compatibility now allows replan-slice over workflow MCP surface", () => {
|
||||
const error = getWorkflowTransportSupportError(
|
||||
"claude-code",
|
||||
["gsd_replan_slice"],
|
||||
|
|
@ -254,7 +254,24 @@ test("transport compatibility still blocks units whose MCP tools are not exposed
|
|||
},
|
||||
);
|
||||
|
||||
assert.match(error ?? "", /requires gsd_replan_slice/);
|
||||
assert.equal(error, null);
|
||||
});
|
||||
|
||||
test("transport compatibility still blocks units whose MCP tools are not exposed", () => {
|
||||
const error = getWorkflowTransportSupportError(
|
||||
"claude-code",
|
||||
["gsd_skip_slice"],
|
||||
{
|
||||
projectRoot: "/tmp/project",
|
||||
env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
|
||||
surface: "auto-mode",
|
||||
unitType: "skip-slice",
|
||||
authMode: "externalCli",
|
||||
baseUrl: "local://claude-code",
|
||||
},
|
||||
);
|
||||
|
||||
assert.match(error ?? "", /requires gsd_skip_slice/);
|
||||
assert.match(error ?? "", /currently exposes only/);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,15 +13,16 @@ import {
|
|||
} from "../gsd-db.ts";
|
||||
import {
|
||||
executeCompleteMilestone,
|
||||
executeValidateMilestone,
|
||||
executePlanMilestone,
|
||||
executePlanSlice,
|
||||
executeReplanSlice,
|
||||
executeReassessRoadmap,
|
||||
executeSaveGateResult,
|
||||
executeSummarySave,
|
||||
executeTaskComplete,
|
||||
executeMilestoneStatus,
|
||||
executePlanMilestone,
|
||||
executePlanSlice,
|
||||
executeSliceComplete,
|
||||
executeValidateMilestone,
|
||||
} from "../tools/workflow-tool-executors.ts";
|
||||
|
||||
function makeTmpBase(): string {
|
||||
|
|
@ -505,3 +506,116 @@ test("executeSaveGateResult validates inputs and persists verdicts", async () =>
|
|||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("executeReplanSlice rewrites pending tasks and renders replan artifacts", async () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
openTestDb(base);
|
||||
await inProjectDir(base, () => executePlanMilestone({
|
||||
milestoneId: "M006",
|
||||
title: "Milestone Six",
|
||||
vision: "Exercise slice replanning.",
|
||||
slices: [
|
||||
{
|
||||
sliceId: "S06",
|
||||
title: "Replan slice",
|
||||
risk: "medium",
|
||||
depends: [],
|
||||
demo: "Slice can be replanned after a blocker task completes.",
|
||||
goal: "Prepare replan state.",
|
||||
successCriteria: "PLAN and REPLAN artifacts update.",
|
||||
proofLevel: "integration",
|
||||
integrationClosure: "Replan shares the workflow executor path.",
|
||||
observabilityImpact: "Executor test covers replan output files.",
|
||||
},
|
||||
],
|
||||
}, base));
|
||||
await inProjectDir(base, () => executePlanSlice({
|
||||
milestoneId: "M006",
|
||||
sliceId: "S06",
|
||||
goal: "Plan a slice that will be replanned.",
|
||||
tasks: [
|
||||
{
|
||||
taskId: "T06",
|
||||
title: "Blocker task",
|
||||
description: "Finish the blocker-discovery task.",
|
||||
estimate: "5m",
|
||||
files: ["src/blocker.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["M006-ROADMAP.md"],
|
||||
expectedOutput: ["T06-SUMMARY.md"],
|
||||
},
|
||||
{
|
||||
taskId: "T07",
|
||||
title: "Pending task",
|
||||
description: "Original follow-up task.",
|
||||
estimate: "10m",
|
||||
files: ["src/pending.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S06-PLAN.md"],
|
||||
expectedOutput: ["Updated plan"],
|
||||
},
|
||||
],
|
||||
}, base));
|
||||
await inProjectDir(base, () => executeTaskComplete({
|
||||
milestoneId: "M006",
|
||||
sliceId: "S06",
|
||||
taskId: "T06",
|
||||
oneLiner: "Completed blocker task",
|
||||
narrative: "The blocker was identified and documented.",
|
||||
verification: "node --test",
|
||||
}, base));
|
||||
|
||||
const result = await inProjectDir(base, () => executeReplanSlice({
|
||||
milestoneId: "M006",
|
||||
sliceId: "S06",
|
||||
blockerTaskId: "T06",
|
||||
blockerDescription: "Original approach no longer works.",
|
||||
whatChanged: "Adjusted the remaining tasks and added a remediation task.",
|
||||
updatedTasks: [
|
||||
{
|
||||
taskId: "T07",
|
||||
title: "Pending task (updated)",
|
||||
description: "Updated follow-up task after replanning.",
|
||||
estimate: "15m",
|
||||
files: ["src/pending.ts", "src/replanned.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S06-PLAN.md"],
|
||||
expectedOutput: ["Updated plan"],
|
||||
},
|
||||
{
|
||||
taskId: "T08",
|
||||
title: "Remediation task",
|
||||
description: "New task introduced by the replan.",
|
||||
estimate: "20m",
|
||||
files: ["src/remediation.ts"],
|
||||
verify: "node --test",
|
||||
inputs: ["S06-REPLAN.md"],
|
||||
expectedOutput: ["Remediation patch"],
|
||||
},
|
||||
],
|
||||
removedTaskIds: [],
|
||||
}, base));
|
||||
|
||||
assert.equal(result.details.operation, "replan_slice");
|
||||
const planPath = String(result.details.planPath);
|
||||
const replanPath = String(result.details.replanPath);
|
||||
assert.ok(existsSync(planPath), "replanned plan should exist on disk");
|
||||
assert.ok(existsSync(replanPath), "replan artifact should exist on disk");
|
||||
assert.match(readFileSync(planPath, "utf-8"), /T08/);
|
||||
assert.match(readFileSync(replanPath, "utf-8"), /Adjusted the remaining tasks/);
|
||||
|
||||
const db = _getAdapter();
|
||||
const updatedTask = db!.prepare(
|
||||
"SELECT title FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
|
||||
).get("M006", "S06", "T07") as Record<string, unknown> | undefined;
|
||||
const insertedTask = db!.prepare(
|
||||
"SELECT title FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
|
||||
).get("M006", "S06", "T08") as Record<string, unknown> | undefined;
|
||||
assert.equal(updatedTask?.title, "Pending task (updated)");
|
||||
assert.equal(insertedTask?.title, "Remediation task");
|
||||
} finally {
|
||||
closeDatabase();
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ 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 type { ReplanSliceParams } from "./replan-slice.js";
|
||||
import { handleReplanSlice } from "./replan-slice.js";
|
||||
import type { ReassessRoadmapParams } from "./reassess-roadmap.js";
|
||||
import { handleReassessRoadmap } from "./reassess-roadmap.js";
|
||||
import type { ValidateMilestoneParams } from "./validate-milestone.js";
|
||||
|
|
@ -137,6 +139,7 @@ export type CompleteMilestoneExecutorParams = Partial<CompleteMilestoneParams> &
|
|||
export type SliceCompleteExecutorParams = CompleteSliceParams;
|
||||
export type PlanMilestoneExecutorParams = PlanMilestoneParams;
|
||||
export type PlanSliceExecutorParams = PlanSliceParams;
|
||||
export type ReplanSliceExecutorParams = ReplanSliceParams;
|
||||
export type ValidateMilestoneExecutorParams = ValidateMilestoneParams;
|
||||
export type ReassessRoadmapExecutorParams = ReassessRoadmapParams;
|
||||
|
||||
|
|
@ -518,6 +521,45 @@ export async function executePlanSlice(
|
|||
}
|
||||
}
|
||||
|
||||
export async function executeReplanSlice(
|
||||
params: ReplanSliceExecutorParams,
|
||||
basePath: string = process.cwd(),
|
||||
): Promise<ToolExecutionResult> {
|
||||
const dbAvailable = await ensureDbOpen();
|
||||
if (!dbAvailable) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Error: GSD database is not available. Cannot replan slice." }],
|
||||
details: { operation: "replan_slice", error: "db_unavailable" },
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await handleReplanSlice(params, basePath);
|
||||
if ("error" in result) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error replanning slice: ${result.error}` }],
|
||||
details: { operation: "replan_slice", error: result.error },
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Replanned slice ${result.sliceId} (${result.milestoneId})` }],
|
||||
details: {
|
||||
operation: "replan_slice",
|
||||
milestoneId: result.milestoneId,
|
||||
sliceId: result.sliceId,
|
||||
replanPath: result.replanPath,
|
||||
planPath: result.planPath,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
|
||||
return {
|
||||
content: [{ type: "text", text: `Error replanning slice: ${msg}` }],
|
||||
details: { operation: "replan_slice", error: msg },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface MilestoneStatusParams {
|
||||
milestoneId: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ const MCP_WORKFLOW_TOOL_SURFACE = new Set([
|
|||
"gsd_milestone_validate",
|
||||
"gsd_plan_milestone",
|
||||
"gsd_plan_slice",
|
||||
"gsd_replan_slice",
|
||||
"gsd_reassess_roadmap",
|
||||
"gsd_roadmap_reassess",
|
||||
"gsd_save_gate_result",
|
||||
"gsd_slice_replan",
|
||||
"gsd_slice_complete",
|
||||
"gsd_summary_save",
|
||||
"gsd_task_complete",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue