Compare commits

..

6 Commits

Author SHA1 Message Date
xiaoju 4d85a2eebb refactor: split test suites — test:ci for unit tests, test for all
- Move hermes ACP integration tests to __tests__/integration/
- Add test:ci script to all packages (excludes integration/)
- CI workflow uses test:ci instead of test
- bun test still runs everything (unit + integration)

Refs #510
2026-05-25 10:27:46 +00:00
xiaoju cef4617956 fix: skip hermes ACP integration tests in CI
These tests require a live Hermes instance which is not available in CI.

Refs #510
2026-05-25 10:22:08 +00:00
xiaoju a11d76264a chore: open-source readiness — LICENSE, CONTRIBUTING, templates, package metadata
CI / check (pull_request) Failing after 32s
- Add MIT LICENSE
- Add CONTRIBUTING.md with setup, conventions, PR workflow
- Add GitHub issue/PR templates
- Add repository/homepage/bugs/license to all package.json files
- Add Install section to README before Quick Start

Fixes #510

小橘 🍊(NEKO Team)
2026-05-25 10:13:36 +00:00
xiaomo 6e8dedeb2f docs: move cursor rules to docs/, add project rules to CLAUDE.md
CI / check (push) Has been cancelled
Also bump @uncaged/json-cas* to ^0.5.2
2026-05-25 09:54:45 +00:00
xiaoju 762c457978 chore: add GitHub Actions CI + README badges
CI / check (push) Has been cancelled
小橘 🍊(NEKO Team)
2026-05-25 09:40:49 +00:00
xiaoju 9c26285424 chore: make solve-issue.yaml portable and add developer failed exit
- Remove hardcoded ~/repos/workflow paths from procedure text
- Use .worktrees/ relative to repo root instead of global path
- Add developer failed → $END exit for unrecoverable situations
- Add worktree field to reviewer rejected variant
- Fix test workflowPath to use import.meta.dirname

Refs #506
2026-05-25 09:07:58 +00:00
26 changed files with 613 additions and 252 deletions
+2 -26
View File
@@ -1,27 +1,3 @@
---
description: Ban dynamic import() in production code — use static imports instead
globs: packages/*/src/**/*.ts
alwaysApply: true
---
# No Dynamic Import
# No Dynamic Import in Production Code
## Rule
Do NOT use `await import()` or dynamic `import()` expressions in production source code.
Always use static top-level `import` statements.
## Exception (must include a comment explaining why)
1. **Bundle loader** — loads user-authored workflow bundles whose paths are only known at runtime
When suppressing, add a comment directly above:
```ts
// Dynamic import required: user bundle path resolved at runtime
const mod = await import(bundlePath);
```
## Test Files
Test files (`__tests__/**`) are exempt.
See [docs/no-dynamic-import.md](../../docs/no-dynamic-import.md) for full rules.
+2 -66
View File
@@ -1,67 +1,3 @@
# Sync README
# Sync Readme
When updating README.md files in this monorepo, follow these conventions.
## Scope
- Root `README.md` — project overview and navigation hub
- Per-package `packages/*/README.md` — each package self-contained
## Root README Structure
The root README should have these sections in order:
1. **Title and one-liner** — stateless workflow engine driven by single-step CLI
2. **Overview** — 2-3 paragraphs explaining what it does and key concepts
3. **Architecture** — dependency layer diagram (text-based)
4. **Packages** — table with ALL packages from packages/ directory, columns: Package, Description, Type (cli/lib/agent/app)
5. **Quick Start** — install, build, register workflow, start thread, run step
6. **CLI Reference** — brief command list, detailed usage in cli-workflow README
7. **Development** — bun install / build / check / test
## Per-Package README Structure
Each package README should have:
1. **Title** — package name
2. **One-line description** — matching package.json
3. **Overview** — what it does, where it sits in the architecture, dependencies
4. **Installation** — bun add (for libs) or "included as binary" (for cli/agents)
5. **API** (lib packages) — all exports from src/index.ts with type signatures, grouped by category, minimal usage examples
6. **CLI Usage** (cli/agent packages) — command reference with examples
7. **Internal Structure** — brief src/ file organization
8. **Configuration** (if applicable)
## Execution Steps
### Step 1: Gather current state
For each package read:
- package.json (name, version, description, dependencies, bin)
- src/index.ts (public API exports)
- Existing README.md (preserve hand-written content worth keeping)
### Step 2: Update root README
- Ensure ALL packages in packages/ directory are listed in the table
- Update CLI command reference from uwf --help output
- Keep Quick Start examples valid
### Step 3: Write/update each package README
- Follow the per-package structure
- API section MUST match actual src/index.ts exports — never invent
- For agent packages: document CLI binary name, how it is invoked
- For lib packages: document exported types and functions
- Internal structure: list actual files in src/
### Step 4: Verify
- All relative links work
- Package names match package.json
- No references to removed/renamed packages
- bun run build still passes
## Guidelines
- Only document what src/index.ts actually exports
- Root README summarizes, package READMEs go into detail
- Verify CLI examples against actual commands
- Preserve existing good prose when updating
- English for all README content
See [docs/sync-readme.md](../../docs/sync-readme.md) for full rules.
+31
View File
@@ -0,0 +1,31 @@
---
name: Bug Report
about: Report a bug or unexpected behavior
labels: bug
---
## Describe the bug
A clear description of what the bug is.
## To reproduce
Steps or commands to reproduce:
```bash
uwf ...
```
## Expected behavior
What you expected to happen.
## Actual behavior
What actually happened. Include error messages or logs.
## Environment
- OS:
- Bun version:
- uwf version (`uwf --version`):
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature Request
about: Suggest a new feature or improvement
labels: enhancement
---
## What
Describe the feature or improvement.
## Why
Why is this needed? What problem does it solve?
## Proposed solution
How should it work? Include API sketches, CLI examples, or workflow YAML snippets if applicable.
+15
View File
@@ -0,0 +1,15 @@
## What
What this PR does.
## Why
Why the change is needed.
## Changes
- `path/to/file` — what changed and why
## Ref
Fixes #
+28
View File
@@ -0,0 +1,28 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- name: Build
run: bun run build
- name: Lint
run: bunx biome check .
- name: Test
run: bun run test:ci
+52 -43
View File
@@ -10,9 +10,9 @@ roles:
procedure: |
On first run (no previous steps):
1. Read the issue and all comments from Gitea using `tea issues <number> -r <owner/repo>`
2. Read CLAUDE.md (or equivalent project conventions file) to understand coding standards
2. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md, .cursor/rules/) in the repo
3. Assess whether the issue has enough information to produce a test spec
4. If insufficient info: comment on the issue via `echo "..." | tea comment <number> -r <owner/repo>` (skip if you already commented), then output status=insufficient_info and terminate
4. If insufficient info: comment on the issue via `echo "..." | tea comment <number> -r <owner/repo>` (skip if you already commented), then output $status=insufficient_info
5. If sufficient: produce a detailed TDD test spec in markdown covering all scenarios
On subsequent runs (bounced back by tester with fix_spec):
@@ -21,7 +21,8 @@ roles:
After producing the test spec:
1. Store it via `uwf cas put-text "<markdown content>"` and capture the returned hash
2. Put the hash in frontmatter.plan (required when status=ready)
2. Put the hash in frontmatter.plan (required when $status=ready)
3. Set repoPath to the absolute path of the repository root
output: "Output a brief summary of the test spec. Set $status to ready (with plan hash and repoPath) or insufficient_info."
frontmatter:
oneOf:
@@ -40,32 +41,41 @@ roles:
- coding
procedure: |
IMPORTANT: Always work in a git worktree, NEVER modify the main working directory directly.
The repo path and other details are provided in your task prompt.
Before starting any work, set up an isolated worktree:
1. `cd ~/repos/workflow && git fetch origin` to get latest refs
2. First time (no existing branch):
- `git worktree add ~/repos/workflow-worktrees/fix/<issue-number>-<short-slug> -b fix/<issue-number>-<short-slug> origin/main`
- `cd ~/repos/workflow-worktrees/fix/<issue-number>-<short-slug> && bun install`
3. If bounced back from reviewer or tester (branch already exists):
- The worktree should already exist at `~/repos/workflow-worktrees/fix/<issue-number>-<short-slug>`
- `cd ~/repos/workflow-worktrees/fix/<issue-number>-<short-slug>`
1. cd into the repo path provided in your task prompt
2. `git fetch origin` to get latest refs
3. First time (no existing branch):
- `git worktree add .worktrees/fix/<issue-number>-<short-slug> -b fix/<issue-number>-<short-slug> origin/main`
- `cd .worktrees/fix/<issue-number>-<short-slug> && bun install`
4. If bounced back from reviewer or tester (branch already exists):
- cd into the existing worktree under `.worktrees/fix/<issue-number>-<short-slug>`
- `git fetch origin && git rebase origin/main`
4. ALL subsequent work must happen inside the worktree directory.
5. ALL subsequent work must happen inside the worktree directory.
Then implement TDD:
5. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the latest planner step's frontmatter.plan)
6. If bounced back from reviewer or tester: read the previous role's output to understand what needs fixing
7. Write tests first based on the spec
8. Implement the code to make tests pass
9. Ensure `bun run build` passes with no errors
10. Run `bun test` to verify all tests pass
output: "List all files changed and provide a summary. Include branch name and worktree path in frontmatter."
6. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner's output in your task prompt)
7. If bounced back from reviewer or tester: read the previous role's feedback in your task prompt
8. Write tests first based on the spec
9. Implement the code to make tests pass
10. Ensure `bun run build` passes with no errors
11. Run `bun test` to verify all tests pass
If you cannot complete the implementation (e.g. the issue is too complex, blocked by external factors,
or repeated attempts fail), set $status=failed with a reason.
output: "List all files changed and provide a summary. Set $status to done (with branch/worktree), or failed (with reason)."
frontmatter:
type: object
properties:
branch: { type: string }
worktree: { type: string }
required: [branch, worktree]
oneOf:
- properties:
$status: { const: "done" }
branch: { type: string }
worktree: { type: string }
required: [$status, branch, worktree]
- properties:
$status: { const: "failed" }
reason: { type: string }
required: [$status, reason]
reviewer:
description: "Code standards compliance check"
goal: "You are a code reviewer. You verify code standards compliance — NOT functionality (that's the tester's job)."
@@ -73,7 +83,7 @@ roles:
- code-review
- static-analysis
procedure: |
First, cd into the worktree: `cd ~/repos/workflow-worktrees/fix/<issue-number>-*` (find the exact directory)
The worktree path is provided in your task prompt. cd into it first.
Before reviewing, verify the git branch:
1. Run `git branch --show-current` — confirm the branch name references the issue number being worked on
@@ -85,12 +95,9 @@ roles:
4. `bunx biome check` — no lint violations
5. TypeScript strict mode — no type errors
Soft checks (review against CLAUDE.md conventions):
- Functional-first: `function` + `type`, not `class` + `interface`
- No optional properties (`?:`) — use `T | null`
- Naming conventions (kebab-case files, PascalCase types, camelCase functions)
- Module boundary discipline (folder exports via index.ts)
- No `console.log` (use structured logger)
Soft checks (review against project conventions if CLAUDE.md / .cursor/rules exist):
- Naming conventions, module boundaries, code style
- No `console.log` in production code
- No dynamic imports in production code
Only review standards compliance. Do NOT test functionality.
@@ -106,17 +113,18 @@ roles:
- properties:
$status: { const: "rejected" }
comments: { type: string }
required: [$status, comments]
worktree: { type: string }
required: [$status, comments, worktree]
tester:
description: "Functional correctness verification"
goal: "You are a tester agent. You verify that the implementation correctly satisfies every scenario in the test spec."
capabilities:
- testing
procedure: |
First, cd into the worktree: `cd ~/repos/workflow-worktrees/fix/<issue-number>-*` (find the exact directory)
The worktree path is provided in your task prompt. cd into it first.
1. Run `bun test` for automated test verification
2. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the latest planner step's frontmatter.plan)
2. Read the test spec from CAS: `uwf cas get <plan hash>` (find the hash from the planner step in the thread history)
3. Verify each scenario in the spec is covered and passing
4. Determine outcome:
- passed: all scenarios verified, tests pass
@@ -143,21 +151,21 @@ roles:
goal: "You are a committer agent. You create a clean commit and push a PR linking the original issue."
capabilities: []
procedure: |
First, cd into the worktree: `cd ~/repos/workflow-worktrees/fix/<issue-number>-*` (find the exact directory)
The worktree path, branch name, and repo info are provided in your task prompt.
cd into the worktree first.
Note: You inherit the developer's worktree and branch. Do NOT create a new branch.
1. Stage all changes: `git add -A`
2. Commit with a descriptive message referencing the issue: `git commit -m "type: description\n\nFixes #N"`
3. Push the branch: `git push -u origin <branch-name>`
- If push hook fails: capture the error log in your output, mark hook_failed
4. On push success: create a PR via `tea pr create --repo uncaged/workflow --title "..." --description "..."`
- The `--repo` flag is required to work in worktree directories (fixes #474 "path segment [0] is empty" error)
- If working on a different repo, extract owner/repo from: `git remote get-url origin | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/'`
- PR description must follow the project template: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
- On tea failure: capture stderr/stdout, log the error clearly, include PR details (title, description, branch) for manual creation, and mark success=false
4. On push success: create a PR via `tea pr create --repo <owner/repo> --title "..." --description "..."`
- Extract owner/repo from: `git remote get-url origin | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/'`
- PR description must include: What / Why / Changes / Ref sections, with `Fixes #N` in Ref
- On tea failure: capture stderr/stdout, include PR details for manual creation, mark hook_failed
5. After PR creation, clean up the worktree:
- `cd ~/repos/workflow`
- `git worktree remove ~/repos/workflow-worktrees/fix/<issue-number>-<slug>`
- cd to the repo root (parent of .worktrees)
- `git worktree remove <worktree-path>`
output: "Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error)."
frontmatter:
oneOf:
@@ -176,9 +184,10 @@ graph:
insufficient_info: { role: "$END", prompt: "Insufficient information to proceed; end the workflow." }
ready: { role: "developer", prompt: "Implement the TDD test spec (CAS hash: {{{plan}}}) in repo {{{repoPath}}}." }
developer:
_: { role: "reviewer", prompt: "Review branch {{{branch}}} at {{{worktree}}} for code standards compliance." }
done: { role: "reviewer", prompt: "Review branch {{{branch}}} at {{{worktree}}} for code standards compliance." }
failed: { role: "$END", prompt: "Developer failed: {{{reason}}}. Ending workflow." }
reviewer:
rejected: { role: "developer", prompt: "Reviewer rejected: {{{comments}}}. Fix the issues." }
rejected: { role: "developer", prompt: "Reviewer rejected: {{{comments}}}. Fix the issues in repo {{{worktree}}}." }
approved: { role: "tester", prompt: "Review passed. Run tests on branch {{{branch}}} at {{{worktree}}}." }
tester:
fix_code: { role: "developer", prompt: "Tests found code issues: {{{report}}}. Fix and re-submit." }
+5
View File
@@ -285,6 +285,11 @@ moderator → agent → extract — one step per invocation, repeat until $
2. **Register**`uwf workflow put <file.yaml>` parses YAML, registers output schemas, stores `WorkflowPayload` in CAS
3. **Run**`uwf thread start` creates a thread, `uwf thread step` executes one cycle per invocation
## Project Rules
- [docs/sync-readme.md](docs/sync-readme.md) — README sync conventions
- [docs/no-dynamic-import.md](docs/no-dynamic-import.md) — no dynamic import in production code
## Commit Convention
```
+110
View File
@@ -0,0 +1,110 @@
# Contributing to @uncaged/workflow
Thank you for your interest in contributing! This guide covers setup, conventions, and the PR workflow.
## Prerequisites
- [Bun](https://bun.sh/) (latest)
- [Node.js](https://nodejs.org/) 20+
- Git
## Setup
```bash
git clone https://github.com/shazhou-ww/uncaged-workflow.git
cd uncaged-workflow
bun install
bun run build
bun test
```
## Development Workflow
```bash
bun run build # TypeScript compilation (all packages)
bun run check # tsc + biome lint + log tag validation
bun run format # Auto-format with Biome
bun test # Run all tests
```
All three (`build`, `check`, `test`) must pass before submitting a PR. A pre-push hook runs `check` + `test` automatically.
## Coding Conventions
See [CLAUDE.md](CLAUDE.md) for the full coding standard. Key points:
- **Functional-first** — `function` + `type`, not `class` + `interface`
- **No optional properties** — use `T | null` instead of `?:`
- **Named exports only** — no default exports
- **No `console.log`** — use the structured logger from `@uncaged/workflow-util`
- **Static imports only** — no `await import()` in production code
- **Biome** for lint + format — run `bun run check` before committing
## Commit Messages
```
<type>(<scope>): <description>
type: feat | fix | refactor | docs | chore | test
scope: cli | moderator | agent-kit | hermes | builtin | claude-code | util | protocol | dashboard
```
Examples:
- `feat(moderator): add cycle detection to graph evaluator`
- `fix(cli): handle missing config file gracefully`
- `docs(protocol): update StepNode field descriptions`
## Pull Request Process
1. **Branch** from `main`: `git checkout -b feat/123-short-description`
2. **Implement** your change with tests
3. **Run checks**: `bun run check && bun test`
4. **Commit** with a descriptive message referencing the issue: `Fixes #123`
5. **Push** and open a PR
### PR Description Template
```
## What
What this PR does.
## Why
Why the change is needed.
## Changes
- `path/to/file.ts` — what changed and why
## Ref
Fixes #N
```
## Adding a Changeset
For any user-facing change (feat, fix, breaking change), add a changeset:
```bash
bun changeset
```
This creates a markdown file in `.changeset/` describing the change. It will be consumed on the next release to bump versions and generate CHANGELOG entries.
## Project Structure
```
packages/
workflow-protocol/ # Shared types and JSON Schema
workflow-util/ # Encoding, IDs, logging, frontmatter
workflow-moderator/ # Status-based graph evaluator
workflow-agent-kit/ # createAgent factory, extract pipeline
workflow-agent-hermes/ # Hermes ACP agent
workflow-agent-builtin/ # Built-in LLM agent
workflow-agent-claude-code/ # Claude Code agent
cli-workflow/ # uwf CLI binary
workflow-dashboard/ # Web UI (private, alpha)
```
Dependency flows downward — lower layers have no dependency on higher layers. See [CLAUDE.md](CLAUDE.md) for the full architecture.
## License
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Uncaged
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+31 -18
View File
@@ -1,5 +1,10 @@
# @uncaged/workflow
[![CI](https://github.com/shazhou-ww/uncaged-workflow/actions/workflows/ci.yml/badge.svg)](https://github.com/shazhou-ww/uncaged-workflow/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@uncaged/cli-workflow?label=%40uncaged%2Fcli-workflow)](https://www.npmjs.com/package/@uncaged/cli-workflow)
[![npm](https://img.shields.io/npm/v/@uncaged/workflow-protocol?label=%40uncaged%2Fworkflow-protocol)](https://www.npmjs.com/package/@uncaged/workflow-protocol)
[![npm](https://img.shields.io/npm/v/@uncaged/workflow-agent-kit?label=%40uncaged%2Fworkflow-agent-kit)](https://www.npmjs.com/package/@uncaged/workflow-agent-kit)
A stateless workflow engine driven by a single-step CLI. Workflows are YAML definitions with roles, status-based routing, and a directed graph. Threads are immutable CAS-linked chains — each `uwf thread step` runs one moderator→agent→extract cycle and exits.
## Overview
@@ -10,6 +15,32 @@ Workflow state lives entirely on disk under `~/.uncaged/workflow/`: CAS nodes fo
Agents are pluggable CLI binaries (`uwf-hermes`, `uwf-builtin`, `uwf-claude-code`, or custom commands). The engine spawns the configured agent with `<thread-id>` and `<role>`, sets `UWF_EDGE_PROMPT` from the graph transition, and captures both the agent's markdown output and a detail CAS node for session replay.
## Install
```bash
npm install -g @uncaged/cli-workflow
```
Requires [Bun](https://bun.sh/) runtime (used internally for TypeScript execution).
## Quick Start
```bash
# 1. Configure provider, model, and default agent
uwf setup
# 2. Register a workflow from YAML
uwf workflow add examples/solve-issue.yaml
# 3. Start a thread (creates head pointer; does not execute)
uwf thread start solve-issue -p "Fix the login redirect bug"
# 4. Execute steps (one at a time, until done)
uwf thread exec <thread-id>
```
Use `-c, --count <number>` on `thread exec` to run multiple steps in one invocation. Override the agent with `--agent <cmd>`.
## Architecture
Dependency layers (lower layers have no dependency on higher layers):
@@ -55,24 +86,6 @@ See [docs/architecture.md](docs/architecture.md) for the full design — three-p
| `workflow-agent-claude-code` | `@uncaged/workflow-agent-claude-code` | `uwf-claude-code` — spawns Claude Code CLI | agent | [README](packages/workflow-agent-claude-code/README.md) |
| `workflow-dashboard` | `@uncaged/workflow-dashboard` | Web graph editor for workflow YAML (private, alpha) | app | [README](packages/workflow-dashboard/README.md) |
## Quick Start
```bash
# 1. Configure provider, model, and default agent
uwf setup
# 2. Register a workflow from YAML
uwf workflow add examples/solve-issue.yaml
# 3. Start a thread (creates head pointer; does not execute)
uwf thread start solve-issue -p "Fix the login redirect bug"
# 4. Execute steps (one at a time, until done)
uwf thread exec <thread-id>
```
Use `-c, --count <number>` on `thread exec` to run multiple steps in one invocation. Override the agent with `--agent <cmd>`.
## CLI Reference
Global options: `-V, --version`, `--format <json|yaml>`, `-h, --help`.
+27
View File
@@ -0,0 +1,27 @@
---
description: Ban dynamic import() in production code — use static imports instead
globs: packages/*/src/**/*.ts
alwaysApply: true
---
# No Dynamic Import in Production Code
## Rule
Do NOT use `await import()` or dynamic `import()` expressions in production source code.
Always use static top-level `import` statements.
## Exception (must include a comment explaining why)
1. **Bundle loader** — loads user-authored workflow bundles whose paths are only known at runtime
When suppressing, add a comment directly above:
```ts
// Dynamic import required: user bundle path resolved at runtime
const mod = await import(bundlePath);
```
## Test Files
Test files (`__tests__/**`) are exempt.
+67
View File
@@ -0,0 +1,67 @@
# Sync README
When updating README.md files in this monorepo, follow these conventions.
## Scope
- Root `README.md` — project overview and navigation hub
- Per-package `packages/*/README.md` — each package self-contained
## Root README Structure
The root README should have these sections in order:
1. **Title and one-liner** — stateless workflow engine driven by single-step CLI
2. **Overview** — 2-3 paragraphs explaining what it does and key concepts
3. **Architecture** — dependency layer diagram (text-based)
4. **Packages** — table with ALL packages from packages/ directory, columns: Package, Description, Type (cli/lib/agent/app)
5. **Quick Start** — install, build, register workflow, start thread, run step
6. **CLI Reference** — brief command list, detailed usage in cli-workflow README
7. **Development** — bun install / build / check / test
## Per-Package README Structure
Each package README should have:
1. **Title** — package name
2. **One-line description** — matching package.json
3. **Overview** — what it does, where it sits in the architecture, dependencies
4. **Installation** — bun add (for libs) or "included as binary" (for cli/agents)
5. **API** (lib packages) — all exports from src/index.ts with type signatures, grouped by category, minimal usage examples
6. **CLI Usage** (cli/agent packages) — command reference with examples
7. **Internal Structure** — brief src/ file organization
8. **Configuration** (if applicable)
## Execution Steps
### Step 1: Gather current state
For each package read:
- package.json (name, version, description, dependencies, bin)
- src/index.ts (public API exports)
- Existing README.md (preserve hand-written content worth keeping)
### Step 2: Update root README
- Ensure ALL packages in packages/ directory are listed in the table
- Update CLI command reference from uwf --help output
- Keep Quick Start examples valid
### Step 3: Write/update each package README
- Follow the per-package structure
- API section MUST match actual src/index.ts exports — never invent
- For agent packages: document CLI binary name, how it is invoked
- For lib packages: document exported types and functions
- Internal structure: list actual files in src/
### Step 4: Verify
- All relative links work
- Package names match package.json
- No references to removed/renamed packages
- bun run build still passes
## Guidelines
- Only document what src/index.ts actually exports
- Root README summarizes, package READMEs go into detail
- Verify CLI examples against actual commands
- Preserve existing good prose when updating
- English for all README content
+11 -1
View File
@@ -11,6 +11,7 @@
"typecheck": "bunx tsc --build",
"format": "biome format --write .",
"test": "bun run --filter './packages/*' test",
"test:ci": "bun run --filter './packages/*' test:ci",
"changeset": "bunx changeset",
"version": "bunx changeset version",
"release": "bun run build && bun test && node scripts/publish-all.mjs"
@@ -23,5 +24,14 @@
"@types/xxhashjs": "^0.2.4",
"@uncaged/workflow-agent-hermes": "workspace:*",
"bun-types": "^1.3.13"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
+15 -4
View File
@@ -11,8 +11,8 @@
"uwf": "./src/cli.ts"
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas-fs": "^0.4.0",
"@uncaged/json-cas": "^0.5.2",
"@uncaged/json-cas-fs": "^0.5.2",
"@uncaged/workflow-agent-kit": "workspace:^",
"@uncaged/workflow-moderator": "workspace:^",
"@uncaged/workflow-protocol": "workspace:^",
@@ -22,12 +22,23 @@
"yaml": "^2.8.4"
},
"scripts": {
"test": "vitest run"
"test": "vitest run",
"test:ci": "vitest run"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"vitest": "^4.1.6"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/cli-workflow"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
@@ -13,8 +13,16 @@ import { parse } from "yaml";
*/
describe("solve-issue workflow: tea pr create worktree fix", () => {
// Navigate up from packages/cli-workflow to repo root
const workflowPath = join(process.cwd(), "..", "..", ".workflows", "solve-issue.yaml");
// Navigate up from packages/cli-workflow/src/__tests__ to repo root
const workflowPath = join(
import.meta.dirname,
"..",
"..",
"..",
"..",
".workflows",
"solve-issue.yaml",
);
test("committer procedure should include --repo flag in tea pr create command", async () => {
const yamlContent = await readFile(workflowPath, "utf-8");
+14 -3
View File
@@ -18,10 +18,11 @@
}
},
"scripts": {
"test": "bun test"
"test": "bun test",
"test:ci": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas": "^0.5.2",
"@uncaged/workflow-agent-kit": "workspace:^",
"@uncaged/workflow-util": "workspace:^"
},
@@ -30,5 +31,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-agent-builtin"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
@@ -18,10 +18,11 @@
}
},
"scripts": {
"test": "bun test"
"test": "bun test",
"test:ci": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas": "^0.5.2",
"@uncaged/workflow-agent-kit": "workspace:^",
"@uncaged/workflow-util": "workspace:^"
},
@@ -30,5 +31,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-agent-claude-code"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
@@ -2,8 +2,6 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { HermesAcpClient } from "../src/acp-client.js";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
describe("handleSessionUpdate — helper extraction", () => {
let client: HermesAcpClient;
@@ -94,75 +92,4 @@ describe("handleSessionUpdate — helper extraction", () => {
});
});
describe("HermesAcpClient", () => {
let client: HermesAcpClient;
beforeEach(() => {
client = new HermesAcpClient();
});
afterEach(async () => {
await client.close();
});
it(
"connect() returns a UUID sessionId",
async () => {
const sessionId = await client.connect(process.cwd());
expect(typeof sessionId).toBe("string");
expect(sessionId).toMatch(UUID_RE);
},
{ timeout: 2 * 60 * 1000 },
);
it(
"prompt() returns a non-empty text response",
async () => {
await client.connect(process.cwd());
const result = await client.prompt("Reply with exactly the word: PONG");
expect(typeof result.text).toBe("string");
expect(result.text.length).toBeGreaterThan(0);
expect(typeof result.sessionId).toBe("string");
expect(result.sessionId).toMatch(UUID_RE);
},
{ timeout: 2 * 60 * 1000 },
);
it(
"prompt() can be called twice on the same session (resume)",
async () => {
await client.connect(process.cwd());
const first = await client.prompt("Say the word ALPHA and nothing else.");
expect(first.text.length).toBeGreaterThan(0);
const second = await client.prompt("Now say the word BETA and nothing else.");
expect(second.text.length).toBeGreaterThan(0);
expect(first.sessionId).toBe(second.sessionId);
},
{ timeout: 2 * 60 * 1000 },
);
// TODO(#435): flaky — depends on live LLM; mock or move to integration suite
it.skip(
"prompt() collects structured messages including tool calls",
async () => {
await client.connect(process.cwd());
const result = await client.prompt("Run this command: echo TOOL_DETAIL_TEST");
expect(result.messages.length).toBeGreaterThan(0);
// Should have at least one tool message (the echo command)
const toolMessages = result.messages.filter((m) => m.role === "tool");
expect(toolMessages.length).toBeGreaterThan(0);
// Tool message should contain the output
const toolContent = toolMessages[0]?.content ?? "";
expect(toolContent).toContain("TOOL_DETAIL_TEST");
// Should have assistant messages with tool_calls
const assistantWithTools = result.messages.filter(
(m) => m.role === "assistant" && m.tool_calls !== null,
);
expect(assistantWithTools.length).toBeGreaterThan(0);
},
{ timeout: 2 * 60 * 1000 },
);
});
@@ -0,0 +1,75 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { HermesAcpClient } from "../../src/acp-client.js";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
describe("HermesAcpClient", () => {
let client: HermesAcpClient;
beforeEach(() => {
client = new HermesAcpClient();
});
afterEach(async () => {
await client.close();
});
it(
"connect() returns a UUID sessionId",
async () => {
const sessionId = await client.connect(process.cwd());
expect(typeof sessionId).toBe("string");
expect(sessionId).toMatch(UUID_RE);
},
{ timeout: 2 * 60 * 1000 },
);
it(
"prompt() returns a non-empty text response",
async () => {
await client.connect(process.cwd());
const result = await client.prompt("Reply with exactly the word: PONG");
expect(typeof result.text).toBe("string");
expect(result.text.length).toBeGreaterThan(0);
expect(typeof result.sessionId).toBe("string");
expect(result.sessionId).toMatch(UUID_RE);
},
{ timeout: 2 * 60 * 1000 },
);
it(
"prompt() can be called twice on the same session (resume)",
async () => {
await client.connect(process.cwd());
const first = await client.prompt("Say the word ALPHA and nothing else.");
expect(first.text.length).toBeGreaterThan(0);
const second = await client.prompt("Now say the word BETA and nothing else.");
expect(second.text.length).toBeGreaterThan(0);
expect(first.sessionId).toBe(second.sessionId);
},
{ timeout: 2 * 60 * 1000 },
);
// TODO(#435): flaky — depends on live LLM; mock or move to integration suite
it.skip(
"prompt() collects structured messages including tool calls",
async () => {
await client.connect(process.cwd());
const result = await client.prompt("Run this command: echo TOOL_DETAIL_TEST");
expect(result.messages.length).toBeGreaterThan(0);
const toolMessages = result.messages.filter((m) => m.role === "tool");
expect(toolMessages.length).toBeGreaterThan(0);
const toolContent = toolMessages[0]?.content ?? "";
expect(toolContent).toContain("TOOL_DETAIL_TEST");
const assistantWithTools = result.messages.filter(
(m) => m.role === "assistant" && m.tool_calls !== null,
);
expect(assistantWithTools.length).toBeGreaterThan(0);
},
{ timeout: 2 * 60 * 1000 },
);
});
+14 -3
View File
@@ -18,10 +18,11 @@
}
},
"scripts": {
"test": "bun test"
"test": "bun test",
"test:ci": "bun test __tests__/*.test.ts"
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas": "^0.5.2",
"@uncaged/workflow-agent-kit": "workspace:^",
"@uncaged/workflow-protocol": "workspace:^",
"@uncaged/workflow-util": "workspace:^"
@@ -31,5 +32,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-agent-hermes"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
+15 -4
View File
@@ -15,11 +15,12 @@
}
},
"scripts": {
"test": "bun test"
"test": "bun test",
"test:ci": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas-fs": "^0.4.0",
"@uncaged/json-cas": "^0.5.2",
"@uncaged/json-cas-fs": "^0.5.2",
"@uncaged/workflow-protocol": "workspace:^",
"@uncaged/workflow-util": "workspace:^",
"dotenv": "^16.6.1",
@@ -30,5 +31,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-agent-kit"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
+13 -2
View File
@@ -15,7 +15,8 @@
}
},
"scripts": {
"test": "bun test"
"test": "bun test",
"test:ci": "bun test"
},
"dependencies": {
"@uncaged/workflow-protocol": "workspace:^",
@@ -27,5 +28,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-moderator"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
+13 -3
View File
@@ -15,13 +15,23 @@
}
},
"dependencies": {
"@uncaged/json-cas": "^0.4.0",
"@uncaged/json-cas-fs": "^0.4.0"
"@uncaged/json-cas": "^0.5.2",
"@uncaged/json-cas-fs": "^0.5.2"
},
"devDependencies": {
"typescript": "^5.8.3"
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-protocol"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}
+11 -1
View File
@@ -20,5 +20,15 @@
},
"publishConfig": {
"access": "public"
}
},
"repository": {
"type": "git",
"url": "https://github.com/shazhou-ww/uncaged-workflow.git",
"directory": "packages/workflow-util"
},
"homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
"bugs": {
"url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
},
"license": "MIT"
}