Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8456a8337b | |||
| 9c8b98a551 | |||
| c3272be760 | |||
| c44b773a86 | |||
| 2776f8e419 | |||
| 7b0e256c13 | |||
| c663ba9e9c | |||
| 71b413f20c |
@@ -0,0 +1,54 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import { getDefaultWorkflowStorageRoot } from "@uncaged/workflow";
|
||||
import { resolveWorkflowStorageRoot } from "../src/storage-env.js";
|
||||
|
||||
describe("resolveWorkflowStorageRoot", () => {
|
||||
let savedInternal: string | undefined;
|
||||
let savedUser: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
savedInternal = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
||||
savedUser = process.env.WORKFLOW_STORAGE_ROOT;
|
||||
delete process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
||||
delete process.env.WORKFLOW_STORAGE_ROOT;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (savedInternal === undefined) {
|
||||
delete process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
||||
} else {
|
||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = savedInternal;
|
||||
}
|
||||
if (savedUser === undefined) {
|
||||
delete process.env.WORKFLOW_STORAGE_ROOT;
|
||||
} else {
|
||||
process.env.WORKFLOW_STORAGE_ROOT = savedUser;
|
||||
}
|
||||
});
|
||||
|
||||
test("returns default when no env vars are set", () => {
|
||||
expect(resolveWorkflowStorageRoot()).toBe(getDefaultWorkflowStorageRoot());
|
||||
});
|
||||
|
||||
test("WORKFLOW_STORAGE_ROOT overrides default", () => {
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/custom-storage";
|
||||
expect(resolveWorkflowStorageRoot()).toBe("/tmp/custom-storage");
|
||||
});
|
||||
|
||||
test("UNCAGED_WORKFLOW_STORAGE_ROOT takes priority over WORKFLOW_STORAGE_ROOT", () => {
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/user-path";
|
||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "/tmp/internal-path";
|
||||
expect(resolveWorkflowStorageRoot()).toBe("/tmp/internal-path");
|
||||
});
|
||||
|
||||
test("ignores empty WORKFLOW_STORAGE_ROOT", () => {
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "";
|
||||
expect(resolveWorkflowStorageRoot()).toBe(getDefaultWorkflowStorageRoot());
|
||||
});
|
||||
|
||||
test("ignores empty UNCAGED_WORKFLOW_STORAGE_ROOT and falls through to WORKFLOW_STORAGE_ROOT", () => {
|
||||
process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "";
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/user-fallback";
|
||||
expect(resolveWorkflowStorageRoot()).toBe("/tmp/user-fallback");
|
||||
});
|
||||
});
|
||||
@@ -21,80 +21,49 @@ import { cmdThreads } from "./cmd-threads.js";
|
||||
import { parseLiveArgv } from "./live-argv.js";
|
||||
import { parseRunArgv } from "./run-argv.js";
|
||||
|
||||
export function formatCliUsage(): string {
|
||||
return [
|
||||
"Usage:",
|
||||
" uncaged-workflow workflow add <name> <file.esm.js> [--types <path>]",
|
||||
" uncaged-workflow workflow list",
|
||||
" uncaged-workflow workflow show <name>",
|
||||
" uncaged-workflow workflow rm <name>",
|
||||
" uncaged-workflow workflow history <name>",
|
||||
" uncaged-workflow workflow rollback <name> [hash]",
|
||||
"",
|
||||
" uncaged-workflow thread run <name> [--prompt <text>] [--max-rounds N]",
|
||||
" uncaged-workflow thread list [name]",
|
||||
" uncaged-workflow thread show <id>",
|
||||
" uncaged-workflow thread rm <id>",
|
||||
" uncaged-workflow thread ps",
|
||||
" uncaged-workflow thread kill <thread-id>",
|
||||
" uncaged-workflow thread live <thread-id> [--debug] [--role <name>]",
|
||||
" uncaged-workflow thread live --latest [--debug] [--role <name>]",
|
||||
" uncaged-workflow thread pause <thread-id>",
|
||||
" uncaged-workflow thread resume <thread-id>",
|
||||
" uncaged-workflow thread fork <thread-id> [--from-role <role>]",
|
||||
"",
|
||||
" uncaged-workflow cas get <thread-id> <hash>",
|
||||
" uncaged-workflow cas put <thread-id> <content>",
|
||||
" uncaged-workflow cas list <thread-id>",
|
||||
" uncaged-workflow cas rm <thread-id> <hash>",
|
||||
" uncaged-workflow cas gc",
|
||||
"",
|
||||
" uncaged-workflow init workspace <name>",
|
||||
" uncaged-workflow init template <name>",
|
||||
"",
|
||||
" uncaged-workflow run <name> [...] (shortcut for thread run)",
|
||||
" uncaged-workflow live <thread-id> [...] (shortcut for thread live)",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function printDeprecation(oldCmd: string, newCmd: string): void {
|
||||
printCliWarn(`⚠ "${oldCmd}" is deprecated, use "${newCmd}" instead`);
|
||||
}
|
||||
|
||||
type DispatchFn = (storageRoot: string, argv: string[]) => Promise<number>;
|
||||
|
||||
type CommandEntry = {
|
||||
handler: DispatchFn;
|
||||
args: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type CommandGroup = {
|
||||
name: string;
|
||||
commands: ReadonlyArray<{ name: string; args: string; description: string }>;
|
||||
};
|
||||
|
||||
// ── Individual dispatch functions ──────────────────────────────────────
|
||||
|
||||
async function dispatchInit(_storageRoot: string, argv: string[]): Promise<number> {
|
||||
const sub = argv[0];
|
||||
const name = argv[1];
|
||||
if (sub === undefined || name === undefined || argv.length > 2) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: init requires workspace|template <name>`);
|
||||
async function dispatchInitWorkspace(_storageRoot: string, argv: string[]): Promise<number> {
|
||||
const name = argv[0];
|
||||
if (name === undefined || argv.length > 1) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: init workspace requires <name>`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sub === "workspace") {
|
||||
const result = await cmdInitWorkspace(process.cwd(), name);
|
||||
if (!result.ok) {
|
||||
printCliError(result.error);
|
||||
return 1;
|
||||
}
|
||||
printCliLine(`initialized workflow workspace at ${result.value.rootPath}`);
|
||||
return 0;
|
||||
const result = await cmdInitWorkspace(process.cwd(), name);
|
||||
if (!result.ok) {
|
||||
printCliError(result.error);
|
||||
return 1;
|
||||
}
|
||||
printCliLine(`initialized workflow workspace at ${result.value.rootPath}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sub === "template") {
|
||||
const result = await cmdInitTemplate(process.cwd(), name);
|
||||
if (!result.ok) {
|
||||
printCliError(result.error);
|
||||
return 1;
|
||||
}
|
||||
printCliLine(`initialized template at ${result.value.templatePath}`);
|
||||
return 0;
|
||||
async function dispatchInitTemplate(_storageRoot: string, argv: string[]): Promise<number> {
|
||||
const name = argv[0];
|
||||
if (name === undefined || argv.length > 1) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: init template requires <name>`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown init subcommand: ${sub}`);
|
||||
return 1;
|
||||
const result = await cmdInitTemplate(process.cwd(), name);
|
||||
if (!result.ok) {
|
||||
printCliError(result.error);
|
||||
return 1;
|
||||
}
|
||||
printCliLine(`initialized template at ${result.value.templatePath}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function dispatchAdd(storageRoot: string, argv: string[]): Promise<number> {
|
||||
@@ -422,49 +391,210 @@ async function dispatchCasRm(storageRoot: string, rest: string[]): Promise<numbe
|
||||
return 0;
|
||||
}
|
||||
|
||||
const CAS_SUBCOMMAND_TABLE: Record<string, DispatchFn> = {
|
||||
get: dispatchCasGet,
|
||||
put: dispatchCasPut,
|
||||
list: dispatchCasList,
|
||||
rm: dispatchCasRm,
|
||||
gc: dispatchGc,
|
||||
// ── Subcommand tables with metadata ────────────────────────────────────
|
||||
|
||||
const WORKFLOW_SUBCOMMAND_TABLE: Record<string, CommandEntry> = {
|
||||
add: {
|
||||
handler: dispatchAdd,
|
||||
args: "<name> <file.esm.js> [--types <path>]",
|
||||
description: "Register a workflow bundle in the registry",
|
||||
},
|
||||
list: { handler: dispatchList, args: "", description: "List all registered workflows" },
|
||||
show: {
|
||||
handler: dispatchShow,
|
||||
args: "<name>",
|
||||
description: "Show details of a registered workflow",
|
||||
},
|
||||
rm: {
|
||||
handler: dispatchRemove,
|
||||
args: "<name>",
|
||||
description: "Remove a workflow from the registry",
|
||||
},
|
||||
history: {
|
||||
handler: dispatchHistory,
|
||||
args: "<name>",
|
||||
description: "Show version history of a workflow",
|
||||
},
|
||||
rollback: {
|
||||
handler: dispatchRollback,
|
||||
args: "<name> [hash]",
|
||||
description: "Rollback a workflow to a previous version",
|
||||
},
|
||||
};
|
||||
|
||||
async function dispatchCas(storageRoot: string, argv: string[]): Promise<number> {
|
||||
const sub = argv[0];
|
||||
if (sub === undefined) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown cas subcommand: (none)`);
|
||||
return 1;
|
||||
}
|
||||
const handler = CAS_SUBCOMMAND_TABLE[sub];
|
||||
if (handler === undefined) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown cas subcommand: ${sub}`);
|
||||
return 1;
|
||||
}
|
||||
return handler(storageRoot, argv.slice(1));
|
||||
const THREAD_SUBCOMMAND_TABLE: Record<string, CommandEntry> = {
|
||||
run: {
|
||||
handler: dispatchRun,
|
||||
args: "<name> [--prompt <text>] [--max-rounds N]",
|
||||
description: "Start a new thread executing a workflow",
|
||||
},
|
||||
list: {
|
||||
handler: dispatchThreadList,
|
||||
args: "[name]",
|
||||
description: "List threads, optionally filtered by workflow name",
|
||||
},
|
||||
show: { handler: dispatchThreadShow, args: "<id>", description: "Show thread details and state" },
|
||||
rm: { handler: dispatchThreadRm, args: "<id>", description: "Remove a thread" },
|
||||
fork: {
|
||||
handler: dispatchFork,
|
||||
args: "<thread-id> [--from-role <role>]",
|
||||
description: "Fork a thread, optionally from a specific role",
|
||||
},
|
||||
ps: { handler: dispatchPs, args: "", description: "List running threads" },
|
||||
kill: { handler: dispatchKill, args: "<thread-id>", description: "Kill a running thread" },
|
||||
live: {
|
||||
handler: dispatchLive,
|
||||
args: "<thread-id> [--debug] [--role <name>]",
|
||||
description: "Attach to a thread and stream output live",
|
||||
},
|
||||
pause: { handler: dispatchPause, args: "<thread-id>", description: "Pause a running thread" },
|
||||
resume: { handler: dispatchResume, args: "<thread-id>", description: "Resume a paused thread" },
|
||||
};
|
||||
|
||||
const CAS_SUBCOMMAND_TABLE: Record<string, CommandEntry> = {
|
||||
get: {
|
||||
handler: dispatchCasGet,
|
||||
args: "<thread-id> <hash>",
|
||||
description: "Retrieve content by hash from a thread's CAS",
|
||||
},
|
||||
put: {
|
||||
handler: dispatchCasPut,
|
||||
args: "<thread-id> <content>",
|
||||
description: "Store content in a thread's CAS, returns hash",
|
||||
},
|
||||
list: {
|
||||
handler: dispatchCasList,
|
||||
args: "<thread-id>",
|
||||
description: "List all CAS entries for a thread",
|
||||
},
|
||||
rm: { handler: dispatchCasRm, args: "<thread-id> <hash>", description: "Remove a CAS entry" },
|
||||
gc: { handler: dispatchGc, args: "", description: "Garbage-collect unreferenced CAS entries" },
|
||||
};
|
||||
|
||||
const INIT_SUBCOMMAND_TABLE: Record<string, CommandEntry> = {
|
||||
workspace: {
|
||||
handler: dispatchInitWorkspace,
|
||||
args: "<name>",
|
||||
description: "Initialize a new workflow workspace",
|
||||
},
|
||||
template: {
|
||||
handler: dispatchInitTemplate,
|
||||
args: "<name>",
|
||||
description: "Initialize a new workflow template",
|
||||
},
|
||||
};
|
||||
|
||||
// ── Command registry ───────────────────────────────────────────────────
|
||||
|
||||
export function getCommandRegistry(): ReadonlyArray<CommandGroup> {
|
||||
return [
|
||||
{
|
||||
name: "workflow",
|
||||
commands: Object.entries(WORKFLOW_SUBCOMMAND_TABLE).map(([name, e]) => ({
|
||||
name,
|
||||
args: e.args,
|
||||
description: e.description,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "thread",
|
||||
commands: Object.entries(THREAD_SUBCOMMAND_TABLE).map(([name, e]) => ({
|
||||
name,
|
||||
args: e.args,
|
||||
description: e.description,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "cas",
|
||||
commands: Object.entries(CAS_SUBCOMMAND_TABLE).map(([name, e]) => ({
|
||||
name,
|
||||
args: e.args,
|
||||
description: e.description,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "init",
|
||||
commands: Object.entries(INIT_SUBCOMMAND_TABLE).map(([name, e]) => ({
|
||||
name,
|
||||
args: e.args,
|
||||
description: e.description,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ── Workflow subcommand table (Phase 1) ────────────────────────────────
|
||||
// ── Auto-generated CLI usage ───────────────────────────────────────────
|
||||
|
||||
const WORKFLOW_SUBCOMMAND_TABLE: Record<string, DispatchFn> = {
|
||||
add: dispatchAdd,
|
||||
list: dispatchList,
|
||||
show: dispatchShow,
|
||||
rm: dispatchRemove,
|
||||
history: dispatchHistory,
|
||||
rollback: dispatchRollback,
|
||||
};
|
||||
export function formatCliUsage(): string {
|
||||
const groups = getCommandRegistry();
|
||||
const lines: string[] = ["Usage:"];
|
||||
for (const group of groups) {
|
||||
for (const cmd of group.commands) {
|
||||
const args = cmd.args ? ` ${cmd.args}` : "";
|
||||
lines.push(` uncaged-workflow ${group.name} ${cmd.name}${args}`);
|
||||
}
|
||||
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("");
|
||||
lines.push("Environment variables:");
|
||||
lines.push(
|
||||
" WORKFLOW_STORAGE_ROOT Override storage directory (default: ~/.uncaged/workflow)",
|
||||
);
|
||||
lines.push(
|
||||
" UNCAGED_WORKFLOW_STORAGE_ROOT Internal override (takes priority over WORKFLOW_STORAGE_ROOT)",
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
async function dispatchWorkflow(storageRoot: string, argv: string[]): Promise<number> {
|
||||
function printDeprecation(oldCmd: string, newCmd: string): void {
|
||||
printCliWarn(`⚠ "${oldCmd}" is deprecated, use "${newCmd}" instead`);
|
||||
}
|
||||
|
||||
// ── Group dispatchers ──────────────────────────────────────────────────
|
||||
|
||||
function dispatchGroup(
|
||||
tableName: string,
|
||||
table: Record<string, CommandEntry>,
|
||||
storageRoot: string,
|
||||
argv: string[],
|
||||
): Promise<number> | null {
|
||||
const sub = argv[0];
|
||||
if (sub === undefined) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown workflow subcommand: (none)`);
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown ${tableName} subcommand: (none)`);
|
||||
return Promise.resolve(1);
|
||||
}
|
||||
const entry = table[sub];
|
||||
if (entry === undefined) {
|
||||
return null;
|
||||
}
|
||||
return entry.handler(storageRoot, argv.slice(1));
|
||||
}
|
||||
|
||||
async function dispatchInit(storageRoot: string, argv: string[]): Promise<number> {
|
||||
const sub = argv[0];
|
||||
const name = argv[1];
|
||||
if (sub === undefined || name === undefined || argv.length > 2) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: init requires workspace|template <name>`);
|
||||
return 1;
|
||||
}
|
||||
const handler = WORKFLOW_SUBCOMMAND_TABLE[sub];
|
||||
if (handler !== undefined) {
|
||||
return handler(storageRoot, argv.slice(1));
|
||||
|
||||
const entry = INIT_SUBCOMMAND_TABLE[sub];
|
||||
if (entry !== undefined) {
|
||||
return entry.handler(storageRoot, argv.slice(1));
|
||||
}
|
||||
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown init subcommand: ${sub}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function dispatchWorkflow(storageRoot: string, argv: string[]): Promise<number> {
|
||||
const result = dispatchGroup("workflow", WORKFLOW_SUBCOMMAND_TABLE, storageRoot, argv);
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
const sub = argv[0];
|
||||
if (sub === "remove") {
|
||||
printDeprecation("workflow remove", "workflow rm");
|
||||
return dispatchRemove(storageRoot, argv.slice(1));
|
||||
@@ -473,33 +603,24 @@ async function dispatchWorkflow(storageRoot: string, argv: string[]): Promise<nu
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ── Thread subcommand table (Phase 2) ──────────────────────────────────
|
||||
|
||||
const THREAD_SUBCOMMAND_TABLE: Record<string, DispatchFn> = {
|
||||
run: dispatchRun,
|
||||
list: dispatchThreadList,
|
||||
show: dispatchThreadShow,
|
||||
rm: dispatchThreadRm,
|
||||
fork: dispatchFork,
|
||||
ps: dispatchPs,
|
||||
kill: dispatchKill,
|
||||
live: dispatchLive,
|
||||
pause: dispatchPause,
|
||||
resume: dispatchResume,
|
||||
};
|
||||
|
||||
async function dispatchThread(storageRoot: string, argv: string[]): Promise<number> {
|
||||
const result = dispatchGroup("thread", THREAD_SUBCOMMAND_TABLE, storageRoot, argv);
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
const sub = argv[0];
|
||||
if (sub === undefined) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown thread subcommand: (none)`);
|
||||
return 1;
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown thread subcommand: ${sub}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function dispatchCas(storageRoot: string, argv: string[]): Promise<number> {
|
||||
const result = dispatchGroup("cas", CAS_SUBCOMMAND_TABLE, storageRoot, argv);
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
const handler = THREAD_SUBCOMMAND_TABLE[sub];
|
||||
if (handler === undefined) {
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown thread subcommand: ${sub}`);
|
||||
return 1;
|
||||
}
|
||||
return handler(storageRoot, argv.slice(1));
|
||||
const sub = argv[0];
|
||||
printCliError(`${formatCliUsage()}\n\nerror: unknown cas subcommand: ${sub}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ── Help ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import { getCommandRegistry } from "./cli-dispatch.js";
|
||||
|
||||
export function formatSkillDoc(): string {
|
||||
const groups = getCommandRegistry();
|
||||
|
||||
const commandSections: string[] = [];
|
||||
for (const group of groups) {
|
||||
const rows = group.commands.map((cmd) => {
|
||||
const args = cmd.args ? `\`${cmd.args}\`` : "(none)";
|
||||
return `| \`${group.name} ${cmd.name}\` | ${args} | ${cmd.description} |`;
|
||||
});
|
||||
commandSections.push(
|
||||
`### ${group.name}\n\n| Command | Args | Description |\n|---------|------|-------------|\n${rows.join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return `# uncaged-workflow CLI Reference
|
||||
|
||||
## Core Concepts
|
||||
@@ -13,48 +28,7 @@ export function formatSkillDoc(): string {
|
||||
|
||||
## Commands
|
||||
|
||||
### workflow
|
||||
|
||||
| Command | Args | Description |
|
||||
|---------|------|-------------|
|
||||
| \`workflow add\` | \`<name> <file.esm.js> [--types <path>]\` | Register a workflow bundle in the registry |
|
||||
| \`workflow list\` | (none) | List all registered workflows |
|
||||
| \`workflow show\` | \`<name>\` | Show details of a registered workflow |
|
||||
| \`workflow rm\` | \`<name>\` | Remove a workflow from the registry |
|
||||
| \`workflow history\` | \`<name>\` | Show version history of a workflow |
|
||||
| \`workflow rollback\` | \`<name> [hash]\` | Rollback a workflow to a previous version |
|
||||
|
||||
### thread
|
||||
|
||||
| Command | Args | Description |
|
||||
|---------|------|-------------|
|
||||
| \`thread run\` | \`<name> [--prompt <text>] [--max-rounds N]\` | Start a new thread executing a workflow |
|
||||
| \`thread list\` | \`[name]\` | List threads, optionally filtered by workflow name |
|
||||
| \`thread show\` | \`<id>\` | Show thread details and state |
|
||||
| \`thread rm\` | \`<id>\` | Remove a thread |
|
||||
| \`thread fork\` | \`<thread-id> [--from-role <role>]\` | Fork a thread, optionally from a specific role |
|
||||
| \`thread ps\` | (none) | List running threads |
|
||||
| \`thread kill\` | \`<thread-id>\` | Kill a running thread |
|
||||
| \`thread live\` | \`<thread-id> [--debug] [--role <name>]\` or \`--latest [--debug] [--role <name>]\` | Attach to a thread and stream output live |
|
||||
| \`thread pause\` | \`<thread-id>\` | Pause a running thread |
|
||||
| \`thread resume\` | \`<thread-id>\` | Resume a paused thread |
|
||||
|
||||
### cas
|
||||
|
||||
| Command | Args | Description |
|
||||
|---------|------|-------------|
|
||||
| \`cas get\` | \`<thread-id> <hash>\` | Retrieve content by hash from a thread's CAS |
|
||||
| \`cas put\` | \`<thread-id> <content>\` | Store content in a thread's CAS, returns hash |
|
||||
| \`cas list\` | \`<thread-id>\` | List all CAS entries for a thread |
|
||||
| \`cas rm\` | \`<thread-id> <hash>\` | Remove a CAS entry |
|
||||
| \`cas gc\` | (none) | Garbage-collect unreferenced CAS entries |
|
||||
|
||||
### init
|
||||
|
||||
| Command | Args | Description |
|
||||
|---------|------|-------------|
|
||||
| \`init workspace\` | \`<name>\` | Initialize a new workflow workspace |
|
||||
| \`init template\` | \`<name>\` | Initialize a new workflow template |
|
||||
${commandSections.join("\n\n")}
|
||||
|
||||
### Top-level shortcuts
|
||||
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import { getDefaultWorkflowStorageRoot } from "@uncaged/workflow";
|
||||
|
||||
/** Resolve storage root, honoring `UNCAGED_WORKFLOW_STORAGE_ROOT` for tests/tools. */
|
||||
/**
|
||||
* Resolve storage root with env var override support.
|
||||
*
|
||||
* Priority (highest first):
|
||||
* 1. `UNCAGED_WORKFLOW_STORAGE_ROOT` — internal/test override
|
||||
* 2. `WORKFLOW_STORAGE_ROOT` — user-facing override
|
||||
* 3. Default (`~/.uncaged/workflow`)
|
||||
*/
|
||||
export function resolveWorkflowStorageRoot(): string {
|
||||
const override = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
||||
if (override !== undefined && override !== "") {
|
||||
return override;
|
||||
const internal = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT;
|
||||
if (internal !== undefined && internal !== "") {
|
||||
return internal;
|
||||
}
|
||||
const userOverride = process.env.WORKFLOW_STORAGE_ROOT;
|
||||
if (userOverride !== undefined && userOverride !== "") {
|
||||
return userOverride;
|
||||
}
|
||||
return getDefaultWorkflowStorageRoot();
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-coder",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "echo no tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { type CoderMeta, coderMetaSchema, coderRole } from "./coder.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-committer",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { type CommitterMeta, committerMetaSchema, committerRole } from "./committer.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-planner",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "echo no tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export {
|
||||
type PlannerMeta,
|
||||
phaseSchema,
|
||||
plannerMetaSchema,
|
||||
plannerRole,
|
||||
} from "./planner.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-preparer",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "echo no tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-reviewer",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { type ReviewerMeta, reviewerMetaSchema, reviewerRole } from "./reviewer.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-submitter",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { type SubmitterMeta, submitterMetaSchema, submitterRole } from "./submitter.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@uncaged/workflow-role-tester",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "echo 'TODO'",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { type TesterMeta, testerMetaSchema, testerRole } from "./tester.js";
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { committerMetaSchema, committerRole } from "../src/committer.js";
|
||||
import { committerMetaSchema, committerRole } from "../src/roles/committer.js";
|
||||
|
||||
describe("committerRole", () => {
|
||||
test("committed sample validates against schema", () => {
|
||||
@@ -6,12 +6,9 @@ import {
|
||||
START,
|
||||
validateWorkflowDescriptor,
|
||||
} from "@uncaged/workflow";
|
||||
|
||||
import type { CommitterMeta } from "@uncaged/workflow-role-committer";
|
||||
import type { PlannerMeta } from "@uncaged/workflow-role-planner";
|
||||
|
||||
import { buildDevelopDescriptor } from "../src/descriptor.js";
|
||||
import { developModerator } from "../src/index.js";
|
||||
import type { CommitterMeta, PlannerMeta } from "../src/roles/index.js";
|
||||
import type { DevelopMeta } from "../src/roles.js";
|
||||
|
||||
const DEFAULT_PHASES: PlannerMeta["phases"] = [
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { reviewerMetaSchema, reviewerRole } from "../src/reviewer.js";
|
||||
import { reviewerMetaSchema, reviewerRole } from "../src/roles/reviewer.js";
|
||||
|
||||
describe("reviewerRole", () => {
|
||||
test("approved sample validates against schema", () => {
|
||||
@@ -10,10 +10,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"@uncaged/workflow-role-coder": "workspace:*",
|
||||
"@uncaged/workflow-role-committer": "workspace:*",
|
||||
"@uncaged/workflow-role-planner": "workspace:*",
|
||||
"@uncaged/workflow-role-reviewer": "workspace:*",
|
||||
"@uncaged/workflow-role-tester": "workspace:*"
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,34 +10,26 @@ import {
|
||||
import { developModerator } from "./moderator.js";
|
||||
import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js";
|
||||
|
||||
export { buildDevelopDescriptor } from "./descriptor.js";
|
||||
export { developModerator } from "./moderator.js";
|
||||
export {
|
||||
type CoderMeta,
|
||||
type CommitterMeta,
|
||||
coderMetaSchema,
|
||||
coderRole,
|
||||
} from "@uncaged/workflow-role-coder";
|
||||
export {
|
||||
type CommitterMeta,
|
||||
committerMetaSchema,
|
||||
committerRole,
|
||||
} from "@uncaged/workflow-role-committer";
|
||||
export {
|
||||
type PlannerMeta,
|
||||
phaseSchema,
|
||||
plannerMetaSchema,
|
||||
plannerRole,
|
||||
} from "@uncaged/workflow-role-planner";
|
||||
export {
|
||||
type ReviewerMeta,
|
||||
reviewerMetaSchema,
|
||||
reviewerRole,
|
||||
} from "@uncaged/workflow-role-reviewer";
|
||||
export {
|
||||
type TesterMeta,
|
||||
testerMetaSchema,
|
||||
testerRole,
|
||||
} from "@uncaged/workflow-role-tester";
|
||||
export { buildDevelopDescriptor } from "./descriptor.js";
|
||||
export { developModerator } from "./moderator.js";
|
||||
} from "./roles/index.js";
|
||||
export {
|
||||
DEVELOP_WORKFLOW_DESCRIPTION,
|
||||
type DevelopMeta,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { RoleDefinition } from "@uncaged/workflow";
|
||||
import { type CoderMeta, coderRole } from "@uncaged/workflow-role-coder";
|
||||
import { type CommitterMeta, committerRole } from "@uncaged/workflow-role-committer";
|
||||
import { type PlannerMeta, plannerRole } from "@uncaged/workflow-role-planner";
|
||||
import { type ReviewerMeta, reviewerRole } from "@uncaged/workflow-role-reviewer";
|
||||
import { type TesterMeta, testerRole } from "@uncaged/workflow-role-tester";
|
||||
import { type CoderMeta, coderRole } from "./roles/coder.js";
|
||||
import { type CommitterMeta, committerRole } from "./roles/committer.js";
|
||||
import { type PlannerMeta, plannerRole } from "./roles/planner.js";
|
||||
import { type ReviewerMeta, reviewerRole } from "./roles/reviewer.js";
|
||||
import { type TesterMeta, testerRole } from "./roles/tester.js";
|
||||
|
||||
export const DEVELOP_WORKFLOW_DESCRIPTION =
|
||||
"Plan phases, implement incrementally, review, verify with tests/build/lint, and commit (planner → coder [repeat per phase] → reviewer → tester → committer).";
|
||||
|
||||
+3
-11
@@ -11,21 +11,13 @@ export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
||||
|
||||
const CODER_SYSTEM = `You are a **coder**. Read the thread for the plan and work on the NEXT incomplete phase only.
|
||||
|
||||
## Finding the current thread ID
|
||||
|
||||
The thread ID is a 26-character Crockford Base32 string (e.g. \`06F03H5V6JTMDST6P3TVH42RWM\`). It appears in the first message of this conversation. If you are unsure, run:
|
||||
|
||||
uncaged-workflow threads
|
||||
|
||||
and use the ID of the active thread.
|
||||
Run \`uncaged-workflow help --skill\` for full CLI reference (thread ID lookup, CAS commands, etc.).
|
||||
|
||||
## Reading phase details
|
||||
|
||||
Each planner phase is identified by a content-hash and a title. To read a phase's full details (name, description, acceptance criteria), run:
|
||||
Each planner phase has a content-hash and title. Read full details with \`uncaged-workflow cas get <THREAD_ID> <HASH>\`.
|
||||
|
||||
uncaged-workflow cas get <THREAD_ID> <HASH>
|
||||
|
||||
Replace \`<THREAD_ID>\` with the actual thread ID and \`<HASH>\` with the phase hash from the plan.
|
||||
The thread ID (26-char Crockford Base32) appears in the first message. If unsure, run \`uncaged-workflow thread list\`.
|
||||
|
||||
## Completing a phase
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export { type CoderMeta, coderMetaSchema, coderRole } from "./coder.js";
|
||||
export { type CommitterMeta, committerMetaSchema, committerRole } from "./committer.js";
|
||||
export {
|
||||
type PlannerMeta,
|
||||
phaseSchema,
|
||||
plannerMetaSchema,
|
||||
plannerRole,
|
||||
} from "./planner.js";
|
||||
export { type ReviewerMeta, reviewerMetaSchema, reviewerRole } from "./reviewer.js";
|
||||
export { type TesterMeta, testerMetaSchema, testerRole } from "./tester.js";
|
||||
+11
-13
@@ -14,27 +14,25 @@ export type PlannerMeta = z.infer<typeof plannerMetaSchema>;
|
||||
|
||||
const PLANNER_SYSTEM = `You are a **planner** for a software task. Break the work into **sequential phases** the coder will execute one at a time.
|
||||
|
||||
## Finding the current thread ID
|
||||
|
||||
The thread ID is a 26-character Crockford Base32 string (e.g. \`06F03H5V6JTMDST6P3TVH42RWM\`). It appears in the first message of this conversation. If you are unsure, run:
|
||||
|
||||
uncaged-workflow threads
|
||||
|
||||
and use the ID of the active thread.
|
||||
Run \`uncaged-workflow help --skill\` for full CLI reference (thread ID lookup, CAS commands, etc.).
|
||||
|
||||
## Storing phase details — MANDATORY
|
||||
|
||||
For each phase you MUST store its full detail text in CAS using this exact CLI command:
|
||||
For each phase, store its full detail text in CAS via \`uncaged-workflow cas put <THREAD_ID> '<content>'\`. The command prints a content-hash — use that as the phase identifier.
|
||||
|
||||
uncaged-workflow cas put <THREAD_ID> '# <name>
|
||||
The thread ID (26-char Crockford Base32) appears in the first message. If unsure, run \`uncaged-workflow thread list\`.
|
||||
|
||||
Description: <description>
|
||||
**Do NOT store phase details in any other way** — the CLI is the only supported storage mechanism.
|
||||
|
||||
Acceptance: <acceptance>'
|
||||
## Phase granularity
|
||||
|
||||
Replace \`<THREAD_ID>\` with the actual thread ID you found above. The command prints a content-hash to stdout — use that hash as the phase identifier.
|
||||
Match the number of phases to task complexity:
|
||||
- Trivial (add a config option, fix a typo, rename): 1 phase
|
||||
- Small (a new feature touching 2-3 files): 1-2 phases
|
||||
- Medium (cross-module refactor): 2-3 phases
|
||||
- Large (new subsystem, architectural change): 3-5 phases
|
||||
|
||||
**Do NOT store phase details in any other way** (no temp files, no invented paths). The CLI command is the only supported storage mechanism.
|
||||
Fewer phases is always better. Each phase must justify its existence — if two phases would be tested together anyway, merge them.
|
||||
|
||||
## Output format
|
||||
|
||||
@@ -6,12 +6,5 @@
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [
|
||||
{ "path": "../workflow" },
|
||||
{ "path": "../workflow-role-coder" },
|
||||
{ "path": "../workflow-role-committer" },
|
||||
{ "path": "../workflow-role-planner" },
|
||||
{ "path": "../workflow-role-reviewer" },
|
||||
{ "path": "../workflow-role-tester" }
|
||||
]
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
|
||||
@@ -11,13 +11,10 @@ import {
|
||||
START,
|
||||
validateWorkflowDescriptor,
|
||||
} from "@uncaged/workflow";
|
||||
|
||||
import type { PreparerMeta } from "@uncaged/workflow-role-preparer";
|
||||
import type { SubmitterMeta } from "@uncaged/workflow-role-submitter";
|
||||
|
||||
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
||||
import type { DeveloperMeta } from "../src/developer.js";
|
||||
import { createSolveIssueRun, solveIssueModerator } from "../src/index.js";
|
||||
import type { PreparerMeta, SubmitterMeta } from "../src/roles/index.js";
|
||||
import type { SolveIssueMeta } from "../src/roles.js";
|
||||
|
||||
function jsonResponse(payload: Record<string, unknown>): Response {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { submitterMetaSchema, submitterRole } from "../src/submitter.js";
|
||||
import { submitterMetaSchema, submitterRole } from "../src/roles/submitter.js";
|
||||
|
||||
describe("submitterRole", () => {
|
||||
test("submitted sample validates against schema", () => {
|
||||
@@ -10,8 +10,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/workflow": "workspace:*",
|
||||
"@uncaged/workflow-role-preparer": "workspace:*",
|
||||
"@uncaged/workflow-role-submitter": "workspace:*",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,6 @@ import {
|
||||
import { solveIssueModerator } from "./moderator.js";
|
||||
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, solveIssueRoles } from "./roles.js";
|
||||
|
||||
export {
|
||||
type PreparerMeta,
|
||||
preparerMetaSchema,
|
||||
preparerRole,
|
||||
} from "@uncaged/workflow-role-preparer";
|
||||
export {
|
||||
type SubmitterMeta,
|
||||
submitterMetaSchema,
|
||||
submitterRole,
|
||||
} from "@uncaged/workflow-role-submitter";
|
||||
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
||||
export {
|
||||
type DeveloperMeta,
|
||||
@@ -28,6 +18,14 @@ export {
|
||||
developerRole,
|
||||
} from "./developer.js";
|
||||
export { solveIssueModerator } from "./moderator.js";
|
||||
export {
|
||||
type PreparerMeta,
|
||||
preparerMetaSchema,
|
||||
preparerRole,
|
||||
type SubmitterMeta,
|
||||
submitterMetaSchema,
|
||||
submitterRole,
|
||||
} from "./roles/index.js";
|
||||
export {
|
||||
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
type SolveIssueMeta,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { RoleDefinition } from "@uncaged/workflow";
|
||||
import { type PreparerMeta, preparerRole } from "@uncaged/workflow-role-preparer";
|
||||
import { type SubmitterMeta, submitterRole } from "@uncaged/workflow-role-submitter";
|
||||
|
||||
import { type DeveloperMeta, developerRole } from "./developer.js";
|
||||
import { type PreparerMeta, preparerRole } from "./roles/preparer.js";
|
||||
import { type SubmitterMeta, submitterRole } from "./roles/submitter.js";
|
||||
|
||||
export const SOLVE_ISSUE_WORKFLOW_DESCRIPTION =
|
||||
"Resolve an issue end-to-end by preparing the repo, delegating implementation to the develop workflow, and opening a pull request (preparer → developer → submitter).";
|
||||
|
||||
+1
@@ -3,3 +3,4 @@ export {
|
||||
preparerMetaSchema,
|
||||
preparerRole,
|
||||
} from "./preparer.js";
|
||||
export { type SubmitterMeta, submitterMetaSchema, submitterRole } from "./submitter.js";
|
||||
@@ -6,9 +6,5 @@
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [
|
||||
{ "path": "../workflow" },
|
||||
{ "path": "../workflow-role-preparer" },
|
||||
{ "path": "../workflow-role-submitter" }
|
||||
]
|
||||
"references": [{ "path": "../workflow" }]
|
||||
}
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/azureuser/repos/uncaged-workflow/packages/workflow
|
||||
@@ -19,13 +19,6 @@
|
||||
"references": [
|
||||
{ "path": "packages/workflow" },
|
||||
{ "path": "packages/workflow-agent-llm" },
|
||||
{ "path": "packages/workflow-role-committer" },
|
||||
{ "path": "packages/workflow-role-coder" },
|
||||
{ "path": "packages/workflow-role-planner" },
|
||||
{ "path": "packages/workflow-role-preparer" },
|
||||
{ "path": "packages/workflow-role-reviewer" },
|
||||
{ "path": "packages/workflow-role-submitter" },
|
||||
{ "path": "packages/workflow-role-tester" },
|
||||
{ "path": "packages/workflow-agent-cursor" },
|
||||
{ "path": "packages/workflow-agent-hermes" },
|
||||
{ "path": "packages/workflow-util-agent" },
|
||||
|
||||
Reference in New Issue
Block a user