refactor: rename RoleDefinition fields for clarity

- identity → goal
- prepare → capabilities (string[])
- execute → procedure
- report → output
- outputSchema → meta

Fixes #364
This commit is contained in:
2026-05-22 00:46:06 +00:00
parent fe035c065d
commit c60c310074
13 changed files with 187 additions and 129 deletions
@@ -211,11 +211,11 @@ describe("cmdThreadRead ### Content section", () => {
roles: {
writer: {
description: "Write",
identity: "You are a writer.",
prepare: "",
execute: "Write content as requested.",
report: "Summarize what was written.",
outputSchema: "placeholder00" as CasRef,
goal: "You are a writer.",
capabilities: [],
procedure: "Write content as requested.",
output: "Summarize what was written.",
meta: "placeholder00" as CasRef,
},
},
conditions: {},
+1 -1
View File
@@ -500,7 +500,7 @@ function formatThreadReadMarkdown(options: {
];
const roleDef = workflow.roles[item.payload.role];
if (roleDef) {
const prompt = roleDef.identity;
const prompt = roleDef.goal;
stepLines.push("", "### Prompt", "", prompt);
}
if (item.payload.detail) {
+14 -14
View File
@@ -42,17 +42,17 @@ function isJsonSchema(value: unknown): value is JSONSchema {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function resolveOutputSchemaRef(
async function resolveMetaRef(
uwf: UwfStore,
roleName: string,
outputSchema: unknown,
meta: unknown,
): Promise<CasRef> {
if (!isJsonSchema(outputSchema)) {
fail(`role "${roleName}": outputSchema must be a JSON Schema object`);
if (!isJsonSchema(meta)) {
fail(`role "${roleName}": meta must be a JSON Schema object`);
}
const schema: JSONSchema = outputSchema.title === undefined
? { ...outputSchema, title: roleName }
: outputSchema;
const schema: JSONSchema = meta.title === undefined
? { ...meta, title: roleName }
: meta;
return putSchema(uwf.store, schema);
}
@@ -62,18 +62,18 @@ async function materializeWorkflowPayload(
): Promise<WorkflowPayload> {
const roles: Record<string, RoleDefinition> = {};
for (const [roleName, role] of Object.entries(raw.roles)) {
const outputSchema = await resolveOutputSchemaRef(
const meta = await resolveMetaRef(
uwf,
`${raw.name}.${roleName}`,
role.outputSchema,
role.meta,
);
roles[roleName] = {
description: role.description,
identity: role.identity,
prepare: role.prepare,
execute: role.execute,
report: role.report,
outputSchema,
goal: role.goal,
capabilities: role.capabilities,
procedure: role.procedure,
output: role.output,
meta,
};
}
return {
+10 -7
View File
@@ -14,15 +14,18 @@ function isRoleDefinition(value: unknown): boolean {
if (!isRecord(value)) {
return false;
}
const outputSchema = value.outputSchema;
const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string";
const meta = value.meta;
const metaOk = isRecord(meta) && typeof meta.type === "string";
const capabilities = value.capabilities;
const capabilitiesOk =
Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string");
return (
typeof value.description === "string" &&
typeof value.identity === "string" &&
typeof value.prepare === "string" &&
typeof value.execute === "string" &&
typeof value.report === "string" &&
schemaOk
typeof value.goal === "string" &&
capabilitiesOk &&
typeof value.procedure === "string" &&
typeof value.output === "string" &&
metaOk
);
}
@@ -1,54 +1,71 @@
import { describe, expect, test } from "vitest";
import type { RoleDefinition } from "@uncaged/workflow-protocol";
import { describe, expect, test } from "vitest";
import { buildRolePrompt } from "../src/build-role-prompt.js";
describe("buildRolePrompt", () => {
test("all fields present", () => {
const role: RoleDefinition = {
description: "A coder",
identity: "You are a senior developer.",
prepare: "Load cursor-agent skill.",
execute: "Implement the feature.",
report: "Summarize changes.",
outputSchema: "placeholder00000" as string,
goal: "You are a senior developer.",
capabilities: ["cursor-agent", "file-edit"],
procedure: "Implement the feature.",
output: "Summarize changes.",
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Identity");
expect(result).toContain("## Goal");
expect(result).toContain("You are a senior developer.");
expect(result).toContain("## Prepare");
expect(result).toContain("Load cursor-agent skill.");
expect(result).toContain("## Execute");
expect(result).toContain("## Capabilities");
expect(result).toContain("- cursor-agent");
expect(result).toContain("- file-edit");
expect(result).toContain("## Procedure");
expect(result).toContain("Implement the feature.");
expect(result).toContain("## Report");
expect(result).toContain("## Output");
expect(result).toContain("Summarize changes.");
});
test("empty fields are omitted", () => {
const role: RoleDefinition = {
description: "A reviewer",
identity: "You are a code reviewer.",
prepare: "",
execute: "Review the PR diff carefully.",
report: "",
outputSchema: "placeholder00000" as string,
goal: "You are a code reviewer.",
capabilities: [],
procedure: "Review the PR diff carefully.",
output: "",
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Identity");
expect(result).toContain("## Execute");
expect(result).not.toContain("## Prepare");
expect(result).not.toContain("## Report");
expect(result).toContain("## Goal");
expect(result).toContain("## Procedure");
expect(result).not.toContain("## Capabilities");
expect(result).not.toContain("## Output");
});
test("all empty returns empty string", () => {
const role: RoleDefinition = {
description: "Minimal",
identity: "",
prepare: "",
execute: "",
report: "",
outputSchema: "placeholder00000" as string,
goal: "",
capabilities: [],
procedure: "",
output: "",
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toBe("");
});
test("capabilities rendered as bullet list", () => {
const role: RoleDefinition = {
description: "Agent",
goal: "",
capabilities: ["search", "code", "browse"],
procedure: "",
output: "",
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toContain("## Capabilities");
expect(result).toContain("- search");
expect(result).toContain("- code");
expect(result).toContain("- browse");
});
});
@@ -3,26 +3,27 @@ import type { RoleDefinition } from "@uncaged/workflow-protocol";
/**
* Build the role prompt from a RoleDefinition.
*
* Assembles structured sections: Identity, Prepare, Execute, Report.
* Empty strings are omitted from the output.
* Assembles structured sections: Goal, Capabilities, Procedure, Output.
* Empty strings and empty arrays are omitted from the output.
*/
export function buildRolePrompt(role: RoleDefinition): string {
const sections: string[] = [];
if (role.identity !== "") {
sections.push(`## Identity\n\n${role.identity}`);
if (role.goal !== "") {
sections.push(`## Goal\n\n${role.goal}`);
}
if (role.prepare !== "") {
sections.push(`## Prepare\n\n${role.prepare}`);
if (role.capabilities.length > 0) {
const list = role.capabilities.map((c) => `- ${c}`).join("\n");
sections.push(`## Capabilities\n\n${list}`);
}
if (role.execute !== "") {
sections.push(`## Execute\n\n${role.execute}`);
if (role.procedure !== "") {
sections.push(`## Procedure\n\n${role.procedure}`);
}
if (role.report !== "") {
sections.push(`## Report\n\n${role.report}`);
if (role.output !== "") {
sections.push(`## Output\n\n${role.output}`);
}
return sections.join("\n\n");
+4 -4
View File
@@ -131,13 +131,13 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
fail(`unknown role: ${role}`);
}
const outputSchema = getSchema(ctx.meta.store, roleDef.outputSchema);
if (outputSchema !== null) {
ctx.outputFormatInstruction = buildOutputFormatInstruction(outputSchema);
const metaSchema = getSchema(ctx.meta.store, roleDef.meta);
if (metaSchema !== null) {
ctx.outputFormatInstruction = buildOutputFormatInstruction(metaSchema);
}
const agentResult = await runAgent(options, ctx);
const outputHash = await extractOutput(agentResult.output, roleDef.outputSchema, storageRoot, ctx);
const outputHash = await extractOutput(agentResult.output, roleDef.meta, storageRoot, ctx);
const stepHash = await persistStep({
ctx,
outputHash,
+6 -6
View File
@@ -2,14 +2,14 @@ import type { JSONSchema } from "@uncaged/json-cas";
const ROLE_DEFINITION: JSONSchema = {
type: "object",
required: ["description", "identity", "prepare", "execute", "report", "outputSchema"],
required: ["description", "goal", "capabilities", "procedure", "output", "meta"],
properties: {
description: { type: "string" },
identity: { type: "string" },
prepare: { type: "string" },
execute: { type: "string" },
report: { type: "string" },
outputSchema: { type: "string", format: "cas_ref" },
goal: { type: "string" },
capabilities: { type: "array", items: { type: "string" } },
procedure: { type: "string" },
output: { type: "string" },
meta: { type: "string", format: "cas_ref" },
},
additionalProperties: false,
};
+5 -5
View File
@@ -18,11 +18,11 @@ export type StepRecord = {
export type RoleDefinition = {
description: string;
identity: string;
prepare: string;
execute: string;
report: string;
outputSchema: CasRef;
goal: string;
capabilities: string[];
procedure: string;
output: string;
meta: CasRef;
};
export type Transition = {