Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const commandDefinitions: Record<string, CommandDefinition> = {
description: "Fix PR feedback, push updates, and reply",
agent: "worker",
templatePath: "commands/pr/fix.md",
subtask: false,
},
"pr/review": {
description: "Review the current PR and publish review feedback",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/commands/pr/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ Run `git push` and use its output as the source of truth.
### Create PR

Use `pr_sync` to create the pull request:
- This step is PR creation only
- Omit `review`, `replies`, `commentBody`, and `commitId` entirely unless you are intentionally updating or reviewing an existing PR instead of creating one
- Generate a concise title (max 70 chars) summarizing the change and store it as `<pr-title>`
- Generate a short description that briefly describes the intent and scope
- Pass `<current-branch>` as `head` when it is available so PR creation does not depend on local upstream inference
Expand Down
22 changes: 22 additions & 0 deletions packages/core/test/pr-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ describe("pr_sync", () => {
assert.equal(result.action, "approved");
});

test("ignores an empty review object", async () => {
const executedCommands: string[] = [];
const shell = createMockShell(executedCommands, [
{
contains: "gh pr create",
stdout: "https://github.com/acme/repo/pull/12\n",
},
]);

const tool = createPrSyncTool(shell);
const output = await tool.execute({
title: "Create PR without review payload",
body: "Omit review-only fields during PR creation.",
review: {},
}, createToolContextForDirectory("/tmp/repo"));

const result = JSON.parse(output);
assert.equal(result.url, "https://github.com/acme/repo/pull/12");
assert.equal(result.action, "created");
assert.equal(executedCommands.length, 1);
});

test("updates and approves an existing PR in one call", async () => {
const executedCommands: string[] = [];
const shell = createMockShell(executedCommands, [
Expand Down
18 changes: 15 additions & 3 deletions packages/core/tools/pr-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ type PrSyncArgs = {
commentBody?: string;
};

function normalizeReviewInput(review?: ReviewInput): ReviewInput | undefined {
if (!review) {
return undefined;
}

const hasBody = Boolean(review.body?.trim());
const hasComments = (review.comments?.length ?? 0) > 0;
const hasApprove = "approve" in review && review.approve === true;

return hasBody || hasComments || hasApprove ? review : undefined;
}

function renderPrBody(args: PrSyncArgs) {
if (args.body?.trim()) {
return args.body.trim();
Expand Down Expand Up @@ -400,12 +412,12 @@ export function createPrSyncTool($: Shell) {
commitId: {
type: "string",
optional: true,
description: "Commit ID to associate with the approval",
description: "Commit SHA to anchor review comments to; omit unless sending review comments",
},
review: {
type: "json",
optional: true,
description: "Structured PR review submission with event, optional body, commitId, and inline comments",
description: "Optional structured PR review submission; omit the field entirely unless submitting a review body, inline comments, or approval",
},
replies: {
type: "json",
Expand All @@ -420,7 +432,7 @@ export function createPrSyncTool($: Shell) {
},
async execute(args: PrSyncArgs, ctx: ToolExecutionContext) {
const body = renderPrBody(args);
const review = args.review;
const review = normalizeReviewInput(args.review);
const metadataUpdate = hasMetadataUpdate(args, body);
const existingPrActions = requiresExistingPullRequest(args, review);

Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/.opencode/commands/pr/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ Run `git push` and use its output as the source of truth.
### Create PR

Use `kompass_pr_sync` to create the pull request:
- This step is PR creation only
- Omit `review`, `replies`, `commentBody`, and `commitId` entirely unless you are intentionally updating or reviewing an existing PR instead of creating one
- Generate a concise title (max 70 chars) summarizing the change and store it as `<pr-title>`
- Generate a short description that briefly describes the intent and scope
- Pass `<current-branch>` as `head` when it is available so PR creation does not depend on local upstream inference
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ const opencodeToolCreators: Record<string, OpenCodeToolCreator> = {
})).describe("Checklist sections rendered as markdown").optional(),
draft: tool.schema.boolean().describe("Create as draft PR").optional(),
refUrl: tool.schema.string().describe("Optional PR URL to update").optional(),
commitId: tool.schema.string().describe("Commit SHA to anchor review comments to").optional(),
commitId: tool.schema.string().describe("Commit SHA to anchor review comments to; omit unless sending review comments").optional(),
review: tool.schema.object({
body: tool.schema.string().describe("Optional review summary body").optional(),
comments: tool.schema.array(tool.schema.object({
Expand All @@ -218,7 +218,7 @@ const opencodeToolCreators: Record<string, OpenCodeToolCreator> = {
? { approve: tool.schema.boolean().describe("Approve the PR with this review comment").optional() }
: {}
),
}).describe("Structured review submission").optional(),
}).describe("Optional structured review submission; omit the field entirely unless submitting a review body, inline comments, or approval").optional(),
replies: tool.schema.array(tool.schema.object({
inReplyTo: tool.schema.number().int().describe("Existing review comment ID to reply to"),
body: tool.schema.string().describe("Reply body"),
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/test/commands-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ describe("applyCommandsConfig", () => {
await applyCommandsConfig(cfg as never, process.cwd());

assert.ok(cfg.command);
assert.equal(cfg.command!["pr/fix"]?.subtask, false);
assert.equal(cfg.command!["pr/review"]?.subtask, true);
assert.equal(cfg.command!["dev"]?.subtask, true);
assert.equal(cfg.command!["ship"]?.subtask, true);
Expand All @@ -353,6 +354,7 @@ describe("applyCommandsConfig", () => {
await applyCommandsConfig(cfg as never, process.cwd());

assert.ok(cfg.command);
assert.equal(cfg.command!["pr/fix"]?.subtask, false);
assert.equal(cfg.command!["pr/review"]?.subtask, false);
assert.equal(cfg.command!["dev"]?.subtask, false);
assert.equal(cfg.command!["ship"]?.subtask, false);
Expand Down
Loading