opencode-config/.opencode/tools/create-pr.ts
Sergey 0c1e3e7d39
feat(create-pr): enforce Watch out and Pending headings in PR body (#207)
* feat(create-pr): enforce Watch out and Pending headings

* test(create-pr): cover Watch out and Pending headings

* docs(handoff): add handoff and ADR for PR #207

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-08-01 03:28:35 +03:00

63 lines
No EOL
2.7 KiB
TypeScript

import { tool } from "@opencode-ai/plugin"
import { runGh, formatResult } from "./_shared"
const TITLE_REGEX = /^(feat|fix|chore|docs|refactor|test|style|perf)\([^)]+\): .{1,72}$/
const CYRILLIC = /[\u0400-\u04FF]/
const RULES = `Rules:
- Title: type(scope): description (<=72 chars description)
- Types: feat, fix, chore, docs, refactor, test, style, perf
- Scope is mandatory
- Title in English only
- Body must contain '## Что сделано' heading
- Body must contain '## Почему' heading
- Body must contain '## Watch out' heading
- Body must contain '## Pending' heading
- Body in Russian (must contain Cyrillic)`
export default tool({
description: "Create a GitHub PR with title/body validation. Validates: title format type(scope): description (<=72), English title, body headings (## Что сделано, ## Почему, ## Watch out, ## Pending), body in Russian. If issue_number provided, appends 'Closes #N' to body. Returns PR URL on success.",
args: {
title: tool.schema.string().describe("PR title (conventional format: type(scope): description)"),
body: tool.schema.string().describe("PR body in Russian with ## Что сделано, ## Почему, ## Watch out and ## Pending headings"),
issue_number: tool.schema.number().optional().describe("Issue number to link (appends 'Closes #N' to body)"),
repo: tool.schema.string().optional().describe("Optional repo (owner/name). If omitted, gh auto-detects from context.worktree."),
},
async execute(args, context) {
const title = args.title
let body = args.body
if (!TITLE_REGEX.test(title)) {
return `❌ PR title must match format: type(scope): description\n\n${RULES}`
}
if (CYRILLIC.test(title)) {
return `❌ PR title must be in English\n\n${RULES}`
}
if (!body.includes("## Что сделано")) {
return `❌ PR body must contain '## Что сделано' heading\n\n${RULES}`
}
if (!body.includes("## Почему")) {
return `❌ PR body must contain '## Почему' heading\n\n${RULES}`
}
if (!body.includes("## Watch out")) {
return `❌ PR body must contain '## Watch out' heading\n\n${RULES}`
}
if (!body.includes("## Pending")) {
return `❌ PR body must contain '## Pending' heading\n\n${RULES}`
}
if (!CYRILLIC.test(body)) {
return `❌ PR body must be in Russian\n\n${RULES}`
}
if (args.issue_number) {
body = body + "\n\nCloses #" + args.issue_number
}
const r = runGh(["pr", "create", "--title", title, "--body", body], args.repo, { cwd: context.worktree })
if (r.status !== 0) {
return formatResult(r, "gh pr create")
}
return `PR created: ${r.stdout.trim()}`
},
})