From 2b89dd3c1b550cca940f224bac493f740975bbea Mon Sep 17 00:00:00 2001 From: xmtp-coder-agent <> Date: Fri, 3 Apr 2026 06:31:57 +0000 Subject: [PATCH 1/2] feat: add pirate task prompt instruction --- README.md | 2 +- docs/plans/2026-04-03-issue-88-design.md | 69 ++++++++++++++++++++ docs/plans/2026-04-03-issue-88-tasks.md | 80 ++++++++++++++++++++++++ src/handlers/create-task.test.ts | 15 ++--- src/handlers/create-task.ts | 8 ++- 5 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 docs/plans/2026-04-03-issue-88-design.md create mode 100644 docs/plans/2026-04-03-issue-88-tasks.md diff --git a/README.md b/README.md index 4262f58..d4dedd9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ See [docs/github-app-setup.md](docs/github-app-setup.md) for step-by-step instru | `GITHUB_APP_WEBHOOK_SECRET` | Yes | GitHub App webhook secret for payload verification | | `GITHUB_AGENT_USERNAME` | No | GitHub username of the designated coder agent (default: `xmtp-coder-agent`) | | `PORT` | No | HTTP server port (default: `3000`) | -| `PROMPT` | No | Custom prompt text — issue URL is always appended (`create_task` only) | +| `PROMPT` | No | Custom prompt text — pirate instruction and issue URL are appended (`create_task` only) | ## Running diff --git a/docs/plans/2026-04-03-issue-88-design.md b/docs/plans/2026-04-03-issue-88-design.md new file mode 100644 index 0000000..6211881 --- /dev/null +++ b/docs/plans/2026-04-03-issue-88-design.md @@ -0,0 +1,69 @@ +# Issue 88 Design + +## Summary + +Issue 88 requires the `create_task` flow to instruct the downstream coding agent to speak like a pirate when a task is first created. The change should affect only the initial task input payload built by `CreateTaskHandler`, while preserving the existing optional base prompt and required issue URL context. + +## Project Goals & Non-Goals + +**Goals** + +- Add a deterministic pirate-speaking instruction to new task input created from `issues.assigned`. +- Preserve the configured base prompt when `PROMPT` is set. +- Preserve the issue URL in the task input so task creation still points the agent at the source issue. +- Cover the new behavior with focused unit tests in the existing `create-task` handler test suite. + +**Non-Goals** + +- Changing comment forwarding behavior for PR comments, issue comments, or failed checks. +- Introducing configurable personas or new environment variables. +- Changing task naming, template selection, permission checks, or GitHub commenting behavior. + +## Context + +- **Catalyst:** [Issue #88](https://github.com/xmtplabs/coder-action/issues/88) +- **Relevant code:** `src/handlers/create-task.ts`, `src/handlers/create-task.test.ts`, `README.md` +- **Impact area:** Initial task prompt construction for the `create_task` handler + +## System Design + +`CreateTaskHandler.run()` currently builds the task input as either `PROMPT + issue URL` or only the issue URL. The handler will instead build the input from ordered sections: + +1. Optional configured base prompt +2. Fixed pirate-speaking instruction +3. Issue URL + +The implementation should keep the formatting simple and deterministic by joining only present sections with blank lines. This keeps existing behavior stable while ensuring every newly created task receives the persona instruction. + +## Libraries & Utilities Required + +**External dependencies:** None + +**Internal modules:** + +| Module | Path | Purpose | +|--------|------|---------| +| CreateTaskHandler | `src/handlers/create-task.ts` | Builds task input and creates tasks | +| CreateTaskHandler tests | `src/handlers/create-task.test.ts` | Verifies prompt construction and handler behavior | + +## Testing & Validation + +### Acceptance Criteria + +1. WHEN `CreateTaskHandler` creates a new task THE SYSTEM SHALL include an instruction telling the agent to speak like a pirate in the task input. +2. WHEN a configured base prompt exists THE SYSTEM SHALL preserve that prompt and append the pirate instruction before the issue URL. +3. WHEN no configured base prompt exists THE SYSTEM SHALL still include both the pirate instruction and the issue URL in the task input. +4. THE SYSTEM SHALL NOT change task creation behavior for existing-task reuse, permission checks, template selection, or GitHub issue commenting. + +### Edge Cases + +- Ensure the new instruction is present exactly once when a base prompt exists. +- Ensure blank-line formatting remains stable so tests can assert exact prompt content. +- Ensure existing task reuse path does not attempt to rebuild or send a new task input. + +### Verification Commands + +- `bun test src/handlers/create-task.test.ts` +- `bun test` +- `bun run typecheck` +- `bun run lint` diff --git a/docs/plans/2026-04-03-issue-88-tasks.md b/docs/plans/2026-04-03-issue-88-tasks.md new file mode 100644 index 0000000..9f569a5 --- /dev/null +++ b/docs/plans/2026-04-03-issue-88-tasks.md @@ -0,0 +1,80 @@ +# Issue 88 Task Decomposition + +> **Source spec:** `docs/plans/2026-04-03-issue-88-design.md` +> **Generated:** 2026-04-03 + +**Goal:** Add a pirate-speaking instruction to newly created task input without regressing the existing create-task flow. + +**Phases:** +1. Prompt construction — update the task input builder and focused tests +2. Verification — run targeted and full project validation + +### Task 1: Add pirate instruction to create-task input + +**Files:** +- Modify: `src/handlers/create-task.ts` +- Modify: `src/handlers/create-task.test.ts` + +- [ ] **Step 1: Write failing test** + ```ts + test("includes pirate instruction in task input", async () => { + github.checkActorPermission.mockResolvedValue(true); + coder.getTask.mockResolvedValue(null); + coder.createTask.mockResolvedValue(mockTask); + + const handler = new CreateTaskHandler( + coder, + github as unknown as import("../github-client").GitHubClient, + baseInputs, + issueContext, + logger, + ); + await handler.run(); + + const createCall = coder.createTask.mock.calls[0] as unknown as [ + string, + { input: string }, + ]; + expect(createCall[1].input).toContain("speak like a pirate"); + }); + ``` + +- [ ] **Step 2: Verify test fails** + Run: `bun test src/handlers/create-task.test.ts` + Expected: FAIL because the task input currently contains only the optional prompt and issue URL. + +- [ ] **Step 3: Implement minimal code** + ```ts + const fullPrompt = [this.inputs.prompt, pirateInstruction, this.context.issueUrl] + .filter((part) => part != null && part.length > 0) + .join("\n\n"); + ``` + +- [ ] **Step 4: Verify test passes** + Run: `bun test src/handlers/create-task.test.ts` + Expected: PASS + +- [ ] **Step 5: Commit** + `git add src/handlers/create-task.ts src/handlers/create-task.test.ts docs/plans/2026-04-03-issue-88-*.md && git commit -m "feat: add pirate task prompt instruction"` + +### Task 2: Verification + +**Files:** +- Modify: none + +- [ ] **Step 1: Run focused verification** + Run: `bun test src/handlers/create-task.test.ts` + Expected: PASS + +- [ ] **Step 2: Run full verification** + Run: `bun test && bun run typecheck && bun run lint` + Expected: PASS + +## Requirement Coverage Matrix + +| # | EARS Requirement | Task(s) | +|---|-------------------|---------| +| 1 | WHEN `CreateTaskHandler` creates a new task THE SYSTEM SHALL include an instruction telling the agent to speak like a pirate in the task input. | Task 1, Task 2 | +| 2 | WHEN a configured base prompt exists THE SYSTEM SHALL preserve that prompt and append the pirate instruction before the issue URL. | Task 1, Task 2 | +| 3 | WHEN no configured base prompt exists THE SYSTEM SHALL still include both the pirate instruction and the issue URL in the task input. | Task 1, Task 2 | +| 4 | THE SYSTEM SHALL NOT change task creation behavior for existing-task reuse, permission checks, template selection, or GitHub issue commenting. | Task 1, Task 2 | diff --git a/src/handlers/create-task.test.ts b/src/handlers/create-task.test.ts index f1f558a..36be205 100644 --- a/src/handlers/create-task.test.ts +++ b/src/handlers/create-task.test.ts @@ -81,7 +81,7 @@ describe("CreateTaskHandler", () => { }); // AC #4: Issue URL appended to prompt - test("appends issue URL to prompt", async () => { + test("appends pirate instruction and issue URL to prompt", async () => { github.checkActorPermission.mockResolvedValue(true); coder.getTask.mockResolvedValue(null); coder.createTask.mockResolvedValue(mockTask); @@ -101,14 +101,13 @@ describe("CreateTaskHandler", () => { { input: string }, ]; const taskInput = createCall[1].input; - expect(taskInput).toContain("Fix the bug"); - expect(taskInput).toEndWith( - "\n\nhttps://github.com/xmtp/libxmtp/issues/42", + expect(taskInput).toBe( + "Fix the bug\n\nSpeak like a pirate in all of your responses.\n\nhttps://github.com/xmtp/libxmtp/issues/42", ); }); - // AC #4: Only issue URL when no prompt provided - test("uses only issue URL when no prompt provided", async () => { + // AC #4: Pirate instruction retained when no prompt provided + test("uses pirate instruction and issue URL when no prompt provided", async () => { github.checkActorPermission.mockResolvedValue(true); coder.getTask.mockResolvedValue(null); coder.createTask.mockResolvedValue(mockTask); @@ -126,7 +125,9 @@ describe("CreateTaskHandler", () => { string, { input: string }, ]; - expect(createCall[1].input).toBe(issueContext.issueUrl); + expect(createCall[1].input).toBe( + "Speak like a pirate in all of your responses.\n\nhttps://github.com/xmtp/libxmtp/issues/42", + ); }); // AC #5: Existing running task diff --git a/src/handlers/create-task.ts b/src/handlers/create-task.ts index dac738f..e117168 100644 --- a/src/handlers/create-task.ts +++ b/src/handlers/create-task.ts @@ -15,6 +15,8 @@ export interface IssueContext { senderLogin: string; } +const PIRATE_INSTRUCTION = "Speak like a pirate in all of your responses."; + export class CreateTaskHandler { constructor( private readonly coder: CoderClient, @@ -85,9 +87,9 @@ export class CreateTaskHandler { } // 4. Build prompt - const fullPrompt = this.inputs.prompt - ? `${this.inputs.prompt}\n\n${this.context.issueUrl}` - : this.context.issueUrl; + const fullPrompt = [this.inputs.prompt, PIRATE_INSTRUCTION, this.context.issueUrl] + .filter((part) => part != null && part.length > 0) + .join("\n\n"); // 5. Get template and create task const templateName = this.resolveTemplateName(); From 60147f2b6661ebcca998b26782423137893b9f87 Mon Sep 17 00:00:00 2001 From: xmtp-coder-agent <> Date: Fri, 3 Apr 2026 06:33:15 +0000 Subject: [PATCH 2/2] fix: format create task prompt builder --- src/handlers/create-task.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/handlers/create-task.ts b/src/handlers/create-task.ts index e117168..456bd66 100644 --- a/src/handlers/create-task.ts +++ b/src/handlers/create-task.ts @@ -87,7 +87,11 @@ export class CreateTaskHandler { } // 4. Build prompt - const fullPrompt = [this.inputs.prompt, PIRATE_INSTRUCTION, this.context.issueUrl] + const fullPrompt = [ + this.inputs.prompt, + PIRATE_INSTRUCTION, + this.context.issueUrl, + ] .filter((part) => part != null && part.length > 0) .join("\n\n");