refactor: rename status to $status, default to _ when absent
- evaluate() reads $status instead of status, defaults to _ when missing - Update all YAML examples and .workflows to use $status - Update cli-workflow resolveEvaluateArgs to use $status - 10 moderator tests pass including new default _ test - Single-exit roles no longer need to declare status field Phase 1 of #499 (closes #500)
This commit is contained in:
@@ -81,7 +81,7 @@ describe("solve-issue workflow: tea pr create worktree fix", () => {
|
||||
expect(workflow.roles.committer?.frontmatter).toBeDefined();
|
||||
});
|
||||
|
||||
test("committer frontmatter schema should require status field", async () => {
|
||||
test("committer frontmatter schema should require $status field", async () => {
|
||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||
// Parse as any to access the raw YAML structure (frontmatter is inline JSON Schema in YAML)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -90,8 +90,8 @@ describe("solve-issue workflow: tea pr create worktree fix", () => {
|
||||
const frontmatter = workflow.roles.committer?.frontmatter;
|
||||
expect(frontmatter).toBeDefined();
|
||||
expect(frontmatter?.type).toBe("object");
|
||||
expect(frontmatter?.properties?.status).toBeDefined();
|
||||
expect(frontmatter?.properties?.status?.enum).toContain("committed");
|
||||
expect(frontmatter?.required).toContain("status");
|
||||
expect(frontmatter?.properties?.["$status"]).toBeDefined();
|
||||
expect(frontmatter?.properties?.["$status"]?.enum).toContain("committed");
|
||||
expect(frontmatter?.required).toContain("$status");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -669,14 +669,16 @@ function formatThreadReadMarkdown(options: {
|
||||
return parts.join("\n\n---\n\n");
|
||||
}
|
||||
|
||||
type EvaluateLastOutput = Record<string, unknown> & { status: string };
|
||||
type EvaluateLastOutput = Record<string, unknown>;
|
||||
|
||||
const STATUS_KEY = "$status";
|
||||
|
||||
function resolveEvaluateArgs(
|
||||
uwf: UwfStore,
|
||||
chain: ChainState,
|
||||
): { lastRole: string; lastOutput: EvaluateLastOutput } {
|
||||
if (chain.headIsStart) {
|
||||
return { lastRole: START_ROLE, lastOutput: { status: "_" } };
|
||||
return { lastRole: START_ROLE, lastOutput: { [STATUS_KEY]: "_" } };
|
||||
}
|
||||
|
||||
const lastStep = chain.stepsNewestFirst[0];
|
||||
@@ -689,11 +691,10 @@ function resolveEvaluateArgs(
|
||||
typeof raw === "object" && raw !== null && !Array.isArray(raw)
|
||||
? (raw as Record<string, unknown>)
|
||||
: {};
|
||||
const status = typeof base.status === "string" ? base.status : "_";
|
||||
|
||||
return {
|
||||
lastRole: lastStep.role,
|
||||
lastOutput: { ...base, status },
|
||||
lastOutput: base,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const solveIssueGraph: WorkflowPayload["graph"] = {
|
||||
|
||||
describe("evaluate", () => {
|
||||
test("$START → first role (unit status _)", () => {
|
||||
const result = evaluate(solveIssueGraph, "$START", { status: "_" });
|
||||
const result = evaluate(solveIssueGraph, "$START", { $status: "_" });
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
value: { role: "planner", prompt: "Start planning from the issue in the task." },
|
||||
@@ -30,7 +30,7 @@ describe("evaluate", () => {
|
||||
|
||||
test("status-based routing (reviewer rejected → developer)", () => {
|
||||
const result = evaluate(solveIssueGraph, "reviewer", {
|
||||
status: "rejected",
|
||||
$status: "rejected",
|
||||
comments: "missing tests",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
@@ -40,7 +40,7 @@ describe("evaluate", () => {
|
||||
});
|
||||
|
||||
test("status-based routing (reviewer approved → $END)", () => {
|
||||
const result = evaluate(solveIssueGraph, "reviewer", { status: "approved" });
|
||||
const result = evaluate(solveIssueGraph, "reviewer", { $status: "approved" });
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
value: { role: "$END", prompt: "Done." },
|
||||
@@ -48,7 +48,7 @@ describe("evaluate", () => {
|
||||
});
|
||||
|
||||
test("missing role in graph → error", () => {
|
||||
const result = evaluate(solveIssueGraph, "unknown-role", { status: "_" });
|
||||
const result = evaluate(solveIssueGraph, "unknown-role", { $status: "_" });
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error.message).toBe('no transitions defined for role "unknown-role"');
|
||||
@@ -56,7 +56,7 @@ describe("evaluate", () => {
|
||||
});
|
||||
|
||||
test("missing status in graph → error", () => {
|
||||
const result = evaluate(solveIssueGraph, "reviewer", { status: "pending" });
|
||||
const result = evaluate(solveIssueGraph, "reviewer", { $status: "pending" });
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error.message).toBe('no transition for role "reviewer" with status "pending"');
|
||||
@@ -65,7 +65,7 @@ describe("evaluate", () => {
|
||||
|
||||
test("mustache template rendering with simple fields", () => {
|
||||
const result = evaluate(solveIssueGraph, "planner", {
|
||||
status: "_",
|
||||
$status: "_",
|
||||
plan: "Add auth middleware",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
@@ -76,7 +76,7 @@ describe("evaluate", () => {
|
||||
|
||||
test("mustache does not HTML-escape prompt content", () => {
|
||||
const result = evaluate(solveIssueGraph, "reviewer", {
|
||||
status: "rejected",
|
||||
$status: "rejected",
|
||||
comments: 'use <T> & "Result<T, E>" types',
|
||||
});
|
||||
expect(result).toEqual({
|
||||
@@ -92,7 +92,7 @@ describe("evaluate", () => {
|
||||
},
|
||||
};
|
||||
const result = evaluate(graph, "reviewer", {
|
||||
status: "_",
|
||||
$status: "_",
|
||||
comments: "<script>alert(1)</script>",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
@@ -101,6 +101,16 @@ describe("evaluate", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("missing $status defaults to _ (unit routing)", () => {
|
||||
const result = evaluate(solveIssueGraph, "planner", {
|
||||
plan: "Add auth middleware",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
value: { role: "developer", prompt: "Implement the plan: Add auth middleware" },
|
||||
});
|
||||
});
|
||||
|
||||
test("mustache template with nested object paths", () => {
|
||||
const graph: Record<string, Record<string, Target>> = {
|
||||
reviewer: {
|
||||
@@ -111,7 +121,7 @@ describe("evaluate", () => {
|
||||
},
|
||||
};
|
||||
const result = evaluate(graph, "reviewer", {
|
||||
status: "_",
|
||||
$status: "_",
|
||||
review: { comments: "refactor the handler" },
|
||||
});
|
||||
expect(result).toEqual({
|
||||
|
||||
@@ -9,14 +9,21 @@ mustache.escape = (text: string) => text;
|
||||
const START_ROLE = "$START";
|
||||
const UNIT_STATUS = "_";
|
||||
|
||||
type LastOutput = Record<string, unknown> & { status: string };
|
||||
type LastOutput = Record<string, unknown>;
|
||||
|
||||
const STATUS_KEY = "$status";
|
||||
|
||||
export function evaluate(
|
||||
graph: Record<string, Record<string, Target>>,
|
||||
lastRole: string,
|
||||
lastOutput: LastOutput,
|
||||
): Result<EvaluateResult, Error> {
|
||||
const status = lastRole === START_ROLE ? UNIT_STATUS : lastOutput.status;
|
||||
const status =
|
||||
lastRole === START_ROLE
|
||||
? UNIT_STATUS
|
||||
: typeof lastOutput[STATUS_KEY] === "string"
|
||||
? (lastOutput[STATUS_KEY] as string)
|
||||
: UNIT_STATUS;
|
||||
|
||||
const roleTargets = graph[lastRole];
|
||||
if (roleTargets === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user