refactor!: rename RoleDefinition.meta → frontmatter

BREAKING CHANGE: All workflow YAML files must use 'frontmatter' instead of 'meta'.

- workflow-protocol: RoleDefinition.meta → frontmatter, schema updated
- cli-workflow: validate.ts, workflow.ts — resolveMetaRef → resolveFrontmatterRef
- workflow-agent-kit: run.ts — metaSchema → frontmatterSchema
- All YAML files updated (examples/, .workflows/)

Fixes #374
This commit is contained in:
2026-05-22 06:04:45 +00:00
parent 8ea554bb5e
commit 02f7f0b708
8 changed files with 39 additions and 25 deletions
+5 -5
View File
@@ -23,7 +23,7 @@ roles:
1. Store it via `uwf cas put "<markdown content>"` and capture the returned hash 1. Store it via `uwf cas put "<markdown content>"` and capture the returned hash
2. Put the hash in meta.plan (required when status=ready) 2. Put the hash in meta.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. Frontmatter must include: status (ready or insufficient_info) and plan (CAS hash of the test spec, required when status=ready)."
meta: frontmatter:
type: object type: object
properties: properties:
status: status:
@@ -45,7 +45,7 @@ roles:
5. Ensure `bun run build` passes with no errors 5. Ensure `bun run build` passes with no errors
6. Run `bun test` to verify all tests pass 6. 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. Frontmatter must include: status (done or failed)."
meta: frontmatter:
type: object type: object
properties: properties:
status: status:
@@ -75,7 +75,7 @@ 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: approved (true or false)." output: "Explain your decision with specific file/line references. Frontmatter must include: approved (true or false)."
meta: frontmatter:
type: object type: object
properties: properties:
approved: approved:
@@ -95,7 +95,7 @@ roles:
- 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. Frontmatter must include: status (passed, fix_code, or fix_spec)."
meta: frontmatter:
type: object type: object
properties: properties:
status: status:
@@ -115,7 +115,7 @@ roles:
4. On push success: create a PR via `tea pr create --title "..." --description "..."` 4. On push success: create a PR via `tea pr create --title "..." --description "..."`
- PR description must follow the project template: What / Why / Changes / Ref sections, with `Fixes #N` in Ref - PR description must follow the project template: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
output: "Include PR URL on success or error log on failure. Frontmatter must include: success (true or false)." output: "Include PR URL on success or error log on failure. Frontmatter must include: success (true or false)."
meta: frontmatter:
type: object type: object
properties: properties:
success: success:
+1 -1
View File
@@ -19,7 +19,7 @@ roles:
output: | output: |
Provide your analysis as markdown under the frontmatter. Provide your analysis as markdown under the frontmatter.
The frontmatter must include your structured findings. The frontmatter must include your structured findings.
meta: frontmatter:
type: object type: object
properties: properties:
thesis: thesis:
+3 -3
View File
@@ -9,7 +9,7 @@ roles:
- planning - planning
procedure: "Analyze the issue and create a detailed, actionable implementation plan." procedure: "Analyze the issue and create a detailed, actionable implementation plan."
output: "Output the plan summary and list of concrete steps." output: "Output the plan summary and list of concrete steps."
meta: frontmatter:
type: object type: object
properties: properties:
plan: plan:
@@ -28,7 +28,7 @@ roles:
- testing - testing
procedure: "Implement the plan. Write code, tests, and ensure existing tests pass." procedure: "Implement the plan. Write code, tests, and ensure existing tests pass."
output: "List all files changed and provide a summary of the implementation." output: "List all files changed and provide a summary of the implementation."
meta: frontmatter:
type: object type: object
properties: properties:
filesChanged: filesChanged:
@@ -46,7 +46,7 @@ roles:
- static-analysis - static-analysis
procedure: "Review the implementation against the plan. Check for bugs, edge cases, and style." procedure: "Review the implementation against the plan. Check for bugs, edge cases, and style."
output: "Approve or reject with detailed comments explaining your decision." output: "Approve or reject with detailed comments explaining your decision."
meta: frontmatter:
type: object type: object
properties: properties:
approved: approved:
+15 -6
View File
@@ -46,11 +46,16 @@ function isJsonSchema(value: unknown): value is JSONSchema {
return typeof value === "object" && value !== null && !Array.isArray(value); return typeof value === "object" && value !== null && !Array.isArray(value);
} }
async function resolveMetaRef(uwf: UwfStore, roleName: string, meta: unknown): Promise<CasRef> { async function resolveFrontmatterRef(
if (!isJsonSchema(meta)) { uwf: UwfStore,
fail(`role "${roleName}": meta must be a JSON Schema object`); roleName: string,
frontmatter: unknown,
): Promise<CasRef> {
if (!isJsonSchema(frontmatter)) {
fail(`role "${roleName}": frontmatter must be a JSON Schema object`);
} }
const schema: JSONSchema = meta.title === undefined ? { ...meta, title: roleName } : meta; const schema: JSONSchema =
frontmatter.title === undefined ? { ...frontmatter, title: roleName } : frontmatter;
return putSchema(uwf.store, schema); return putSchema(uwf.store, schema);
} }
@@ -60,14 +65,18 @@ export async function materializeWorkflowPayload(
): Promise<WorkflowPayload> { ): Promise<WorkflowPayload> {
const roles: Record<string, RoleDefinition> = {}; const roles: Record<string, RoleDefinition> = {};
for (const [roleName, role] of Object.entries(raw.roles)) { for (const [roleName, role] of Object.entries(raw.roles)) {
const meta = await resolveMetaRef(uwf, `${raw.name}.${roleName}`, role.meta); const frontmatter = await resolveFrontmatterRef(
uwf,
`${raw.name}.${roleName}`,
role.frontmatter,
);
roles[roleName] = { roles[roleName] = {
description: role.description, description: role.description,
goal: role.goal, goal: role.goal,
capabilities: role.capabilities, capabilities: role.capabilities,
procedure: role.procedure, procedure: role.procedure,
output: role.output, output: role.output,
meta, frontmatter,
}; };
} }
return { return {
+3 -3
View File
@@ -15,8 +15,8 @@ function isRoleDefinition(value: unknown): boolean {
if (!isRecord(value)) { if (!isRecord(value)) {
return false; return false;
} }
const meta = value.meta; const frontmatter = value.frontmatter;
const metaOk = isRecord(meta) && typeof meta.type === "string"; const frontmatterOk = isRecord(frontmatter) && typeof frontmatter.type === "string";
const capabilities = value.capabilities; const capabilities = value.capabilities;
const capabilitiesOk = const capabilitiesOk =
Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string"); Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string");
@@ -26,7 +26,7 @@ function isRoleDefinition(value: unknown): boolean {
capabilitiesOk && capabilitiesOk &&
typeof value.procedure === "string" && typeof value.procedure === "string" &&
typeof value.output === "string" && typeof value.output === "string" &&
metaOk frontmatterOk
); );
} }
+9 -4
View File
@@ -130,13 +130,18 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
fail(`unknown role: ${role}`); fail(`unknown role: ${role}`);
} }
const metaSchema = getSchema(ctx.meta.store, roleDef.meta); const frontmatterSchema = getSchema(ctx.meta.store, roleDef.frontmatter);
if (metaSchema !== null) { if (frontmatterSchema !== null) {
ctx.outputFormatInstruction = buildOutputFormatInstruction(metaSchema); ctx.outputFormatInstruction = buildOutputFormatInstruction(frontmatterSchema);
} }
const agentResult = await runAgent(options, ctx); const agentResult = await runAgent(options, ctx);
const outputHash = await extractOutput(agentResult.output, roleDef.meta, storageRoot, ctx); const outputHash = await extractOutput(
agentResult.output,
roleDef.frontmatter,
storageRoot,
ctx,
);
const stepHash = await persistStep({ const stepHash = await persistStep({
ctx, ctx,
outputHash, outputHash,
+2 -2
View File
@@ -2,14 +2,14 @@ import type { JSONSchema } from "@uncaged/json-cas";
const ROLE_DEFINITION: JSONSchema = { const ROLE_DEFINITION: JSONSchema = {
type: "object", type: "object",
required: ["description", "goal", "capabilities", "procedure", "output", "meta"], required: ["description", "goal", "capabilities", "procedure", "output", "frontmatter"],
properties: { properties: {
description: { type: "string" }, description: { type: "string" },
goal: { type: "string" }, goal: { type: "string" },
capabilities: { type: "array", items: { type: "string" } }, capabilities: { type: "array", items: { type: "string" } },
procedure: { type: "string" }, procedure: { type: "string" },
output: { type: "string" }, output: { type: "string" },
meta: { type: "string", format: "cas_ref" }, frontmatter: { type: "string", format: "cas_ref" },
}, },
additionalProperties: false, additionalProperties: false,
}; };
+1 -1
View File
@@ -22,7 +22,7 @@ export type RoleDefinition = {
capabilities: string[]; capabilities: string[];
procedure: string; procedure: string;
output: string; output: string;
meta: CasRef; frontmatter: CasRef;
}; };
export type Transition = { export type Transition = {