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:
@@ -14,6 +14,7 @@ import {
|
|||||||
cmdCasWalk,
|
cmdCasWalk,
|
||||||
} from "./commands/cas.js";
|
} from "./commands/cas.js";
|
||||||
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
|
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
|
||||||
|
import { cmdSkillCli } from "./commands/skill.js";
|
||||||
import {
|
import {
|
||||||
cmdThreadFork,
|
cmdThreadFork,
|
||||||
cmdThreadKill,
|
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
|
program
|
||||||
.command("setup")
|
.command("setup")
|
||||||
.description("Configure provider, model, and agent")
|
.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("## Capabilities");
|
||||||
expect(result).toContain("- cursor-agent");
|
expect(result).toContain("- cursor-agent");
|
||||||
expect(result).toContain("- file-edit");
|
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("## Procedure");
|
||||||
expect(result).toContain("Implement the feature.");
|
expect(result).toContain("Implement the feature.");
|
||||||
expect(result).toContain("## Output");
|
expect(result).toContain("## Output");
|
||||||
expect(result).toContain("Summarize changes.");
|
expect(result).toContain("Summarize changes.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("empty fields are omitted", () => {
|
test("empty fields are omitted but Prepare is always present", () => {
|
||||||
const role: RoleDefinition = {
|
const role: RoleDefinition = {
|
||||||
description: "A reviewer",
|
description: "A reviewer",
|
||||||
goal: "You are a code reviewer.",
|
goal: "You are a code reviewer.",
|
||||||
@@ -35,12 +38,14 @@ describe("buildRolePrompt", () => {
|
|||||||
};
|
};
|
||||||
const result = buildRolePrompt(role);
|
const result = buildRolePrompt(role);
|
||||||
expect(result).toContain("## Goal");
|
expect(result).toContain("## Goal");
|
||||||
|
expect(result).toContain("## Prepare");
|
||||||
|
expect(result).toContain("uwf skill cli");
|
||||||
expect(result).toContain("## Procedure");
|
expect(result).toContain("## Procedure");
|
||||||
expect(result).not.toContain("## Capabilities");
|
expect(result).not.toContain("## Capabilities");
|
||||||
expect(result).not.toContain("## Output");
|
expect(result).not.toContain("## Output");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all empty returns empty string", () => {
|
test("all empty still includes Prepare section", () => {
|
||||||
const role: RoleDefinition = {
|
const role: RoleDefinition = {
|
||||||
description: "Minimal",
|
description: "Minimal",
|
||||||
goal: "",
|
goal: "",
|
||||||
@@ -50,7 +55,12 @@ describe("buildRolePrompt", () => {
|
|||||||
meta: "placeholder00000" as string,
|
meta: "placeholder00000" as string,
|
||||||
};
|
};
|
||||||
const result = buildRolePrompt(role);
|
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", () => {
|
test("capabilities rendered as bullet list", () => {
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
|||||||
/**
|
/**
|
||||||
* Build the role prompt from a RoleDefinition.
|
* 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.
|
* 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 {
|
export function buildRolePrompt(role: RoleDefinition): string {
|
||||||
const sections: string[] = [];
|
const sections: string[] = [];
|
||||||
@@ -18,6 +22,22 @@ export function buildRolePrompt(role: RoleDefinition): string {
|
|||||||
sections.push(`## Capabilities\n\n${list}`);
|
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 !== "") {
|
if (role.procedure !== "") {
|
||||||
sections.push(`## Procedure\n\n${role.procedure}`);
|
sections.push(`## Procedure\n\n${role.procedure}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user