From 9ab6291a418d4b0e86cdc45912b9fcf215151b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Sun, 24 May 2026 16:56:19 +0000 Subject: [PATCH] fix(workflow): add --repo flag to tea pr create in worktree dirs Fixes #474 --- .workflows/solve-issue.yaml | 5 +- .../solve-issue-tea-worktree.test.ts | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts diff --git a/.workflows/solve-issue.yaml b/.workflows/solve-issue.yaml index 21a2371..d132095 100644 --- a/.workflows/solve-issue.yaml +++ b/.workflows/solve-issue.yaml @@ -137,8 +137,11 @@ roles: 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 --title "..." --description "..."` + 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 5. After PR creation, clean up the worktree: - `cd ~/repos/workflow` - `git worktree remove ~/repos/workflow-worktrees/fix/-` 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 new file mode 100644 index 0000000..cd38a4c --- /dev/null +++ b/packages/cli-workflow/src/__tests__/solve-issue-tea-worktree.test.ts @@ -0,0 +1,98 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import type { WorkflowPayload } from "@uncaged/workflow-protocol"; +import { describe, expect, test } from "vitest"; +import { parse } from "yaml"; + +/** + * Test: Issue #474 - tea pr create fails in git worktree directories + * + * This test verifies that the solve-issue workflow's committer role + * includes the --repo flag when running tea pr create, which fixes + * the "path segment [0] is empty" error in worktree directories. + */ + +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"); + + test("committer procedure should include --repo flag in tea pr create command", async () => { + const yamlContent = await readFile(workflowPath, "utf-8"); + const workflow = parse(yamlContent) as WorkflowPayload; + + expect(workflow.roles.committer).toBeDefined(); + const committerProcedure = workflow.roles.committer?.procedure; + expect(committerProcedure).toBeDefined(); + + // Verify the procedure includes tea pr create with --repo flag + expect(committerProcedure).toContain("tea pr create"); + expect(committerProcedure).toContain("--repo"); + + // Verify the --repo flag appears before or together with tea pr create + // This ensures the command is: tea pr create --repo ... + const teaPrCreateMatch = committerProcedure?.match(/tea pr create[^\n]*/); + expect(teaPrCreateMatch).not.toBeNull(); + + if (teaPrCreateMatch) { + const teaCommandLine = teaPrCreateMatch[0]; + expect(teaCommandLine).toContain("--repo"); + } + }); + + test("committer procedure should mention repo extraction from git remote", async () => { + const yamlContent = await readFile(workflowPath, "utf-8"); + const workflow = parse(yamlContent) as WorkflowPayload; + + const committerProcedure = workflow.roles.committer?.procedure; + expect(committerProcedure).toBeDefined(); + + // Verify the procedure mentions extracting repo info from git remote + // This ensures fallback logic is documented + expect(committerProcedure).toMatch(/git remote/i); + }); + + test("committer procedure should include error handling for tea failures", async () => { + const yamlContent = await readFile(workflowPath, "utf-8"); + const workflow = parse(yamlContent) as WorkflowPayload; + + const committerProcedure = workflow.roles.committer?.procedure; + expect(committerProcedure).toBeDefined(); + + // Verify the procedure includes error handling guidance + // This ensures we capture failures and provide actionable output + expect(committerProcedure).toMatch(/error|fail/i); + }); + + test("workflow should be parseable as valid WorkflowPayload", async () => { + const yamlContent = await readFile(workflowPath, "utf-8"); + const workflow = parse(yamlContent) as WorkflowPayload; + + // Basic structure validation + expect(workflow.name).toBe("solve-issue"); + expect(workflow.roles).toBeDefined(); + expect(workflow.conditions).toBeDefined(); + expect(workflow.graph).toBeDefined(); + + // Verify committer role exists with required fields + expect(workflow.roles.committer).toBeDefined(); + expect(workflow.roles.committer?.description).toBeDefined(); + expect(workflow.roles.committer?.goal).toBeDefined(); + expect(workflow.roles.committer?.procedure).toBeDefined(); + expect(workflow.roles.committer?.output).toBeDefined(); + expect(workflow.roles.committer?.frontmatter).toBeDefined(); + }); + + test("committer frontmatter schema should require success field", async () => { + const yamlContent = await readFile(workflowPath, "utf-8"); + // Parse as any to access the raw YAML structure (frontmatter is inline JSON Schema in YAML) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const workflow = parse(yamlContent) as any; + + const frontmatter = workflow.roles.committer?.frontmatter; + expect(frontmatter).toBeDefined(); + expect(frontmatter?.type).toBe("object"); + expect(frontmatter?.properties?.success).toBeDefined(); + expect(frontmatter?.properties?.success?.type).toBe("boolean"); + expect(frontmatter?.required).toContain("success"); + }); +});