refactor: discriminated union frontmatter for solve-issue workflow
- planner: oneOf ready (plan, repoPath) | insufficient_info - developer: single exit, plain object (branch, worktree), no $status - reviewer: oneOf approved (branch, worktree) | rejected (comments) - tester: oneOf passed (branch, worktree) | fix_code (report) | fix_spec (report) - committer: oneOf committed (prUrl) | hook_failed (error) - Edge prompts now use mustache templates with variant-specific fields - Developer simplified from 2 exits to single exit (unit routing) Phase 2 of #499 (closes #501)
This commit is contained in:
+59
-45
@@ -22,16 +22,17 @@ roles:
|
|||||||
After producing the test spec:
|
After producing the test spec:
|
||||||
1. Store it via `uwf cas put-text "<markdown content>"` and capture the returned hash
|
1. Store it via `uwf cas put-text "<markdown content>"` and capture the returned hash
|
||||||
2. Put the hash in frontmatter.plan (required when status=ready)
|
2. Put the hash in frontmatter.plan (required when status=ready)
|
||||||
output: "Output a brief summary of the test spec. Frontmatter must include: $status (ready or insufficient_info) and plan (CAS hash of the test spec, required when status=ready)."
|
output: "Output a brief summary of the test spec. Set $status to ready (with plan hash and repoPath) or insufficient_info."
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
oneOf:
|
||||||
properties:
|
- properties:
|
||||||
$status:
|
$status: { const: "ready" }
|
||||||
type: string
|
plan: { type: string }
|
||||||
enum: [ready, insufficient_info]
|
repoPath: { type: string }
|
||||||
plan:
|
required: [$status, plan, repoPath]
|
||||||
type: string
|
- properties:
|
||||||
required: [$status]
|
$status: { const: "insufficient_info" }
|
||||||
|
required: [$status]
|
||||||
developer:
|
developer:
|
||||||
description: "TDD implementation per test spec"
|
description: "TDD implementation per test spec"
|
||||||
goal: "You are a developer agent. You implement code changes following TDD — write tests first, then implementation."
|
goal: "You are a developer agent. You implement code changes following TDD — write tests first, then implementation."
|
||||||
@@ -58,14 +59,13 @@ roles:
|
|||||||
8. Implement the code to make tests pass
|
8. Implement the code to make tests pass
|
||||||
9. Ensure `bun run build` passes with no errors
|
9. Ensure `bun run build` passes with no errors
|
||||||
10. Run `bun test` to verify all tests pass
|
10. Run `bun test` to verify all tests pass
|
||||||
output: "List all files changed and provide a summary. Frontmatter must include: $status (done or failed)."
|
output: "List all files changed and provide a summary. Include branch name and worktree path in frontmatter."
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
$status:
|
branch: { type: string }
|
||||||
type: string
|
worktree: { type: string }
|
||||||
enum: [done, failed]
|
required: [branch, worktree]
|
||||||
required: [$status]
|
|
||||||
reviewer:
|
reviewer:
|
||||||
description: "Code standards compliance check"
|
description: "Code standards compliance check"
|
||||||
goal: "You are a code reviewer. You verify code standards compliance — NOT functionality (that's the tester's job)."
|
goal: "You are a code reviewer. You verify code standards compliance — NOT functionality (that's the tester's job)."
|
||||||
@@ -95,14 +95,18 @@ roles:
|
|||||||
|
|
||||||
Only review standards compliance. Do NOT test functionality.
|
Only review standards compliance. Do NOT test functionality.
|
||||||
If rejecting, you MUST explain the specific reason in your output.
|
If rejecting, you MUST explain the specific reason in your output.
|
||||||
output: "Explain your decision with specific file/line references. Frontmatter must include: $status (approved or rejected)."
|
output: "Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments)."
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
oneOf:
|
||||||
properties:
|
- properties:
|
||||||
$status:
|
$status: { const: "approved" }
|
||||||
type: string
|
branch: { type: string }
|
||||||
enum: [approved, rejected]
|
worktree: { type: string }
|
||||||
required: [$status]
|
required: [$status, branch, worktree]
|
||||||
|
- properties:
|
||||||
|
$status: { const: "rejected" }
|
||||||
|
comments: { type: string }
|
||||||
|
required: [$status, comments]
|
||||||
tester:
|
tester:
|
||||||
description: "Functional correctness verification"
|
description: "Functional correctness verification"
|
||||||
goal: "You are a tester agent. You verify that the implementation correctly satisfies every scenario in the test spec."
|
goal: "You are a tester agent. You verify that the implementation correctly satisfies every scenario in the test spec."
|
||||||
@@ -118,14 +122,22 @@ roles:
|
|||||||
- passed: all scenarios verified, tests pass
|
- passed: all scenarios verified, tests pass
|
||||||
- fix_code: tests fail or implementation doesn't match spec → send back to developer
|
- fix_code: tests fail or implementation doesn't match spec → send back to developer
|
||||||
- fix_spec: the spec itself is wrong or incomplete → send back to planner
|
- fix_spec: the spec itself is wrong or incomplete → send back to planner
|
||||||
output: "Report test results per scenario. Frontmatter must include: $status (passed, fix_code, or fix_spec)."
|
output: "Report test results per scenario. Set $status to passed (with branch/worktree), fix_code (with report), or fix_spec (with report)."
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
oneOf:
|
||||||
properties:
|
- properties:
|
||||||
$status:
|
$status: { const: "passed" }
|
||||||
type: string
|
branch: { type: string }
|
||||||
enum: [passed, fix_code, fix_spec]
|
worktree: { type: string }
|
||||||
required: [$status]
|
required: [$status, branch, worktree]
|
||||||
|
- properties:
|
||||||
|
$status: { const: "fix_code" }
|
||||||
|
report: { type: string }
|
||||||
|
required: [$status, report]
|
||||||
|
- properties:
|
||||||
|
$status: { const: "fix_spec" }
|
||||||
|
report: { type: string }
|
||||||
|
required: [$status, report]
|
||||||
committer:
|
committer:
|
||||||
description: "Commits and creates PR"
|
description: "Commits and creates PR"
|
||||||
goal: "You are a committer agent. You create a clean commit and push a PR linking the original issue."
|
goal: "You are a committer agent. You create a clean commit and push a PR linking the original issue."
|
||||||
@@ -146,30 +158,32 @@ roles:
|
|||||||
5. After PR creation, clean up the worktree:
|
5. After PR creation, clean up the worktree:
|
||||||
- `cd ~/repos/workflow`
|
- `cd ~/repos/workflow`
|
||||||
- `git worktree remove ~/repos/workflow-worktrees/fix/<issue-number>-<slug>`
|
- `git worktree remove ~/repos/workflow-worktrees/fix/<issue-number>-<slug>`
|
||||||
output: "Include PR URL on success or error log on failure. Frontmatter must include: $status (committed or hook_failed)."
|
output: "Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error)."
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
oneOf:
|
||||||
properties:
|
- properties:
|
||||||
$status:
|
$status: { const: "committed" }
|
||||||
type: string
|
prUrl: { type: string }
|
||||||
enum: [committed, hook_failed]
|
required: [$status, prUrl]
|
||||||
required: [$status]
|
- properties:
|
||||||
|
$status: { const: "hook_failed" }
|
||||||
|
error: { type: string }
|
||||||
|
required: [$status, error]
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
_: { role: "planner", prompt: "Analyze the issue and produce an implementation plan." }
|
_: { role: "planner", prompt: "Analyze the issue and produce an implementation plan." }
|
||||||
planner:
|
planner:
|
||||||
insufficient_info: { role: "$END", prompt: "Insufficient information to proceed; end the workflow." }
|
insufficient_info: { role: "$END", prompt: "Insufficient information to proceed; end the workflow." }
|
||||||
ready: { role: "developer", prompt: "Implement the plan from the planner." }
|
ready: { role: "developer", prompt: "Implement the TDD test spec (CAS hash: {{{plan}}}) in repo {{{repoPath}}}." }
|
||||||
developer:
|
developer:
|
||||||
failed: { role: "$END", prompt: "Development failed; end the workflow." }
|
_: { role: "reviewer", prompt: "Review branch {{{branch}}} at {{{worktree}}} for code standards compliance." }
|
||||||
done: { role: "reviewer", prompt: "Send the implementation to the reviewer." }
|
|
||||||
reviewer:
|
reviewer:
|
||||||
rejected: { role: "developer", prompt: "Reviewer rejected the implementation; fix the issues." }
|
rejected: { role: "developer", prompt: "Reviewer rejected: {{{comments}}}. Fix the issues." }
|
||||||
approved: { role: "tester", prompt: "Review passed; run tests on the implementation." }
|
approved: { role: "tester", prompt: "Review passed. Run tests on branch {{{branch}}} at {{{worktree}}}." }
|
||||||
tester:
|
tester:
|
||||||
fix_code: { role: "developer", prompt: "Tests found code issues; return to developer." }
|
fix_code: { role: "developer", prompt: "Tests found code issues: {{{report}}}. Fix and re-submit." }
|
||||||
fix_spec: { role: "planner", prompt: "Tests found spec issues; return to planner." }
|
fix_spec: { role: "planner", prompt: "Tests found spec issues: {{{report}}}. Revise the test spec." }
|
||||||
passed: { role: "committer", prompt: "Tests passed; commit and push the changes." }
|
passed: { role: "committer", prompt: "All tests passed. Commit and push branch {{{branch}}} from {{{worktree}}}." }
|
||||||
committer:
|
committer:
|
||||||
hook_failed: { role: "developer", prompt: "Push hook failed; return to developer to fix." }
|
hook_failed: { role: "developer", prompt: "Push hook failed: {{{error}}}. Fix and re-submit." }
|
||||||
committed: { role: "$END", prompt: "Commit succeeded; complete the workflow." }
|
committed: { role: "$END", prompt: "PR created: {{{prUrl}}}. Workflow complete." }
|
||||||
|
|||||||
@@ -81,17 +81,18 @@ describe("solve-issue workflow: tea pr create worktree fix", () => {
|
|||||||
expect(workflow.roles.committer?.frontmatter).toBeDefined();
|
expect(workflow.roles.committer?.frontmatter).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("committer frontmatter schema should require $status field", async () => {
|
test("committer frontmatter schema should be oneOf with $status discriminant", async () => {
|
||||||
const yamlContent = await readFile(workflowPath, "utf-8");
|
const yamlContent = await readFile(workflowPath, "utf-8");
|
||||||
// Parse as any to access the raw YAML structure (frontmatter is inline JSON Schema in YAML)
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const workflow = parse(yamlContent) as any;
|
const workflow = parse(yamlContent) as any;
|
||||||
|
|
||||||
const frontmatter = workflow.roles.committer?.frontmatter;
|
const frontmatter = workflow.roles.committer?.frontmatter;
|
||||||
expect(frontmatter).toBeDefined();
|
expect(frontmatter).toBeDefined();
|
||||||
expect(frontmatter?.type).toBe("object");
|
expect(frontmatter?.oneOf).toBeDefined();
|
||||||
expect(frontmatter?.properties?.["$status"]).toBeDefined();
|
const committedVariant = frontmatter.oneOf.find(
|
||||||
expect(frontmatter?.properties?.["$status"]?.enum).toContain("committed");
|
(v: any) => v.properties?.["$status"]?.const === "committed",
|
||||||
expect(frontmatter?.required).toContain("$status");
|
);
|
||||||
|
expect(committedVariant).toBeDefined();
|
||||||
|
expect(committedVariant.required).toContain("$status");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user