147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
/**
|
|
* 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<string, unknown>, run: (params: any) => Promise<unknown>) {
|
|
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: {},
|
|
};
|
|
}
|
|
},
|
|
});
|
|
}
|
|
}
|