Compare commits

...

5 Commits

Author SHA1 Message Date
xiaoju 485bfcb0b6 chore: remove unused build scripts
All packages are pure Bun/TS — no build step needed.
The build scripts were all placeholder `echo 'TODO'` anyway.
2026-05-08 00:46:35 +00:00
xiaoju 7d3954097d docs: fix deprecated CLI commands in README
- workflow add (was: add)
- workflow list (was: list)
- thread list (was: threads)
- thread show (was: thread)

小橘 🍊
2026-05-07 16:15:29 +00:00
xiaoju 4a925b98af docs: create README.md, update architecture.md for current structure
- Create root README.md with project intro, concepts, packages, quickstart
- Remove workflow-role-* references from docs/architecture.md
- Roles now live inside template packages (src/roles/)
- Clean up untracked dist/packages/workflow-role-* remnants

Fixes #88
2026-05-07 16:10:37 +00:00
xingyue bfea771a52 Merge pull request 'fix(cli): improve usage format + fix skill index title' (#86) from fix/85-usage-format into main 2026-05-07 15:33:27 +00:00
xiaoju 5e411a1f19 fix(cli): improve usage output format + fix skill index title
- Usage: grouped sections with titles, aligned descriptions
- Header: 'uncaged-workflow — workflow engine CLI'
- Footer: 'Use <command> --help for subcommand details.'
- Fix skill index title: 'uncaged-workflow skill' (was: help --skill)
- 242 tests pass

Closes #85

小橘 🍊
2026-05-07 15:31:48 +00:00
15 changed files with 170 additions and 27 deletions
+71
View File
@@ -0,0 +1,71 @@
# @uncaged/workflow
A workflow engine that executes single-file ESM bundles. Each workflow is a self-contained `.esm.js` file identified by its XXH64 hash (Crockford Base32).
## Core Concepts
| Concept | Description |
|---------|-------------|
| **Workflow** | A single-file ESM module exporting `run` (workflow function) and `descriptor` (metadata). Identified by its XXH64 hash. |
| **Bundle** | The physical `.esm.js` file stored in `~/.uncaged/workflow/bundles/`. |
| **Thread** | A single execution of a workflow, identified by a ULID. Persisted as `.data.jsonl` + `.info.jsonl`. |
| **Role** | A named actor within a workflow. Each role produces output with typed `meta`. Roles live inside template packages (`src/roles/`). |
| **Registry** | `workflow.yaml` — maps workflow names to current/historical bundle hashes. |
| **CAS** | Content-Addressed Storage — bundles are immutable and addressed by hash. |
## Monorepo Packages
```
packages/
workflow/ # @uncaged/workflow — core lib (types, engine, hash, ULID, registry)
cli-workflow/ # @uncaged/cli-workflow — CLI (`uncaged-workflow` command)
workflow-template-develop/ # @uncaged/workflow-template-develop — develop workflow template (includes roles)
workflow-template-solve-issue/ # @uncaged/workflow-template-solve-issue — solve-issue workflow template (includes roles)
workflow-agent-hermes/ # @uncaged/workflow-agent-hermes — Hermes agent adapter
workflow-agent-cursor/ # @uncaged/workflow-agent-cursor — Cursor agent adapter
workflow-agent-llm/ # @uncaged/workflow-agent-llm — LLM agent adapter
workflow-util-agent/ # @uncaged/workflow-util-agent — agent utilities (buildAgentPrompt, spawnCli)
```
Managed with **bun workspace** using the `workspace:*` protocol.
## Quick Start
```bash
# Install dependencies
bun install
# Build all packages
bun run build
# Register a workflow bundle
uncaged-workflow workflow add solve-issue dist/packages/workflow-template-solve-issue/solve-issue.esm.js
# Run a workflow
uncaged-workflow run solve-issue --prompt "Fix bug #42"
```
## CLI Usage
```bash
uncaged-workflow help # Show all commands
uncaged-workflow workflow list # List registered workflows
uncaged-workflow run <name> # Start a workflow thread
uncaged-workflow thread list # List all threads
uncaged-workflow thread show <id> # Inspect a thread
uncaged-workflow skill # Agent-consumable reference docs
```
See `uncaged-workflow help` for the full command reference.
## Development
```bash
bun run check # Biome lint + format check
bun run format # Auto-format with Biome
bun test # Run tests
```
## Architecture
See [docs/architecture.md](docs/architecture.md) for the full design — three-phase engine loop, bundle contract, storage layout, and design decisions.
+2 -5
View File
@@ -17,11 +17,8 @@ A workflow engine that executes single-file ESM bundles. Each workflow is a self
| `workflow-agent-cursor` | `@uncaged/workflow-agent-cursor` | Cursor CLI agent (extracts workspace from ctx) | | `workflow-agent-cursor` | `@uncaged/workflow-agent-cursor` | Cursor CLI agent (extracts workspace from ctx) |
| `workflow-agent-hermes` | `@uncaged/workflow-agent-hermes` | Hermes CLI agent | | `workflow-agent-hermes` | `@uncaged/workflow-agent-hermes` | Hermes CLI agent |
| `workflow-agent-llm` | `@uncaged/workflow-agent-llm` | OpenAI-compatible LLM agent | | `workflow-agent-llm` | `@uncaged/workflow-agent-llm` | OpenAI-compatible LLM agent |
| `workflow-role-planner` | `@uncaged/workflow-role-planner` | Pure data: phased planning prompt + schema | | `workflow-template-develop` | `@uncaged/workflow-template-develop` | Develop workflow template (roles in `src/roles/`) |
| `workflow-role-coder` | `@uncaged/workflow-role-coder` | Pure data: coding prompt + schema | | `workflow-template-solve-issue` | `@uncaged/workflow-template-solve-issue` | Solve-issue workflow template (roles in `src/roles/`) |
| `workflow-role-reviewer` | `@uncaged/workflow-role-reviewer` | Pure data: code review prompt + schema |
| `workflow-role-committer` | `@uncaged/workflow-role-committer` | Pure data: git commit prompt + schema |
| `workflow-template-solve-issue` | `@uncaged/workflow-template-solve-issue` | Composes roles + moderator into a complete workflow |
| `workflow-util-agent` | `@uncaged/workflow-util-agent` | `buildAgentPrompt` + `spawnCli` utilities | | `workflow-util-agent` | `@uncaged/workflow-util-agent` | `buildAgentPrompt` + `spawnCli` utilities |
Monorepo with **bun workspace**, `workspace:*` protocol. Monorepo with **bun workspace**, `workspace:*` protocol.
-1
View File
@@ -6,7 +6,6 @@
"examples" "examples"
], ],
"scripts": { "scripts": {
"build": "bun run --filter '*' build",
"check": "bunx tsc --build && biome check .", "check": "bunx tsc --build && biome check .",
"typecheck": "bunx tsc --build", "typecheck": "bunx tsc --build",
"format": "biome format --write .", "format": "biome format --write .",
+32 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { runCli } from "../src/cli-dispatch.js"; import { formatCliUsage, runCli } from "../src/cli-dispatch.js";
import { import {
formatSkillDoc, formatSkillDoc,
formatSkillIndex, formatSkillIndex,
@@ -90,6 +90,8 @@ describe("getSkillTopics", () => {
describe("formatSkillIndex", () => { describe("formatSkillIndex", () => {
test("lists all topics", () => { test("lists all topics", () => {
const idx = formatSkillIndex(); const idx = formatSkillIndex();
expect(idx).toContain("# uncaged-workflow skill");
expect(idx).not.toContain("# uncaged-workflow help --skill");
expect(idx).toContain("cli"); expect(idx).toContain("cli");
expect(idx).toContain("develop"); expect(idx).toContain("develop");
expect(idx).toContain("author"); expect(idx).toContain("author");
@@ -97,6 +99,35 @@ describe("formatSkillIndex", () => {
}); });
}); });
describe("formatCliUsage", () => {
test("has tagline, grouped sections, help hint, and env vars", () => {
const u = formatCliUsage();
expect(u.startsWith("uncaged-workflow — workflow engine CLI")).toBe(true);
expect(u).toContain("Workflow registry:");
expect(u).toContain("Thread execution:");
expect(u).toContain("Content-addressable storage:");
expect(u).toContain("Development:");
expect(u).toContain("Shortcuts:");
expect(u).toContain("Reference:");
expect(u).toContain("skill [topic]");
expect(u).toContain("Agent-consumable docs");
expect(u).toContain("Use <command> --help for subcommand details.");
expect(u).toContain("Environment variables:");
expect(u).toContain("WORKFLOW_STORAGE_ROOT");
expect(u).toContain("UNCAGED_WORKFLOW_STORAGE_ROOT");
});
test("lists commands from registry with descriptions", () => {
const u = formatCliUsage();
expect(u).toContain("workflow add");
expect(u).toContain("Register a workflow bundle in the registry");
expect(u).toContain("thread run");
expect(u).toContain("Start a new thread executing a workflow");
expect(u).toContain("cas gc");
expect(u).toContain("Garbage-collect unreferenced CAS entries");
});
});
describe("formatSkillTopic('cli') — legacy formatSkillDoc", () => { describe("formatSkillTopic('cli') — legacy formatSkillDoc", () => {
const doc = formatSkillDoc(); const doc = formatSkillDoc();
@@ -129,8 +129,9 @@ describe("init workspace", () => {
test("usage lists init subcommands", () => { test("usage lists init subcommands", () => {
const u = formatCliUsage(); const u = formatCliUsage();
expect(u).toContain("uncaged-workflow init workspace <name>"); expect(u).toContain("init workspace <name>");
expect(u).toContain("uncaged-workflow init template <name>"); expect(u).toContain("init template <name>");
expect(u).toContain("Development:");
}); });
test("runCli rejects unknown init subcommand", async () => { test("runCli rejects unknown init subcommand", async () => {
-1
View File
@@ -10,7 +10,6 @@
"yaml": "^2.8.4" "yaml": "^2.8.4"
}, },
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
} }
} }
+61 -9
View File
@@ -3,7 +3,12 @@ import { cmdAdd, formatAddSuccess, parseAddArgv } from "./cmd-add.js";
import { cmdCasGet, cmdCasList, cmdCasPut, cmdCasRm } from "./cmd-cas.js"; import { cmdCasGet, cmdCasList, cmdCasPut, cmdCasRm } from "./cmd-cas.js";
import { cmdFork, parseForkArgv } from "./cmd-fork.js"; import { cmdFork, parseForkArgv } from "./cmd-fork.js";
import { cmdGc } from "./cmd-gc.js"; import { cmdGc } from "./cmd-gc.js";
import { formatSkillDoc, formatSkillIndex, formatSkillTopic } from "./cmd-help.js"; import {
formatSkillDoc,
formatSkillIndex,
formatSkillTopic,
getSkillTopics,
} from "./cmd-help.js";
import { cmdHistory } from "./cmd-history.js"; import { cmdHistory } from "./cmd-history.js";
import { cmdInitTemplate, cmdInitWorkspace } from "./cmd-init.js"; import { cmdInitTemplate, cmdInitWorkspace } from "./cmd-init.js";
import { cmdKill } from "./cmd-kill.js"; import { cmdKill } from "./cmd-kill.js";
@@ -525,21 +530,68 @@ export function getCommandRegistry(): ReadonlyArray<CommandGroup> {
// ── Auto-generated CLI usage ─────────────────────────────────────────── // ── Auto-generated CLI usage ───────────────────────────────────────────
const USAGE_SECTION_BY_GROUP: Record<string, string> = {
workflow: "Workflow registry:",
thread: "Thread execution:",
cas: "Content-addressable storage:",
init: "Development:",
};
function formatUsageCommandLines(
rows: ReadonlyArray<{ prefix: string; description: string }>,
): string[] {
const maxPrefix = rows.reduce((max, row) => Math.max(max, row.prefix.length), 0);
const gap = 2;
return rows.map((row) => {
const pad = " ".repeat(maxPrefix - row.prefix.length + gap);
return ` ${row.prefix}${pad}${row.description}`;
});
}
export function formatCliUsage(): string { export function formatCliUsage(): string {
const groups = getCommandRegistry(); const groups = getCommandRegistry();
const lines: string[] = ["Usage:"]; const lines: string[] = ["uncaged-workflow — workflow engine CLI", ""];
for (const group of groups) { for (const group of groups) {
for (const cmd of group.commands) { const sectionTitle = USAGE_SECTION_BY_GROUP[group.name];
const args = cmd.args ? ` ${cmd.args}` : ""; if (sectionTitle === undefined) {
lines.push(` uncaged-workflow ${group.name} ${cmd.name}${args}`); throw new Error(`BUG: missing usage section title for group "${group.name}"`);
} }
lines.push(sectionTitle);
const rows = group.commands.map((cmd) => {
const args = cmd.args ? ` ${cmd.args}` : "";
return {
prefix: `${group.name} ${cmd.name}${args}`,
description: cmd.description,
};
});
lines.push(...formatUsageCommandLines(rows));
lines.push(""); lines.push("");
} }
lines.push(" uncaged-workflow run <name> [...] (shortcut for thread run)");
lines.push(" uncaged-workflow live <thread-id> [...] (shortcut for thread live)"); lines.push("Shortcuts:");
lines.push(
...formatUsageCommandLines([
{ prefix: "run <name> [...]", description: "→ thread run" },
{ prefix: "live <id> [...]", description: "→ thread live" },
]),
);
lines.push(""); lines.push("");
lines.push(" uncaged-workflow skill [topic] agent-consumable reference docs");
lines.push(" uncaged-workflow help show this usage"); lines.push("Reference:");
const skillTopicNames = getSkillTopics()
.map((t) => t.name)
.join(", ");
lines.push(
...formatUsageCommandLines([
{
prefix: "skill [topic]",
description: `Agent-consumable docs (${skillTopicNames})`,
},
]),
);
lines.push("");
lines.push("Use <command> --help for subcommand details.");
lines.push(""); lines.push("");
lines.push("Environment variables:"); lines.push("Environment variables:");
lines.push( lines.push(
+1 -1
View File
@@ -34,7 +34,7 @@ export function formatSkillTopic(topic: string): string | null {
export function formatSkillIndex(): string { export function formatSkillIndex(): string {
const rows = SKILL_TOPICS.map((t) => `| \`${t.name}\` | ${t.description} |`); const rows = SKILL_TOPICS.map((t) => `| \`${t.name}\` | ${t.description} |`);
return `# uncaged-workflow help --skill return `# uncaged-workflow skill
Available topics: Available topics:
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
-1
View File
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
@@ -11,7 +11,6 @@
} }
}, },
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
-1
View File
@@ -5,7 +5,6 @@
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"scripts": { "scripts": {
"build": "echo 'TODO'",
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {