import { tool } from "@opencode-ai/plugin" import { runGh, formatResult } from "./_shared" const TITLE_REGEX = /^(feat|fix|chore|docs|refactor|test|style|perf)\([^)]+\): .{1,80}$/ const CYRILLIC = /[\u0400-\u04FF]/ const RULES = `Rules: - Title: conventional format type(scope): description (≤80 chars, English only) - Body must contain '## Контекст' heading (зачем + текущее состояние) - Body must contain '## Задача' heading (что делаем) - Body must contain '## Контракты' heading (ожидаемое поведение / API) - Body must contain '## Инварианты' heading (правила без исключений) - Body must contain '## Граничные случаи' heading (что при ошибках) - Body must contain '## Влияние на связанные компоненты' heading (связанные файлы/оракулы/агенты/промпты/валидаторы; paired updates; «нет связанных компонентов» для тривиальных фич) - Body must contain '## Вне scope' heading (что НЕ делаем) - Body must contain '## Критерии приемки' heading (как проверяем) - Body must contain Cyrillic (Russian language required)` export default tool({ description: "Create a GitHub issue with title/body validation. Validates: title format type(scope): description (<=80), English title, body headings (## Контекст, ## Задача, ## Контракты, ## Инварианты, ## Граничные случаи, ## Влияние на связанные компоненты, ## Вне scope, ## Критерии приемки), body in Russian. Optional labels. Returns issue URL on success.", args: { title: tool.schema.string().describe("Issue title (conventional format: type(scope): description, <=80 chars)"), body: tool.schema.string().describe("Issue body in Russian with ## Контекст, ## Задача, ## Контракты, ## Инварианты, ## Граничные случаи, ## Влияние на связанные компоненты, ## Вне scope, ## Критерии приемки headings"), labels: tool.schema.array(tool.schema.string()).optional().describe("Labels to assign (e.g. ['bug', 'enhancement'])"), 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 const body = args.body if (!TITLE_REGEX.test(title)) { return `❌ Issue title must match format: type(scope): description\n\n${RULES}` } if (CYRILLIC.test(title)) { return `❌ Issue title must be in English\n\n${RULES}` } if (!body.includes("## Контекст")) { return `❌ Issue body must contain '## Контекст' heading\n\n${RULES}` } if (!body.includes("## Задача")) { return `❌ Issue body must contain '## Задача' heading\n\n${RULES}` } if (!body.includes("## Критерии приемки")) { return `❌ Issue body must contain '## Критерии приемки' heading\n\n${RULES}` } if (!body.includes("## Контракты")) { return `❌ Issue body must contain '## Контракты' heading\n\n${RULES}` } if (!body.includes("## Инварианты")) { return `❌ Issue body must contain '## Инварианты' heading\n\n${RULES}` } if (!body.includes("## Граничные случаи")) { return `❌ Issue body must contain '## Граничные случаи' heading\n\n${RULES}` } if (!body.includes("## Влияние на связанные компоненты")) { return `❌ Issue body must contain '## Влияние на связанные компоненты' heading\n\n${RULES}` } if (!body.includes("## Вне scope")) { return `❌ Issue body must contain '## Вне scope' heading\n\n${RULES}` } if (!CYRILLIC.test(body)) { return `❌ Issue body must be in Russian\n\n${RULES}` } const ghArgs = ["issue", "create", "--title", title, "--body", body] if (args.labels && args.labels.length > 0) { ghArgs.push("--label", args.labels.join(",")) } const r = runGh(ghArgs, args.repo, { cwd: context.worktree }) if (r.status !== 0) { return formatResult(r, "gh issue create") } return `Issue created: ${r.stdout.trim()}` }, })