feat(FN-4468): add workflow step gateMode advisory gating
Adds an "advisory" `gateMode` option for workflow steps: steps in advisory mode log findings but no longer block merge completion, while steps that must block can still be configured as blocking. The change covers the workflow step types and schema, executor gating logic, dashboard UI in WorkflowSte Fusion-Task-Id: FN-4468
This commit is contained in:
@@ -290,7 +290,7 @@ describe("Database", () => {
|
||||
});
|
||||
|
||||
it("seeds schema version", () => {
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
});
|
||||
|
||||
it("includes tokenUsageCacheWriteTokens on freshly initialized tasks table", () => {
|
||||
@@ -318,7 +318,7 @@ describe("Database", () => {
|
||||
|
||||
it("is idempotent - calling init() twice does not fail", () => {
|
||||
expect(() => db.init()).not.toThrow();
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
});
|
||||
it("does not overwrite existing config on re-init", () => {
|
||||
// Update the config
|
||||
@@ -1383,7 +1383,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 29 (includes v1→v2 through v26→v29)
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -1408,11 +1408,11 @@ describe("schema migrations", () => {
|
||||
const db = new Database(fusionDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
// Re-init should not fail
|
||||
db.init();
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -1447,7 +1447,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(cols.map((col) => col.name)).toContain("priority");
|
||||
@@ -1488,7 +1488,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
const colNames = cols.map((col) => col.name);
|
||||
@@ -1560,7 +1560,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
const colNames = cols.map((col) => col.name);
|
||||
@@ -1800,7 +1800,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(chat_messages)").all() as Array<{ name: string }>;
|
||||
expect(cols.map((col) => col.name)).toContain("attachments");
|
||||
@@ -1874,7 +1874,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'agentRatings'").all() as Array<{ name: string }>;
|
||||
expect(tables).toEqual([{ name: "agentRatings" }]);
|
||||
@@ -1898,7 +1898,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'mission_events'").all() as Array<{ name: string }>;
|
||||
expect(tables).toEqual([{ name: "mission_events" }]);
|
||||
@@ -2002,7 +2002,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 29
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -2221,7 +2221,7 @@ describe("schema migrations", () => {
|
||||
|
||||
localDb.init();
|
||||
|
||||
expect(localDb.getSchemaVersion()).toBe(75);
|
||||
expect(localDb.getSchemaVersion()).toBe(76);
|
||||
const columns = localDb.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(columns.map((column) => column.name)).toContain("tokenUsageCacheWriteTokens");
|
||||
|
||||
@@ -2532,7 +2532,7 @@ describe("createDatabase factory", () => {
|
||||
const db = createDatabase(fusionDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
expect(db.getLastModified()).toBeGreaterThan(0);
|
||||
|
||||
db.close();
|
||||
@@ -2668,7 +2668,7 @@ describe("migration v67 drops orphan project auth tables", () => {
|
||||
|
||||
migrated = new Database(fusion);
|
||||
migrated.init();
|
||||
expect(migrated.getSchemaVersion()).toBe(75);
|
||||
expect(migrated.getSchemaVersion()).toBe(76);
|
||||
const tables = migrated
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
|
||||
.all() as Array<{ name: string }>;
|
||||
@@ -2695,7 +2695,7 @@ describe("migration v67 drops orphan project auth tables", () => {
|
||||
|
||||
try {
|
||||
fresh.init();
|
||||
expect(fresh.getSchemaVersion()).toBe(75);
|
||||
expect(fresh.getSchemaVersion()).toBe(76);
|
||||
const tables = fresh
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'project_auth_%'")
|
||||
.all() as Array<{ name: string }>;
|
||||
|
||||
@@ -886,7 +886,7 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
// Step 1: Create a fresh database at v33 (runs all migrations up to 33)
|
||||
const db1 = createDatabase(legacyDir);
|
||||
db1.init();
|
||||
expect(db1.getSchemaVersion()).toBe(75);
|
||||
expect(db1.getSchemaVersion()).toBe(76);
|
||||
db1.close();
|
||||
|
||||
// Step 2: Manually downgrade to version 32 and drop insight tables
|
||||
@@ -921,7 +921,7 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
expect(tableNamesBefore).not.toContain("project_insight_runs");
|
||||
// Now run init — this triggers the v32→v33 migration
|
||||
db3.init();
|
||||
expect(db3.getSchemaVersion()).toBe(75);
|
||||
expect(db3.getSchemaVersion()).toBe(76);
|
||||
|
||||
// Step 4: Verify insight tables exist after migration
|
||||
const tablesAfter = db3.prepare(
|
||||
@@ -952,12 +952,12 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
try {
|
||||
const db1 = createDatabase(testDir);
|
||||
db1.init();
|
||||
expect(db1.getSchemaVersion()).toBe(75);
|
||||
expect(db1.getSchemaVersion()).toBe(76);
|
||||
db1.close();
|
||||
|
||||
const db2 = createDatabase(testDir);
|
||||
expect(() => db2.init()).not.toThrow();
|
||||
expect(db2.getSchemaVersion()).toBe(75);
|
||||
expect(db2.getSchemaVersion()).toBe(76);
|
||||
db2.close();
|
||||
} finally {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
@@ -971,7 +971,7 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
// Step 1: Create a fresh DB and run migrations
|
||||
const db1 = createDatabase(compatDir);
|
||||
db1.init();
|
||||
expect(db1.getSchemaVersion()).toBe(75);
|
||||
expect(db1.getSchemaVersion()).toBe(76);
|
||||
|
||||
// Step 2: Strip lifecycle and cancelledAt columns by recreating the
|
||||
// table without them. This simulates a DB that was created before the
|
||||
|
||||
@@ -2639,7 +2639,7 @@ describe("MissionStore", () => {
|
||||
|
||||
describe("Loop State & Validator Run Schema (v31)", () => {
|
||||
it("schema version is 40 after migration", () => {
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
});
|
||||
|
||||
it("mission_features table has loop state columns", () => {
|
||||
|
||||
@@ -584,7 +584,7 @@ describe("Run Audit", () => {
|
||||
});
|
||||
|
||||
it("schema version is bumped to 40", () => {
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("TaskStore task documents", () => {
|
||||
|
||||
expect(tableNames.has("task_documents")).toBe(true);
|
||||
expect(tableNames.has("task_document_revisions")).toBe(true);
|
||||
expect(db.getSchemaVersion()).toBe(75);
|
||||
expect(db.getSchemaVersion()).toBe(76);
|
||||
|
||||
const index = db
|
||||
.prepare(
|
||||
|
||||
@@ -160,6 +160,7 @@ async function migrateConfig(fusionDir: string, db: Database): Promise<void> {
|
||||
mode,
|
||||
phase,
|
||||
prompt,
|
||||
gateMode,
|
||||
toolMode,
|
||||
scriptName,
|
||||
enabled,
|
||||
@@ -168,7 +169,7 @@ async function migrateConfig(fusionDir: string, db: Database): Promise<void> {
|
||||
modelId,
|
||||
createdAt,
|
||||
updatedAt
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
for (const step of workflowSteps) {
|
||||
@@ -189,6 +190,7 @@ async function migrateConfig(fusionDir: string, db: Database): Promise<void> {
|
||||
mode,
|
||||
phase,
|
||||
mode === "prompt" ? step.prompt || "" : "",
|
||||
step.gateMode ?? (mode === "script" ? "gate" : "advisory"),
|
||||
mode === "prompt" ? step.toolMode ?? null : null,
|
||||
mode === "script" ? step.scriptName ?? null : null,
|
||||
step.enabled === false ? 0 : 1,
|
||||
|
||||
@@ -119,7 +119,7 @@ export function probeFts5(db: DatabaseSync): boolean {
|
||||
|
||||
// ── Schema Definition ────────────────────────────────────────────────
|
||||
|
||||
const SCHEMA_VERSION = 75;
|
||||
const SCHEMA_VERSION = 76;
|
||||
|
||||
function normalizeTaskComments(
|
||||
steeringComments: SteeringComment[] | undefined,
|
||||
@@ -324,6 +324,7 @@ CREATE TABLE IF NOT EXISTS workflow_steps (
|
||||
mode TEXT NOT NULL DEFAULT 'prompt',
|
||||
phase TEXT NOT NULL DEFAULT 'pre-merge',
|
||||
prompt TEXT NOT NULL DEFAULT '',
|
||||
gateMode TEXT NOT NULL DEFAULT 'advisory',
|
||||
toolMode TEXT,
|
||||
scriptName TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
@@ -1769,6 +1770,7 @@ export class Database {
|
||||
mode TEXT NOT NULL DEFAULT 'prompt',
|
||||
phase TEXT NOT NULL DEFAULT 'pre-merge',
|
||||
prompt TEXT NOT NULL DEFAULT '',
|
||||
gateMode TEXT NOT NULL DEFAULT 'advisory',
|
||||
toolMode TEXT,
|
||||
scriptName TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
@@ -1798,6 +1800,7 @@ export class Database {
|
||||
mode,
|
||||
phase,
|
||||
prompt,
|
||||
gateMode,
|
||||
toolMode,
|
||||
scriptName,
|
||||
enabled,
|
||||
@@ -1806,7 +1809,7 @@ export class Database {
|
||||
modelId,
|
||||
createdAt,
|
||||
updatedAt
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
for (const step of workflowSteps) {
|
||||
@@ -1837,6 +1840,9 @@ export class Database {
|
||||
mode,
|
||||
phase,
|
||||
typeof step.prompt === "string" ? step.prompt : "",
|
||||
step.gateMode === "gate" || step.gateMode === "advisory"
|
||||
? step.gateMode
|
||||
: (mode === "script" ? "gate" : "advisory"),
|
||||
step.toolMode === "coding" || step.toolMode === "readonly" ? step.toolMode : null,
|
||||
typeof step.scriptName === "string" ? step.scriptName : null,
|
||||
step.enabled === false ? 0 : 1,
|
||||
@@ -3244,6 +3250,13 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
if (version < 76) {
|
||||
this.applyMigration(76, () => {
|
||||
this.addColumnIfMissing("workflow_steps", "gateMode", "TEXT NOT NULL DEFAULT 'advisory'");
|
||||
this.db.exec("UPDATE workflow_steps SET gateMode = CASE WHEN mode = 'script' THEN 'gate' ELSE 'advisory' END WHERE gateMode IS NULL OR gateMode = ''");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ function countWorkflow(results: WorkflowStepResult[] | undefined): Deterministic
|
||||
let pending = 0;
|
||||
for (const result of list) {
|
||||
if (result.status === "passed") passed += 1;
|
||||
else if (result.status === "failed") failed += 1;
|
||||
else if (result.status === "failed" || result.status === "advisory_failure") failed += 1;
|
||||
else if (result.status === "pending") pending += 1;
|
||||
}
|
||||
return { total: list.length, passed, failed, pending };
|
||||
|
||||
@@ -2579,6 +2579,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
mode: "prompt",
|
||||
phase: "pre-merge",
|
||||
prompt: template.prompt,
|
||||
gateMode: "advisory",
|
||||
toolMode: template.toolMode || "readonly",
|
||||
enabled: true,
|
||||
createdAt: now,
|
||||
@@ -2594,6 +2595,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
mode: string;
|
||||
phase: string | null;
|
||||
prompt: string;
|
||||
gateMode: string | null;
|
||||
toolMode: string | null;
|
||||
scriptName: string | null;
|
||||
enabled: number;
|
||||
@@ -2611,6 +2613,9 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
mode: row.mode === "script" ? "script" : "prompt",
|
||||
phase: row.phase === "post-merge" ? "post-merge" : "pre-merge",
|
||||
prompt: row.prompt || "",
|
||||
gateMode: row.gateMode === "gate" || row.gateMode === "advisory"
|
||||
? row.gateMode
|
||||
: (row.mode === "script" ? "gate" : "advisory"),
|
||||
toolMode: row.toolMode === "coding" || row.toolMode === "readonly" ? row.toolMode : undefined,
|
||||
scriptName: row.scriptName ?? undefined,
|
||||
enabled: Boolean(row.enabled),
|
||||
@@ -2679,6 +2684,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
mode: "prompt",
|
||||
phase: "pre-merge",
|
||||
prompt: template.prompt,
|
||||
gateMode: "advisory",
|
||||
toolMode: template.toolMode || "readonly",
|
||||
enabled: true,
|
||||
});
|
||||
@@ -7092,6 +7098,7 @@ ${stepsSection}`;
|
||||
mode,
|
||||
phase: input.phase || "pre-merge",
|
||||
prompt: mode === "prompt" ? (input.prompt || "") : "",
|
||||
gateMode: input.gateMode || (mode === "script" ? "gate" : "advisory"),
|
||||
toolMode: mode === "prompt" ? (input.toolMode || "readonly") : undefined,
|
||||
scriptName: mode === "script" ? input.scriptName : undefined,
|
||||
enabled: input.enabled !== undefined ? input.enabled : true,
|
||||
@@ -7111,6 +7118,7 @@ ${stepsSection}`;
|
||||
mode,
|
||||
phase,
|
||||
prompt,
|
||||
gateMode,
|
||||
toolMode,
|
||||
scriptName,
|
||||
enabled,
|
||||
@@ -7119,7 +7127,7 @@ ${stepsSection}`;
|
||||
modelId,
|
||||
createdAt,
|
||||
updatedAt
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
step.id,
|
||||
step.templateId ?? null,
|
||||
@@ -7128,6 +7136,7 @@ ${stepsSection}`;
|
||||
step.mode,
|
||||
step.phase || "pre-merge",
|
||||
step.prompt,
|
||||
step.gateMode ?? (step.mode === "script" ? "gate" : "advisory"),
|
||||
step.toolMode ?? null,
|
||||
step.scriptName ?? null,
|
||||
step.enabled ? 1 : 0,
|
||||
@@ -7170,6 +7179,7 @@ ${stepsSection}`;
|
||||
mode: entry.template.mode ?? "prompt",
|
||||
phase: entry.template.phase ?? "pre-merge",
|
||||
prompt: entry.template.prompt ?? "",
|
||||
gateMode: entry.template.gateMode ?? ((entry.template.mode ?? "prompt") === "script" ? "gate" : "advisory"),
|
||||
scriptName: entry.template.scriptName,
|
||||
toolMode: entry.template.toolMode,
|
||||
enabled: entry.template.enabled ?? true,
|
||||
@@ -7195,6 +7205,7 @@ ${stepsSection}`;
|
||||
mode: string;
|
||||
phase: string | null;
|
||||
prompt: string;
|
||||
gateMode: string | null;
|
||||
toolMode: string | null;
|
||||
scriptName: string | null;
|
||||
enabled: number;
|
||||
@@ -7232,6 +7243,7 @@ ${stepsSection}`;
|
||||
mode: string;
|
||||
phase: string | null;
|
||||
prompt: string;
|
||||
gateMode: string | null;
|
||||
toolMode: string | null;
|
||||
scriptName: string | null;
|
||||
enabled: number;
|
||||
@@ -7257,6 +7269,7 @@ ${stepsSection}`;
|
||||
mode: string;
|
||||
phase: string | null;
|
||||
prompt: string;
|
||||
gateMode: string | null;
|
||||
toolMode: string | null;
|
||||
scriptName: string | null;
|
||||
enabled: number;
|
||||
@@ -7289,6 +7302,7 @@ ${stepsSection}`;
|
||||
mode: string;
|
||||
phase: string | null;
|
||||
prompt: string;
|
||||
gateMode: string | null;
|
||||
toolMode: string | null;
|
||||
scriptName: string | null;
|
||||
enabled: number;
|
||||
@@ -7317,6 +7331,7 @@ ${stepsSection}`;
|
||||
// When switching to script mode, clear prompt and model overrides
|
||||
if (newMode === "script") {
|
||||
step.prompt = "";
|
||||
step.gateMode = step.gateMode || "gate";
|
||||
step.toolMode = undefined;
|
||||
step.modelProvider = undefined;
|
||||
step.modelId = undefined;
|
||||
@@ -7324,6 +7339,7 @@ ${stepsSection}`;
|
||||
// When switching to prompt mode, clear scriptName
|
||||
if (newMode === "prompt") {
|
||||
step.scriptName = undefined;
|
||||
step.gateMode = step.gateMode || "advisory";
|
||||
step.toolMode = step.toolMode || "readonly";
|
||||
}
|
||||
}
|
||||
@@ -7333,6 +7349,7 @@ ${stepsSection}`;
|
||||
if (updates.phase !== undefined) step.phase = updates.phase;
|
||||
if (updates.prompt !== undefined && step.mode === "prompt") step.prompt = updates.prompt;
|
||||
if (updates.toolMode !== undefined && step.mode === "prompt") step.toolMode = updates.toolMode;
|
||||
if (updates.gateMode !== undefined) step.gateMode = updates.gateMode;
|
||||
if (updates.scriptName !== undefined && step.mode === "script") step.scriptName = updates.scriptName;
|
||||
if (updates.enabled !== undefined) step.enabled = updates.enabled;
|
||||
if (updates.defaultOn !== undefined) step.defaultOn = updates.defaultOn;
|
||||
@@ -7353,6 +7370,7 @@ ${stepsSection}`;
|
||||
mode = ?,
|
||||
phase = ?,
|
||||
prompt = ?,
|
||||
gateMode = ?,
|
||||
toolMode = ?,
|
||||
scriptName = ?,
|
||||
enabled = ?,
|
||||
@@ -7368,6 +7386,7 @@ ${stepsSection}`;
|
||||
step.mode,
|
||||
step.phase || "pre-merge",
|
||||
step.prompt,
|
||||
step.gateMode ?? (step.mode === "script" ? "gate" : "advisory"),
|
||||
step.toolMode ?? null,
|
||||
step.scriptName ?? null,
|
||||
step.enabled ? 1 : 0,
|
||||
|
||||
@@ -240,6 +240,7 @@ export type WorkflowStepToolMode = "readonly" | "coding";
|
||||
|
||||
/** Lifecycle phase for workflow step execution. */
|
||||
export type WorkflowStepPhase = "pre-merge" | "post-merge";
|
||||
export type WorkflowStepGateMode = "gate" | "advisory";
|
||||
|
||||
export interface WorkflowStep {
|
||||
/** Unique identifier (e.g., "WS-001") */
|
||||
@@ -256,6 +257,8 @@ export interface WorkflowStep {
|
||||
phase?: WorkflowStepPhase;
|
||||
/** Full agent prompt to execute when this step runs (used when mode is "prompt") */
|
||||
prompt: string;
|
||||
/** Whether failures should block merge (`gate`) or be informational (`advisory`). */
|
||||
gateMode?: WorkflowStepGateMode;
|
||||
/** Tool set available to prompt-mode workflow agents. Defaults to readonly. */
|
||||
toolMode?: WorkflowStepToolMode;
|
||||
/** Name of a script from project settings `scripts` map to execute (required when mode is "script") */
|
||||
@@ -352,6 +355,8 @@ export interface WorkflowStepInput {
|
||||
phase?: WorkflowStepPhase;
|
||||
/** Agent prompt (used when mode is "prompt"). Optional — can be AI-generated later via refinement. */
|
||||
prompt?: string;
|
||||
/** Failure behavior. Defaults to advisory for prompt mode and gate for script mode. */
|
||||
gateMode?: WorkflowStepGateMode;
|
||||
/** Tool set available to prompt-mode workflow agents. Defaults to readonly. */
|
||||
toolMode?: WorkflowStepToolMode;
|
||||
/** Script name from project settings (required when mode is "script").
|
||||
@@ -377,7 +382,7 @@ export interface WorkflowStepResult {
|
||||
/** Lifecycle phase at execution time */
|
||||
phase?: WorkflowStepPhase;
|
||||
/** Execution status */
|
||||
status: "passed" | "failed" | "skipped" | "pending";
|
||||
status: "passed" | "failed" | "advisory_failure" | "skipped" | "pending";
|
||||
/** Output from the workflow step agent (findings, errors, etc.) */
|
||||
output?: string;
|
||||
/** ISO-8601 timestamp when the step started */
|
||||
@@ -404,6 +409,8 @@ export interface WorkflowStepTemplate {
|
||||
scriptName?: string;
|
||||
/** Tool set available when the template runs as a prompt-mode step. */
|
||||
toolMode?: WorkflowStepToolMode;
|
||||
/** Failure behavior for materialized steps from this template. */
|
||||
gateMode?: WorkflowStepGateMode;
|
||||
/** Whether this template should be auto-selected for new tasks. */
|
||||
defaultOn?: boolean;
|
||||
/** AI model provider override for prompt-mode templates. */
|
||||
|
||||
Reference in New Issue
Block a user