diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f090c96..3eca042 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -7,18 +7,19 @@ on: branches: [main] jobs: - test: + check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup Bun - uses: oven-sh/setup-bun@v2 + - uses: oven-sh/setup-bun@v2 - - name: Install dependencies - run: bun install + - run: bun install - - name: Check + - name: Build + run: bun run build + + - name: Lint run: bun run check - name: Test diff --git a/biome.json b/biome.json index f72dd1a..b1d0c18 100644 --- a/biome.json +++ b/biome.json @@ -39,7 +39,8 @@ "linter": { "rules": { "suspicious": { - "noExplicitAny": "off" + "noExplicitAny": "off", + "noConsole": "off" }, "style": { "noNonNullAssertion": "off" diff --git a/package.json b/package.json index 928f279..3f6b1a1 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "@uncaged/workflow-monorepo", "private": true, + "packageManager": "bun@1.3.14", "workspaces": [ "packages/*" ], "scripts": { "uwf": "bun packages/cli-workflow/src/cli.ts", + "preinstall": "npx only-allow bun", + "prepublishOnly": "echo 'Use bun run release instead' && exit 1", "build": "bunx tsc --build", "check": "bunx tsc --build && biome check . && bash scripts/lint-log-tags.sh", "typecheck": "bunx tsc --build", @@ -23,7 +26,9 @@ "@types/node": "^25.7.0", "@types/xxhashjs": "^0.2.4", "@uncaged/workflow-agent-hermes": "workspace:*", - "bun-types": "^1.3.13" + "bun-types": "^1.3.13", + "typescript": "^5.8.3", + "vitest": "^4.1.7" }, "repository": { "type": "git", diff --git a/packages/cli-workflow/package.json b/packages/cli-workflow/package.json index c512c81..311e630 100644 --- a/packages/cli-workflow/package.json +++ b/packages/cli-workflow/package.json @@ -22,6 +22,7 @@ "yaml": "^2.8.4" }, "scripts": { + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", "test": "vitest run", "test:ci": "vitest run" }, @@ -34,12 +35,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/cli-workflow" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts b/packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts index 5683213..e9ae476 100644 --- a/packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts +++ b/packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts @@ -98,7 +98,7 @@ describe("solve-issue workflow: tea pr create worktree fix", () => { expect(frontmatter).toBeDefined(); expect(frontmatter?.oneOf).toBeDefined(); const committedVariant = frontmatter.oneOf.find( - (v: any) => v.properties?.["$status"]?.const === "committed", + (v: any) => v.properties?.$status?.const === "committed", ); expect(committedVariant).toBeDefined(); expect(committedVariant.required).toContain("$status"); diff --git a/packages/cli-workflow/src/__tests__/spawn-agent-json.test.ts b/packages/cli-workflow/src/__tests__/spawn-agent-json.test.ts index 3d03091..1a0c261 100644 --- a/packages/cli-workflow/src/__tests__/spawn-agent-json.test.ts +++ b/packages/cli-workflow/src/__tests__/spawn-agent-json.test.ts @@ -56,7 +56,7 @@ const VALID_OUTPUT: AdapterOutput = { describe("spawnAgent JSON parsing", () => { test("B1. parses valid JSON from agent stdout", () => { - const stdout = JSON.stringify(VALID_OUTPUT) + "\n"; + const stdout = `${JSON.stringify(VALID_OUTPUT)}\n`; const result = parseAgentStdout(stdout); expect(result.stepHash).toBe("0123456789ABC"); expect(result.detailHash).toBe("DEFGH12345678"); @@ -68,7 +68,7 @@ describe("spawnAgent JSON parsing", () => { }); test("B2. extracts stepHash for head pointer", () => { - const stdout = JSON.stringify(VALID_OUTPUT) + "\n"; + const stdout = `${JSON.stringify(VALID_OUTPUT)}\n`; const result = parseAgentStdout(stdout); expect(result.stepHash).toBe("0123456789ABC"); expect(isCasRef(result.stepHash)).toBe(true); @@ -76,7 +76,7 @@ describe("spawnAgent JSON parsing", () => { test("B3. handles debug lines before JSON", () => { const debugLines = "[debug] loading context...\n[debug] running agent...\n"; - const stdout = debugLines + JSON.stringify(VALID_OUTPUT) + "\n"; + const stdout = `${debugLines + JSON.stringify(VALID_OUTPUT)}\n`; const result = parseAgentStdout(stdout); expect(result.stepHash).toBe("0123456789ABC"); }); @@ -88,13 +88,13 @@ describe("spawnAgent JSON parsing", () => { test("B5. rejects JSON missing stepHash", () => { const incomplete = { detailHash: "DEFGH12345678", role: "planner" }; - const stdout = JSON.stringify(incomplete) + "\n"; + const stdout = `${JSON.stringify(incomplete)}\n`; expect(() => parseAgentStdout(stdout)).toThrow("missing valid stepHash"); }); test("B6. rejects JSON with invalid stepHash", () => { const bad = { ...VALID_OUTPUT, stepHash: "not-a-hash" }; - const stdout = JSON.stringify(bad) + "\n"; + const stdout = `${JSON.stringify(bad)}\n`; expect(() => parseAgentStdout(stdout)).toThrow("missing valid stepHash"); }); }); diff --git a/packages/cli-workflow/src/commands/skill.ts b/packages/cli-workflow/src/commands/skill.ts index 5364b3a..ab540fe 100644 --- a/packages/cli-workflow/src/commands/skill.ts +++ b/packages/cli-workflow/src/commands/skill.ts @@ -5,12 +5,7 @@ export { generateUserReference as cmdSkillUser, } from "@uncaged/workflow-util"; -const SKILL_NAMES = [ - "user", - "author", - "developer", - "adapter", -] as const; +const SKILL_NAMES = ["user", "author", "developer", "adapter"] as const; export function cmdSkillList(): ReadonlyArray { return [...SKILL_NAMES]; diff --git a/packages/cli-workflow/vitest.config.ts b/packages/cli-workflow/vitest.config.ts index 8696084..cf90b0a 100644 --- a/packages/cli-workflow/vitest.config.ts +++ b/packages/cli-workflow/vitest.config.ts @@ -3,5 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, }, }); diff --git a/packages/workflow-agent-builtin/package.json b/packages/workflow-agent-builtin/package.json index 4ae4442..33a9db5 100644 --- a/packages/workflow-agent-builtin/package.json +++ b/packages/workflow-agent-builtin/package.json @@ -18,8 +18,9 @@ } }, "scripts": { - "test": "bun test", - "test:ci": "bun test" + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" }, "dependencies": { "@uncaged/json-cas": "^0.5.3", @@ -34,12 +35,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-agent-builtin" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/workflow-agent-builtin/vitest.config.ts b/packages/workflow-agent-builtin/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-agent-builtin/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/packages/workflow-agent-claude-code/package.json b/packages/workflow-agent-claude-code/package.json index 39e2bb0..a585f78 100644 --- a/packages/workflow-agent-claude-code/package.json +++ b/packages/workflow-agent-claude-code/package.json @@ -18,8 +18,9 @@ } }, "scripts": { - "test": "bun test", - "test:ci": "bun test" + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" }, "dependencies": { "@uncaged/json-cas": "^0.5.3", @@ -34,12 +35,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-agent-claude-code" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/workflow-agent-claude-code/src/claude-code.ts b/packages/workflow-agent-claude-code/src/claude-code.ts index 3f68106..32d7f77 100644 --- a/packages/workflow-agent-claude-code/src/claude-code.ts +++ b/packages/workflow-agent-claude-code/src/claude-code.ts @@ -152,9 +152,7 @@ async function runClaudeCode(ctx: AgentContext): Promise { } catch (err) { log( "5VKR8N3Q", - "resume failed for session %s, falling back to fresh run: %s", - cachedSessionId, - err, + `resume failed for session ${cachedSessionId}, falling back to fresh run: ${err}`, ); } } diff --git a/packages/workflow-agent-claude-code/vitest.config.ts b/packages/workflow-agent-claude-code/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-agent-claude-code/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/packages/workflow-agent-hermes/package.json b/packages/workflow-agent-hermes/package.json index 5039398..5af99bf 100644 --- a/packages/workflow-agent-hermes/package.json +++ b/packages/workflow-agent-hermes/package.json @@ -18,8 +18,9 @@ } }, "scripts": { - "test": "bun test", - "test:ci": "bun test __tests__/*.test.ts" + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" }, "dependencies": { "@uncaged/json-cas": "^0.5.3", @@ -35,12 +36,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-agent-hermes" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "engines": { "bun": ">= 1.0.0" diff --git a/packages/workflow-agent-hermes/vitest.config.ts b/packages/workflow-agent-hermes/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-agent-hermes/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/packages/workflow-dashboard/package.json b/packages/workflow-dashboard/package.json index ffa5288..0e18696 100644 --- a/packages/workflow-dashboard/package.json +++ b/packages/workflow-dashboard/package.json @@ -5,7 +5,9 @@ "type": "module", "scripts": { "dev": "bun server.ts", - "build": "vite build" + "build": "vite build", + "test": "vitest run", + "test:ci": "vitest run" }, "dependencies": { "@base-ui/react": "^1.5.0", diff --git a/packages/workflow-protocol/package.json b/packages/workflow-protocol/package.json index a4711f7..2b957d7 100644 --- a/packages/workflow-protocol/package.json +++ b/packages/workflow-protocol/package.json @@ -14,6 +14,11 @@ "import": "./dist/index.js" } }, + "scripts": { + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" + }, "dependencies": { "@uncaged/json-cas": "^0.5.3", "@uncaged/json-cas-fs": "^0.5.3" @@ -26,12 +31,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-protocol" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/workflow-protocol/src/__tests__/types.test.ts b/packages/workflow-protocol/src/__tests__/types.test.ts index 2c1a63e..9f0289e 100644 --- a/packages/workflow-protocol/src/__tests__/types.test.ts +++ b/packages/workflow-protocol/src/__tests__/types.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import type { StartNodePayload, StepRecord, Target } from "../types.js"; describe("Protocol types for thread/edge location", () => { diff --git a/packages/workflow-protocol/vitest.config.ts b/packages/workflow-protocol/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-protocol/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/packages/workflow-util-agent/package.json b/packages/workflow-util-agent/package.json index a2b4051..4c8f29b 100644 --- a/packages/workflow-util-agent/package.json +++ b/packages/workflow-util-agent/package.json @@ -15,8 +15,9 @@ } }, "scripts": { - "test": "bun test", - "test:ci": "bun test" + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" }, "dependencies": { "@uncaged/json-cas": "^0.5.3", @@ -34,12 +35,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-util-agent" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/workflow-util-agent/src/__tests__/parseArgv.test.ts b/packages/workflow-util-agent/src/__tests__/parseArgv.test.ts index ec5dddc..903a11f 100644 --- a/packages/workflow-util-agent/src/__tests__/parseArgv.test.ts +++ b/packages/workflow-util-agent/src/__tests__/parseArgv.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; describe("parseArgv empty prompt error message", () => { let stderrOutput: string; diff --git a/packages/workflow-util-agent/src/build-output-format-instruction.ts b/packages/workflow-util-agent/src/build-output-format-instruction.ts index f857a03..8db5c7b 100644 --- a/packages/workflow-util-agent/src/build-output-format-instruction.ts +++ b/packages/workflow-util-agent/src/build-output-format-instruction.ts @@ -214,7 +214,7 @@ function getConstValue(propSchema: JSONSchema): string { function buildVariantBlock(variant: JSONSchema, discriminant: string): string { const props = extractSchemaProperties(variant); const value = getConstValue( - ((variant.properties as Record) ?? {})[discriminant] ?? {}, + (variant.properties as Record)?.[discriminant] ?? {}, ); const yamlExample = buildYamlExampleBlock(props); const fieldList = buildFieldList(props); diff --git a/packages/workflow-util-agent/tsconfig.json b/packages/workflow-util-agent/tsconfig.json index 0424b27..27c4bed 100644 --- a/packages/workflow-util-agent/tsconfig.json +++ b/packages/workflow-util-agent/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "dist" }, "include": ["src"], - "references": [{ "path": "../workflow-protocol" }] + "references": [{ "path": "../workflow-protocol" }, { "path": "../workflow-util" }] } diff --git a/packages/workflow-util-agent/vitest.config.ts b/packages/workflow-util-agent/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-util-agent/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/packages/workflow-util/package.json b/packages/workflow-util/package.json index d42fae8..007274c 100644 --- a/packages/workflow-util/package.json +++ b/packages/workflow-util/package.json @@ -14,6 +14,11 @@ "import": "./dist/index.js" } }, + "scripts": { + "prepublishOnly": "echo 'Use bun run release from repo root' && exit 1", + "test": "vitest run", + "test:ci": "vitest run" + }, "dependencies": {}, "devDependencies": { "typescript": "^5.8.3" @@ -23,12 +28,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/shazhou-ww/uncaged-workflow.git", + "url": "https://git.shazhou.work/uncaged/workflow.git", "directory": "packages/workflow-util" }, - "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme", + "homepage": "https://git.shazhou.work/uncaged/workflow#readme", "bugs": { - "url": "https://github.com/shazhou-ww/uncaged-workflow/issues" + "url": "https://git.shazhou.work/uncaged/workflow/issues" }, "license": "MIT" } diff --git a/packages/workflow-util/src/__tests__/ulid-timestamp.test.ts b/packages/workflow-util/src/__tests__/ulid-timestamp.test.ts index 7e25cf3..742d2bb 100644 --- a/packages/workflow-util/src/__tests__/ulid-timestamp.test.ts +++ b/packages/workflow-util/src/__tests__/ulid-timestamp.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "bun:test"; +import { describe, expect, it } from "vitest"; import { extractUlidTimestamp, generateUlid } from "../ulid.js"; describe("extractUlidTimestamp", () => { diff --git a/packages/workflow-util/vitest.config.ts b/packages/workflow-util/vitest.config.ts new file mode 100644 index 0000000..cf90b0a --- /dev/null +++ b/packages/workflow-util/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + passWithNoTests: true, + }, +}); diff --git a/scripts/publish-all.mjs b/scripts/publish-all.mjs index a8ed36e..b8cd854 100644 --- a/scripts/publish-all.mjs +++ b/scripts/publish-all.mjs @@ -21,6 +21,7 @@ const publishOrder = [ "workflow-util-agent", "workflow-agent-hermes", "workflow-agent-builtin", + "workflow-agent-claude-code", "cli-workflow", ]; @@ -59,7 +60,7 @@ let failed = false; for (const name of publishOrder) { const pkgDir = join(root, "packages", name); const tagFlag = tag ? `--tag ${tag}` : ""; - const cmd = `npm publish --access public ${tagFlag}`; + const cmd = `npm publish --access public --ignore-scripts ${tagFlag}`; console.log(`📦 ${name}...`); diff --git a/tsconfig.json b/tsconfig.json index c2c0557..78fa99c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ { "path": "packages/workflow-util-agent" }, { "path": "packages/workflow-agent-hermes" }, { "path": "packages/workflow-agent-builtin" }, + { "path": "packages/workflow-agent-claude-code" }, { "path": "packages/cli-workflow" } ] } diff --git a/workflows/solve-issue.yaml b/workflows/solve-issue.yaml new file mode 100644 index 0000000..eed7ff8 --- /dev/null +++ b/workflows/solve-issue.yaml @@ -0,0 +1,202 @@ +name: "solve-issue" +description: "TDD-driven issue resolution adapted for the workflow monorepo with bun + vitest" +roles: + planner: + description: "Analyzes issue and outputs a TDD test spec" + goal: "You are a planning agent. You analyze Gitea issues and produce a TDD test specification that downstream roles will implement and verify." + capabilities: + - issue-analysis + - planning + procedure: | + On first run (no previous steps): + 1. Read the issue and all comments from Gitea using `tea issues -r ` + 2. Look for project conventions files (CLAUDE.md, CONTRIBUTING.md) 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 -r ` (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): + 1. Read the tester's output from the previous step to understand what's wrong with the spec + 2. Revise the test spec accordingly + + After producing the test spec: + 1. Store it via `uwf cas put-text ""` and capture the returned hash + 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: + - properties: + $status: { const: "ready" } + plan: { type: string } + repoPath: { type: string } + required: [$status, plan, repoPath] + - properties: + $status: { const: "insufficient_info" } + required: [$status] + developer: + description: "TDD implementation per test spec" + goal: "You are a developer agent. You implement code changes following TDD — write tests first, then implementation." + capabilities: + - 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 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/- -b fix/- origin/main` + - `cd .worktrees/fix/- && bun install` + 4. If bounced back from reviewer or tester (branch already exists): + - cd into the existing worktree under `.worktrees/fix/-` + - `git fetch origin && git rebase origin/main` + 5. ALL subsequent work must happen inside the worktree directory. + + Then implement TDD: + 6. Read the test spec from CAS: `uwf cas get ` (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 (use vitest) + 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: + 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)." + capabilities: + - code-review + - static-analysis + procedure: | + 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 + 2. If the branch doesn't correspond to the issue, flag it in your output and reject + + Then perform code review: + Hard checks (must all pass): + 3. `bun run build` — no build errors + 4. `bunx biome check` — no lint violations + 5. TypeScript strict mode — no type errors + + Soft checks (review against project conventions from CLAUDE.md): + - Functional-first: functions + types, no classes (except for errors or third-party requirements) + - Named exports only, no default exports + - No optional properties (use `T | null` instead of `?:`) + - Folder module discipline: index.ts only re-exports, types in types.ts + - Crockford Base32 log tags (8-char, unique per call site) + - No `console.log` in production code (use createLogger from @uncaged/workflow-util) + - No dynamic imports in production code + + Only review standards compliance. Do NOT test functionality. + If rejecting, you MUST explain the specific reason in your output. + output: "Explain your decision with specific file/line references. Set $status to approved (with branch/worktree) or rejected (with comments)." + frontmatter: + oneOf: + - properties: + $status: { const: "approved" } + branch: { type: string } + worktree: { type: string } + required: [$status, branch, worktree] + - properties: + $status: { const: "rejected" } + comments: { type: string } + 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: | + 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 ` (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 + - fix_code: tests fail or implementation doesn't match spec → send back to developer + - fix_spec: the spec itself is wrong or incomplete → send back to planner + output: "Report test results per scenario. Set $status to passed (with branch/worktree), fix_code (with report), or fix_spec (with report)." + frontmatter: + oneOf: + - properties: + $status: { const: "passed" } + branch: { type: string } + worktree: { type: string } + required: [$status, branch, worktree] + - properties: + $status: { const: "fix_code" } + report: { type: string } + required: [$status, report] + - properties: + $status: { const: "fix_spec" } + report: { type: string } + required: [$status, report] + committer: + description: "Commits and creates PR" + goal: "You are a committer agent. You create a clean commit and push a PR linking the original issue." + capabilities: [] + procedure: | + 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 ` + - 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 --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 to the repo root (parent of .worktrees) + - `git worktree remove ` + output: "Include PR URL on success or error log on failure. Set $status to committed (with prUrl) or hook_failed (with error)." + frontmatter: + oneOf: + - properties: + $status: { const: "committed" } + prUrl: { type: string } + required: [$status, prUrl] + - properties: + $status: { const: "hook_failed" } + error: { type: string } + required: [$status, error] +graph: + $START: + _: { role: "planner", prompt: "Analyze the issue and produce an implementation plan." } + planner: + 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: + 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 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." } + fix_spec: { role: "planner", prompt: "Tests found spec issues: {{{report}}}. Revise the test spec." } + passed: { role: "committer", prompt: "All tests passed. Commit and push branch {{{branch}}} from {{{worktree}}}." } + committer: + hook_failed: { role: "developer", prompt: "Push hook failed: {{{error}}}. Fix and re-submit." } + committed: { role: "$END", prompt: "PR created: {{{prUrl}}}. Workflow complete." }