All checks were successful
CI / bootstrap (push) Successful in 7s
CI / lint (push) Successful in 17s
CI / typecheck (push) Successful in 25s
CI / test (3.12) (push) Successful in 2m48s
CI / test (3.13) (push) Successful in 1m41s
CI / test (3.14) (push) Successful in 1m34s
CI / complexity (push) Successful in 21s
77 lines
No EOL
4.5 KiB
TypeScript
77 lines
No EOL
4.5 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,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 = await runGh(ghArgs, args.repo, { cwd: context.worktree })
|
||
if (r.status !== 0) {
|
||
return formatResult(r, "gh issue create")
|
||
}
|
||
|
||
return `Issue created: ${r.stdout.trim()}`
|
||
},
|
||
}) |