feat: add uwf skill cli command and Prepare section in role prompt

- Add 'uwf skill cli' command that prints markdown CLI reference
- buildRolePrompt now generates ## Prepare section:
  - Always prompts agent to run 'uwf skill cli' (explicit skill)
  - Renders capabilities as keyword hints for implicit skill loading

Fixes #369
This commit is contained in:
2026-05-22 03:20:04 +00:00
parent 8efc5050cb
commit 866154ad73
4 changed files with 114 additions and 4 deletions
+10
View File
@@ -14,6 +14,7 @@ import {
cmdCasWalk,
} from "./commands/cas.js";
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
import { cmdSkillCli } from "./commands/skill.js";
import {
cmdThreadFork,
cmdThreadKill,
@@ -220,6 +221,15 @@ thread
});
});
const skill = program.command("skill").description("Built-in skill references for agents");
skill
.command("cli")
.description("Print a markdown reference of all uwf commands")
.action(() => {
console.log(cmdSkillCli());
});
program
.command("setup")
.description("Configure provider, model, and agent")
@@ -0,0 +1,70 @@
export function cmdSkillCli(): string {
return `# uwf CLI Reference
## Setup
\`\`\`
uwf setup # interactive setup wizard
uwf setup --provider <name> --base-url <url> \\
--api-key <key> --model <name> # non-interactive setup
[--agent <name>] # optional: default agent alias
\`\`\`
## Workflow Commands
\`\`\`
uwf workflow put <file> # register a workflow from YAML file
uwf workflow show <id> # show workflow by name or CAS hash
uwf workflow list # list all registered workflows
\`\`\`
## Thread Commands
\`\`\`
uwf thread start <workflow> -p <prompt> # create a thread (no execution)
uwf thread step <thread-id> # execute one moderator→agent→extract cycle
[--agent <cmd>] # override agent command
uwf thread show <thread-id> # show thread head pointer
uwf thread list # list active threads
[--all] # include archived threads
uwf thread kill <thread-id> # terminate and archive a thread
uwf thread steps <thread-id> # list all steps in a thread
uwf thread read <thread-id> # render thread context as markdown
[--quota <chars>] # max output characters (default 32000)
[--before <step-hash>] # load steps before this hash (exclusive)
[--start] # include start step in output
uwf thread fork <step-hash> # fork a thread from a specific step
uwf thread step-details <step-hash> # dump full detail node of a step as YAML
\`\`\`
## CAS Commands
\`\`\`
uwf cas get <hash> # read a CAS node (type + payload)
[--timestamp] # include timestamp in output
uwf cas put <type-hash> <data> # store a node, print its hash
# <data>: JSON file path or inline JSON string
uwf cas has <hash> # check if a hash exists
uwf cas refs <hash> # list direct CAS references from a node
uwf cas walk <hash> # recursive traversal from a node
uwf cas reindex # rebuild type index from all CAS nodes
uwf cas schema list # list all registered schemas
uwf cas schema get <hash> # show a schema by its type hash
\`\`\`
## Global Options
\`\`\`
uwf --format <fmt> # output format: json (default) or yaml
uwf -V, --version # print version
\`\`\`
## Key Concepts
- **Workflow**: YAML definition with roles, conditions, and a routing graph; stored as a CAS node identified by its XXH64 hash.
- **Thread**: A single workflow execution (ULID). State is an immutable CAS chain; active threads are indexed in \`threads.yaml\`.
- **Step**: One moderator→agent→extract cycle. Run \`uwf thread step\` repeatedly until \`$END\`.
- **CAS**: Content-Addressed Storage — all nodes are immutable and identified by hash.
- **Role**: Named actor with a system prompt and JSON Schema output; the moderator routes between roles.
`;
}
@@ -18,13 +18,16 @@ describe("buildRolePrompt", () => {
expect(result).toContain("## Capabilities");
expect(result).toContain("- cursor-agent");
expect(result).toContain("- file-edit");
expect(result).toContain("## Prepare");
expect(result).toContain("uwf skill cli");
expect(result).toContain("cursor-agent, file-edit");
expect(result).toContain("## Procedure");
expect(result).toContain("Implement the feature.");
expect(result).toContain("## Output");
expect(result).toContain("Summarize changes.");
});
test("empty fields are omitted", () => {
test("empty fields are omitted but Prepare is always present", () => {
const role: RoleDefinition = {
description: "A reviewer",
goal: "You are a code reviewer.",
@@ -35,12 +38,14 @@ describe("buildRolePrompt", () => {
};
const result = buildRolePrompt(role);
expect(result).toContain("## Goal");
expect(result).toContain("## Prepare");
expect(result).toContain("uwf skill cli");
expect(result).toContain("## Procedure");
expect(result).not.toContain("## Capabilities");
expect(result).not.toContain("## Output");
});
test("all empty returns empty string", () => {
test("all empty still includes Prepare section", () => {
const role: RoleDefinition = {
description: "Minimal",
goal: "",
@@ -50,7 +55,12 @@ describe("buildRolePrompt", () => {
meta: "placeholder00000" as string,
};
const result = buildRolePrompt(role);
expect(result).toBe("");
expect(result).toContain("## Prepare");
expect(result).toContain("uwf skill cli");
expect(result).not.toContain("## Goal");
expect(result).not.toContain("## Capabilities");
expect(result).not.toContain("## Procedure");
expect(result).not.toContain("## Output");
});
test("capabilities rendered as bullet list", () => {
@@ -3,8 +3,12 @@ import type { RoleDefinition } from "@uncaged/workflow-protocol";
/**
* Build the role prompt from a RoleDefinition.
*
* Assembles structured sections: Goal, Capabilities, Procedure, Output.
* Assembles structured sections: Goal, Capabilities, Prepare, Procedure, Output.
* Empty strings and empty arrays are omitted from the output.
*
* The Prepare section always instructs the agent to run `uwf skill cli` to load
* workflow knowledge, plus renders the capabilities array as keyword hints for
* implicit skill loading.
*/
export function buildRolePrompt(role: RoleDefinition): string {
const sections: string[] = [];
@@ -18,6 +22,22 @@ export function buildRolePrompt(role: RoleDefinition): string {
sections.push(`## Capabilities\n\n${list}`);
}
const prepareLines: string[] = [
"Run the following command to load workflow CLI knowledge before starting work:",
"",
"```",
"uwf skill cli",
"```",
];
if (role.capabilities.length > 0) {
const keywords = role.capabilities.join(", ");
prepareLines.push(
"",
`You have the following capabilities: ${keywords}. Load relevant skills matching these keywords before starting work.`,
);
}
sections.push(`## Prepare\n\n${prepareLines.join("\n")}`);
if (role.procedure !== "") {
sections.push(`## Procedure\n\n${role.procedure}`);
}