/** * Jira tools — native pi tools over the tracker REST API * (Jira Server/DC, rest/api/2). Thin wrappers around lib/jira.ts. * * No MCP and no bash: these are first-class tools the agent can call directly. * URL comes from config/company.json; JIRA_TOKEN from the env. * * CONFIGURE: config/company.json → trackerUrl; env.sh → JIRA_TOKEN. * DISABLE: remove/rename this file from extensions/. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { issueGet, issueCreate, issueUpdate, issueComment, setStatus, search, linkMr, } from "./lib/jira.ts"; function tool(name: string, description: string, parameters: Record, run: (params: any) => Promise) { return { name, description, parameters, run }; } export default function jiraToolsExtension(pi: ExtensionAPI) { const tools = [ tool( "jira_issue_get", "Получить тикет Jira по ключу (summary, status, description).", { type: "object", properties: { key: { type: "string", description: "Ключ задачи, например PROJ-123" } }, required: ["key"] }, async (p) => issueGet(p.key), ), tool( "jira_issue_create", "Создать задачу в Jira.", { type: "object", properties: { projectKey: { type: "string", description: "Ключ проекта, например PROJ" }, summary: { type: "string", description: "Заголовок задачи" }, issuetype: { type: "string", description: "Тип: Bug, Task, Story и т.п." }, description: { type: "string", description: "Описание (Markdown/Plain)" }, priority: { type: "string", description: "Приоритет" }, labels: { type: "array", items: { type: "string" } }, assignee: { type: "string", description: "Исполнитель" }, }, required: ["projectKey", "summary", "issuetype"], }, async (p) => issueCreate(p), ), tool( "jira_issue_update", "Обновить поля задачи Jira (summary/description/status и т.п.). Status переводит по доступным переходам.", { type: "object", properties: { key: { type: "string", description: "Ключ задачи" }, summary: { type: "string" }, description: { type: "string" }, priority: { type: "string" }, labels: { type: "array", items: { type: "string" } }, assignee: { type: "string" }, status: { type: "string", description: "To Do / In Progress / Review / Done" }, }, required: ["key"], }, async (p) => issueUpdate(p.key, p), ), tool( "jira_issue_comment", "Добавить комментарий к тикету Jira.", { type: "object", properties: { key: { type: "string", description: "Ключ задачи" }, text: { type: "string", description: "Текст комментария" }, }, required: ["key", "text"], }, async (p) => issueComment(p.key, p.text), ), tool( "jira_issue_transition", "Перевести тикет Jira в статус (To Do / In Progress / Review / Done).", { type: "object", properties: { key: { type: "string", description: "Ключ задачи" }, target: { type: "string", description: "Целевой статус: To Do, In Progress, Review, Done" }, }, required: ["key", "target"], }, async (p) => setStatus(p.key, p.target), ), tool( "jira_search", "Найти тикеты Jira по JQL-запросу.", { type: "object", properties: { jql: { type: "string", description: "JQL, например 'project = PROJ AND status != Done'" }, maxResults: { type: "number", description: "Макс. число результатов" }, }, required: ["jql"], }, async (p) => search(p.jql, p.maxResults), ), tool( "jira_link_mr", "Привязать ссылку на merge request к тикету Jira (комментарий со ссылкой).", { type: "object", properties: { key: { type: "string", description: "Ключ задачи" }, mrUrl: { type: "string", description: "URL merge request" }, title: { type: "string", description: "Заголовок MR (по умолчанию 'Merge request')" }, }, required: ["key", "mrUrl"], }, async (p) => linkMr(p.key, p.mrUrl, p.title), ), ]; for (const t of tools) { pi.registerTool({ name: t.name, label: t.name, description: t.description, parameters: t.parameters as any, async execute(_id: string, params: unknown) { try { const out = await t.run(params ?? {}); return { content: [{ type: "text", text: JSON.stringify(out ?? {}, null, 2) }], details: {} }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); return { content: [{ type: "text", text: `jira error: ${msg}` }], isError: true, details: {}, }; } }, }); } }