Adds a non-mutating `fn workflow validate` dry-run path across CLI, agent tools, and dashboard API so custom workflow IR can be checked before create/update. - Add `packages/cli/src/commands/workflow.ts` implementing `fn workflow validate <id> | --file <path>` with JSON/text output, wired into `bin.ts`. - Add `fn_workflow_validate` agent tool (`agent-tools.ts`, `index.ts`) reusing the existing parseWorkflowIr/trait/code-node/column-agent validation used by create/update, performing no persistence. - Add `POST /api/workflows/validate` route in `register-workflow-routes.ts` plus dashboard route test coverage. - Extend heartbeat tool-gating/exposure tests and gating classifications to include `fn_workflow_validate` alongside the other workflow tools. - Update CLI/agent extension docs (`docs/cli-reference.md`, `docs/agents.md`, `docs/workflow-steps.md`, fusion skill references) to document the new command/tool. - Add changeset `.changeset/fn-7911-workflow-validate.md` (minor) describing the new capability. Files changed: .changeset/fn-7911-workflow-validate.md | 7 ++ docs/agents.md | 5 +- docs/cli-reference.md | 13 ++ docs/workflow-steps.md | 3 +- packages/cli/skill/fusion/SKILL.md | 2 +- .../cli/skill/fusion/references/extension-tools.md | 10 ++ .../skill/fusion/references/fusion-capabilities.md | 1 + .../src/__tests__/extension-workflow-tools.test.ts | 1 + packages/cli/src/__tests__/extension.test.ts | 1 + .../src/__tests__/workflow-docs-current.test.ts | 1 + packages/cli/src/bin.ts | 22 ++++ packages/cli/src/commands/workflow.ts | 80 ++++++++++++ packages/cli/src/extension.ts | 10 ++ .../dashboard/src/__tests__/chat-manager.test.ts | 1 + .../dashboard/src/__tests__/chat.rooms.test.ts | 1 + .../planning-document-tools-exposure.test.ts | 1 + .../__tests__/workflow-validate-route.test.ts | 101 +++++++++++++++ .../src/routes/register-workflow-routes.ts | 27 +++- .../engine/src/__tests__/agent-action-gate.test.ts | 2 +- .../agent-workflow-tools-exposure.test.ts | 70 ++++++++++- .../src/__tests__/gating-classifications.test.ts | 3 +- .../src/__tests__/heartbeat-executor.test.ts | 37 +++--- .../src/__tests__/heartbeat-session-prompt.test.ts | 5 +- .../src/__tests__/permanent-agent-gating.test.ts | 2 +- packages/engine/src/agent-heartbeat.ts | 5 +- packages/engine/src/agent-tools.ts | 140 ++++++++++++++++++++- packages/engine/src/executor.ts | 6 + packages/engine/src/gating-classifications.ts | 2 + packages/engine/src/index.ts | 4 + 29 files changed, 532 insertions(+), 31 deletions(-) Fusion-Task-Id: FN-7911 Fusion-Task-Lineage: 903d15fe-a7ec-458f-aa34-8f2e895a9603 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
97 lines
4.2 KiB
TypeScript
97 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
const workspaceRoot = resolve(import.meta.dirname, "../../../..");
|
|
const doc = (relativePath: string) => readFileSync(resolve(workspaceRoot, relativePath), "utf8");
|
|
|
|
const workflowSteps = () => doc("docs/workflow-steps.md");
|
|
const workflowEditor = () => doc("docs/workflow-editor.md");
|
|
const dashboardGuide = () => doc("docs/dashboard-guide.md");
|
|
const agents = () => doc("docs/agents.md");
|
|
const cliReference = () => doc("docs/cli-reference.md");
|
|
|
|
/*
|
|
FNXC:WorkflowDocs 2026-06-30-09:30:
|
|
Workflow docs drift easily when implementation tasks add agent tools or board affordances. Keep this guard targeted to the public docs surfaces that define the current workflow behavior inventory.
|
|
*/
|
|
|
|
describe("workflow documentation current behavior", () => {
|
|
it("documents the current built-in workflow catalog and fail-closed runtime contract", () => {
|
|
const content = workflowSteps();
|
|
for (const id of [
|
|
"builtin:coding",
|
|
"builtin:legacy-coding",
|
|
"builtin:quick-fix",
|
|
"builtin:review-heavy",
|
|
"builtin:marketing",
|
|
"builtin:compound-engineering",
|
|
"builtin:stepwise-coding",
|
|
"builtin:design",
|
|
"builtin:pr-workflow",
|
|
"builtin:lead-generation",
|
|
]) {
|
|
expect(content).toContain(id);
|
|
}
|
|
expect(content).toContain("fails closed");
|
|
expect(content).toContain("invalid-ir");
|
|
expect(content).toContain("instead of silently falling back");
|
|
expect(content).toContain("An explicit empty list");
|
|
expect(content).toContain('`json-steps` `"depends": []`');
|
|
expect(content).toContain("An absent annotation/key is different");
|
|
expect(content).toContain("default-on gates still run and appear for in-progress tasks when a persisted selection array is empty");
|
|
expect(content).toContain("edit controls show only the persisted selection");
|
|
});
|
|
|
|
it("keeps workflow tool docs broader than list/select only", () => {
|
|
const combined = [workflowSteps(), workflowEditor(), agents(), cliReference()].join("\n");
|
|
for (const tool of [
|
|
"fn_workflow_list",
|
|
"fn_workflow_get",
|
|
"fn_workflow_validate",
|
|
"fn_workflow_create",
|
|
"fn_workflow_update",
|
|
"fn_workflow_delete",
|
|
"fn_workflow_settings",
|
|
"fn_trait_list",
|
|
"fn_workflow_select",
|
|
"workflow_id",
|
|
]) {
|
|
expect(combined).toContain(tool);
|
|
}
|
|
expect(combined).toContain("explicitly requested");
|
|
expect(combined).toContain("tasks they created");
|
|
});
|
|
|
|
it("documents aggregate board selection, workflow badges, and create forwarding", () => {
|
|
const content = dashboardGuide();
|
|
expect(content).toContain("All workflows");
|
|
expect(content).toContain("workflow name");
|
|
expect(content).toContain("synthetic aggregate");
|
|
expect(content).toContain("active workflow selection");
|
|
expect(content).toContain("Hidden columns stay workflow-scoped in the aggregate");
|
|
expect(content).toContain("it never submits the synthetic aggregate as a workflow id");
|
|
});
|
|
|
|
it("documents current workflow settings ownership and extension task-id semantics", () => {
|
|
const settings = doc("docs/settings-reference.md");
|
|
expect(settings).toContain("Settings → Project Models → Default workflow model lanes");
|
|
expect(settings).toContain("workflow editor Values tab");
|
|
expect(settings).toContain("Built-in prompt overrides");
|
|
expect(settings).toContain("not writable through");
|
|
|
|
const cli = cliReference();
|
|
expect(cli).toContain("must pass `task_id`");
|
|
expect(cli).toContain("treats `null` as deleting a stored override");
|
|
expect(cli).toContain("broader-than-default column permission bindings require explicit policy-escalation confirmation");
|
|
});
|
|
|
|
it("keeps removed workflow-step CRUD surfaces documented only as removed", () => {
|
|
const content = workflowSteps();
|
|
expect(content).toContain("There is no longer a Settings → Workflow Steps manager or step CRUD form");
|
|
expect(content).toContain("The legacy workflow-step CRUD routes were removed");
|
|
expect(content).not.toContain("create a workflow step in Settings");
|
|
expect(content).not.toContain("Use `POST /api/workflow-steps`");
|
|
});
|
|
});
|