feat(KB-218): add workflow steps for post-implementation review

- Add core data model for workflow step definitions with AI-assisted prompt refinement
- Create API routes for CRUD operations and prompt refinement via /api/workflow-steps
- Add WorkflowStepManager dashboard UI for defining and managing workflow steps
- Integrate workflow step selection into NewTaskModal for per-task enablement
- Execute workflow steps sequentially in executor after task_done() with readonly tools
- Run workflow step agents before moving tasks to in-review, failing on step errors
- Add comprehensive tests for store, API routes, components, and executor integration
This commit is contained in:
gsxdsm
2026-03-31 03:55:01 -07:00
parent 609c06f96e
commit 23964bf0c0
18 changed files with 2141 additions and 8 deletions

View File

@@ -16,6 +16,7 @@ vi.mock("../../api", () => ({
autoSelectModelPreset: false,
defaultPresetBySize: {},
}),
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
}));
function makeTask(id: string): Task {
@@ -457,4 +458,83 @@ describe("NewTaskModal", () => {
// The overflow-y: auto is applied via CSS in styles.css
expect(modal?.contains(modalBody)).toBe(true);
});
it("shows workflow step checkboxes when steps are available", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
{ id: "WS-002", name: "QA Check", description: "Run tests", prompt: "Run tests", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
]);
renderNewTaskModal();
await waitFor(() => {
expect(screen.getByTestId("workflow-steps-section")).toBeInTheDocument();
expect(screen.getByText("Docs Review")).toBeInTheDocument();
expect(screen.getByText("QA Check")).toBeInTheDocument();
});
});
it("does not show workflow steps section when no steps are available", async () => {
renderNewTaskModal();
await waitFor(() => {
expect(screen.queryByTestId("workflow-steps-section")).toBeNull();
});
});
it("toggles workflow step selection", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
]);
renderNewTaskModal();
await waitFor(() => {
expect(screen.getByTestId("workflow-step-checkbox-WS-001")).toBeInTheDocument();
});
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector("input[type='checkbox']") as HTMLInputElement;
expect(checkbox.checked).toBe(false);
fireEvent.click(checkbox);
expect(checkbox.checked).toBe(true);
fireEvent.click(checkbox);
expect(checkbox.checked).toBe(false);
});
it("passes selected workflow steps to onCreateTask", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
]);
const { props } = renderNewTaskModal();
await waitFor(() => {
expect(screen.getByTestId("workflow-step-checkbox-WS-001")).toBeInTheDocument();
});
// Check the workflow step
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector("input[type='checkbox']") as HTMLInputElement;
fireEvent.click(checkbox);
// Fill in description
const textarea = screen.getByPlaceholderText("What needs to be done?");
fireEvent.change(textarea, { target: { value: "Test task" } });
// Submit
const submitBtn = screen.getByText("Create Task");
fireEvent.click(submitBtn);
await waitFor(() => {
expect(props.onCreateTask).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: ["WS-001"],
})
);
});
});
});