Ticket AI について
Ticket AIは、チームのトラッカーからチケットの書き方を学び、その基準で下書きを測定するMCPサーバー兼コマンドラインツールです。分析はモデルを使わずに集計し、モデルによるチケット作成は任意です。
主な機能
- 01クローズ済みチケットから慣例を学習
- 02セクション、長さ、ラベル、言語を測定
- 03関連チケットと変更ファイルを検索
- 04下書きと既存チケットをレビュー
- 05未完了チケットを優先度順に確認
- 06任意でモデルを使ってチケットを作成
活用シーン
- 01チームのチケット慣例に沿って下書きを確認
- 02チケット作成前に関連する過去の作業を検索
- 03未完了項目を優先度順にレビュー
- 04CIでチケットレビューをゲート
MCP 対応
慣例の学習、テンプレートとコンテキストの取得、下書き・チケット・未完了項目のレビューに使う8つの読み取り専用MCPツールを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
uvx ticket-ai-mcpTICKET_AI_TRACKERWhich tracker to read: gitlab, jira or github.TICKET_AI_PROJECTThe project to measure. A GitLab path like acme/shop or its numeric id, a Jira project key like PROJ, or a GitHub owner/repo.TICKET_AI_GITLAB_URLBase URL of the GitLab instance, for example https://gitlab.example.com. Required when the tracker is gitlab.TICKET_AI_GITLAB_TOKENシークレットGitLab personal access token. The read_api scope is enough; nothing here writes.TICKET_AI_JIRA_URLBase URL of the Jira instance. Cloud or self-hosted Server and Data Center; which one is detected from the instance.TICKET_AI_JIRA_EMAILAccount email, for Jira Cloud's basic auth. Leave unset for a public board or for a Server personal access token.TICKET_AI_JIRA_TOKENシークレットJira API token from id.atlassian.com, or a Server personal access token on its own. Leave unset to read a public instance anonymously.TICKET_AI_GITHUB_TOKENシークレットGitHub token with read access to the repository's issues.TICKET_AI_REPOCheckout to search when gathering context for a subject. Defaults to the working directory, which on a wrong guess produces confident nonsense - the report names the directory it searched.TICKET_AI_TICKET_LANGUAGEForce the language tickets should be written in, overriding what was measured in the corpus. Leave unset unless the board is mid-switch: a measurement beats a setting.TICKET_AI_CACHE_DIRWhere the learned profile is kept between runs. Defaults to .ticket-ai/ under the working directory.ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
8掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| keep | integer | いいえ | |
| sample | integer | いいえ | |
| project | any | いいえ | |
| tracker | any | いいえ | |
| from_tickets | any | いいえ |
詳細
ticket-ai
チームが実際にどのようにチケットを書いているかを測定し、新しいチケットにもそれを求める MCP サーバーと CLI です。「このチケットはよくない」ではなく、「ここでリリースされた40件のチケットのうち31件には受け入れ条件のセクションがあり、このチケットにはありません」と示します。
「受け入れ条件」セクションがありません。 模範となるチケット40件のうち31件(78%)にあります。
チケットに関するアドバイスの多くは無料なので、無視されてしまいます。「受け入れ条件を追加する」「再現手順を含める」——誰もが聞いたことはありますが、誰も何も変えていません。自分たちのプロジェクトで実際に起きたことを数えれば、簡単には聞き流せませんし、チケットについての個人的な意見でもありません。
**この処理にはモデルを一切介しません。**チームの書き方を学習し、関連チケットを見つけ、下書きを評価するのは集計処理であり、キーもトラッカー以外へのネットワーク接続も必要ありません。チケットの作成にはモデルが必要なので、その部分はオプトインで、どのモデルを使うかは自分で選べます。 実際のボードを対象に実行してわかったこと →
インストール
uv tool install ticket-ai-mcp
設定は環境変数のみです。--token で渡したトークンは、シェルの履歴とプロセス一覧に残ります。
export TICKET_AI_TRACKER=gitlab
export TICKET_AI_PROJECT=acme/shop # or the numeric id
export TICKET_AI_GITLAB_URL=https://gitlab.example.com
export TICKET_AI_GITLAB_TOKEN=... # read_api scope is enough
公開プロジェクトではトークンは任意です。TICKET_AI_GITLAB_URL=https://gitlab.com を設定し、トークンを指定しなければ、公開ボードを読み取れます。自分のインスタンスを指定する前に、これが何をするのかを手早く試せます。
Jira と GitHub
# Jira — Cloud or self-hosted Server / Data Center. Which one you are on is
# detected from the instance; you do not have to say.
export TICKET_AI_TRACKER=jira
export TICKET_AI_PROJECT=PROJ
export TICKET_AI_JIRA_URL=https://acme.atlassian.net
# Leave the credentials unset for a public board — plenty answer without any.
# Cloud: the token comes from id.atlassian.com, and is not the password.
export [email protected]
export TICKET_AI_JIRA_TOKEN=...
# Self-hosted: a personal access token on its own, sent as a Bearer.
export TICKET_AI_JIRA_TOKEN=...
# Only if detection gets it wrong: cloud | server | auto (the default)
export TICKET_AI_JIRA_API=server
# GitHub
export TICKET_AI_TRACKER=github
export TICKET_AI_PROJECT=acme/shop
export TICKET_AI_GITHUB_TOKEN=...
mcp-name: io.github.syrian963/ticket-ai-mcp
1つのコマンド
ticket-ai learn # mine the tracker, cache the profile
ticket-ai style # what it learned
learn は1〜2分ほどかかります。ランキングには各チケットのコメントとリンクされたマージリクエストが必要なため、チケットごとに1〜2回、追加のリクエストが発生します。TICKET_AI_CACHE_DIR を設定した場合はそのディレクトリに、設定していない場合は .ticket-ai/ にキャッシュされるため、レビューのたびではなく一度だけ実行すれば済みます。
あとは次のとおりです。
ticket-ai learn --from '#412,#98' # or name the good ones yourself
ticket-ai context 'export is broken on mobile' # what already exists
ticket-ai gaps # declared template vs what arrives
ticket-ai draft --title '...' --file draft.md # check one before creating it
ticket-ai review '#42' # measure one that exists
ticket-ai open # every open ticket, worst first
習慣にする価値があるのは draft です。チケットを作成してから確認しても、レビューはもう後戻りできない段階です。すでにボードに通知されており、修正はすべて履歴の残る編集になってしまいます。
$ ticket-ai draft --title 'Filter kaputt' --file draft.md
Alignment with house style: 33% over 3 checks
### MEDIUM - The description is 116 characters.
The shortest quarter of tickets that shipped here start at 639; the median is 1079.
### MEDIUM - The ticket has no labels.
62% of the exemplars are labelled.
--from の内容はそのまま採用されます。フィルタリングも、選んだ内容に対するスコア付けもありません。3語でチケットを説明すれば、それがこのチームのチケットの書き方についての回答となり、プロファイルにもそのように記録されます。
MCP サーバーとして
{
"mcpServers": {
"ticket-ai": {
"command": "uvx",
"args": ["ticket-ai-mcp"],
"env": {
"TICKET_AI_TRACKER": "gitlab",
"TICKET_AI_PROJECT": "acme/shop",
"TICKET_AI_GITLAB_URL": "https://gitlab.example.com",
"TICKET_AI_GITLAB_TOKEN": "..."
}
}
}
}
読み取り専用のツールが8つあります:learn_conventions、house_style、ticket_template、ticket_context、template_gaps、review_draft、review_ticket、review_open_tickets。
検索対象にしたいチェックアウトがアシスタントの作業ディレクトリではない場合は、env ブロックに TICKET_AI_REPO を追加してください。
この方法でチケットを書く
すでに Claude Code を使っている場合は、この方法がおすすめです。キーもcomposeも、2回目のモデル呼び出しも必要ありません。チケットを頼むと、アシスタントが5つのことを行います。そのうち3つはここで行われます。
ticket_template— 形式を決めます。どのセクションを含めるか、長さ、言語、ラベルを指定します。ticket_context— 既存の情報を確認します。関連チケット、マージリクエストで変更されたファイル、チェックアウト内で関連事項に言及しているファイルを調べます。- それらのファイルを読みます。
ticket_contextはテキスト検索を行うもので、分析はしません。どこを見ればよいかを示すだけで、実際に読む手間は省けません。 - チケットを書きます。
review_draft— 書いた内容を測定し、見つかった問題を修正してから、結果を表示します。
「AI」が使われるところ
数を数えてもドイツ語の段落は書けないため、チケットを書くにはモデルが必要です。それ以外には何も必要ありません。
| 書く | 必要なもの | |
|---|---|---|
| MCP、Claude Code 内 | はい | 何も必要ありません。アシスタントはすでにモデルです |
--writer ollama | はい | お使いのマシン上のモデル。キーもアカウントも不要で、データがノートパソコンの外に出ることもありません |
--writer openai | はい | ベース URL とキー。OpenRouter、Azure AI Foundry、vLLM など、どのプロバイダーでも利用できます |
| ライターなし(デフォルト) | いいえ | 何も必要ありません。測定と情報収集を行い、文章は自分で書きます |
export TICKET_AI_WRITER=ollama # or openai, with a base url and key
ticket-ai models # what that endpoint can reach
ticket-ai compose --title 'Etikettendruck bricht bei mehr als zehn Positionen ab'
ticket-ai models --workflow # an Actions workflow that drafts new issues
composeは本文を書いて測定し、その結果をモデルに1回だけ返します。また、本文だけをリダイレクトできるよう、レビューは標準エラー出力に表示します。--fail-underを指定すると、チームのスタイルに合わないドラフトの出力を拒否します。
どのモデルが書いた場合でも、ドラフトは他のチケットと同じ方法で測定されます。この一連の処理があるため、小型のローカルモデルも利用できます。数を数えて作られた形式に沿って書き、その後、チーム独自のチケットと照らし合わせて評価されます。
ページを使う方法
ticket-ai ui --lang de # or en
127.0.0.1:8760で開くローカルページには、3つのタブがあります。チームのスタイル、ドラフトを貼り付ける入力欄、未対応のバックログを問題の深刻な順に並べた一覧です。このプロセスがトラッカートークンを保持するため、ループバック接続に限定されており、それを変更するフラグはありません。
ビルド手順も CDN も不要です。CSS と JavaScript をインラインで含む HTML ファイルが1つあるだけです。
2つの言語は別々です
export TICKET_AI_UI_LANGUAGE=de # buttons and headings
export TICKET_AI_TICKET_LANGUAGE=de # what it says to write tickets in
この違いは混同されやすいものですが、分けておく価値があります。ドイツ語を使うチームでも、ツールのボタンは英語で表示したい場合があります。一方、ドイツ語のボードに参加する人は、チケットをドイツ語で書く必要があります。
TICKET_AI_TICKET_LANGUAGEを設定すると、コーパスの測定結果より優先され、再学習なしで反映されます。ボードが言語を切り替えている最中でない限り、設定しないでください。測定結果は設定よりも優先されます。また、ボードで使われていない言語を強制すると、既存のチケットがすべて言語チェックに失敗します。
指摘事項、それに付随する注意点、チケットですでに適切に記載できている項目の一覧は、表示時点で読者が使っている言語で表示されます。ただし、指摘事項内のセクション名は翻訳されません。これらはチーム独自の見出しであり、翻訳すると、チームに存在しないセクションになってしまうためです。
CI での使用
ticket-ai review "$CI_ISSUE" --fail-under 0.5
測定するもの
プロジェクトの標準スタイル。 セクション、長さ、ラベル、タイトルマーカー、言語など、あなたから提供されたか、ツールが自ら見つけたコーパスに基づいて測定します。すべての指摘には、そのコーパスにおける件数が示されます。
成功したチケットだけでなく、失敗したチケットも対象です。 割合は比較に使えます。「チケットの78%に受け入れ条件がある」ではなく、*「マージされたチケットでは78%、停滞したチケットでは30%」*と示せます。後者は根拠になりますが、前者では「そうですか」で済まされてしまいます。
分類の基準は結果だけです。変更のマージ、再オープン、確認質問のやり取りなどに基づきます。チケットの内容を基準に分類することはありません。内容で分類してから内容を比較すると、循環論法になるためです。古くなったチケットをボットがクローズした場合は、どちらのグループからも除外します。こうしたチケットが示すのは文章の質ではなく、注目度だからです。2つのグループを分けられないボードでは、そのことを伝えます。誰かに書き方を変えてもらう前に知っておく価値があるためです。ボットを除外する条件と、そのきっかけとなった実際のボードについては、**docs/shipped-against-stalled.md**をご覧ください。
いつも一緒に現れるセクション。 チケットの41%に含まれるセクションは、どんな基準で見ても割合が低すぎます。しかし、Zielセクションも含むチケットでは76%、含まないチケットでは20%に含まれるなら、チームにはテンプレートがあり、特定の種類の作業にだけ適用されていることになります。ボード全体の割合では、そのことがまったく見えません。
宣言したフォームと、実際に作成されたチケットとの比較。 チェックアウト内に.github/ISSUE_TEMPLATEまたは.gitlab/issue_templatesがある場合、gapsは各フィールドを、実際のチケットに含まれる頻度と照らし合わせます。この差はどちらの方向にも生じます。必須フィールドがチケットの10%にしか現れないなら、人々が簡単には提供できない情報を求めるフォームです。多くのチケットに含まれているのにフォームには記載されていないセクションがあれば、プロジェクトで生まれたものの、記録されていない慣習です。
チケットを書くために必要な情報。 件名を渡すと、関連する過去のチケット、それらのチケットに関連するマージリクエストで実際に変更されたファイル、そしてチェックアウト内でその件名に言及しているファイルが返されます。2つ目の情報はトラッカーの履歴にしかありません。コードをどれだけ読んでも得られず、通常は作業がどこに反映されるかを見つける最も手っ取り早い方法です。
分析の仕組み
サンプルとして使うのはクローズ済みのチケットだけです。未解決のチケットがどれほど見事に書かれていても、誰かがそれに基づいて行動できたことを示すものは、まだ何もありません。各チケットは次の基準で評価されます。
| 指標 | 重み | 理由 |
|---|---|---|
| マージ済みMRが関連付けられている | 0.30 | 書かれたとおりに実装できたことを示す最も強い証拠です |
| 実質的な説明がある | 0.20 | 内容のないチケットからテンプレートについて学べることはありません |
| セクションがある | 0.15 | 学習対象となるのはテンプレートです |
| 確認質問がない | 0.15 | 「どういう意味ですか?」というコメントが11件もあれば、明確ではなかったということです |
| 再オープンされていない | 0.10 | 再オープンは、理解される前にクローズされたことを意味します |
| ラベルが付いている | 0.10 |
1時間以内に作成・クローズされたチケットは、スコアを半分にします。重複や誤字の修正であることが多く、実際の作業の特徴とは異なるためです。ボットの作成者はすべて除外します。Renovateからプロジェクトの標準スタイルを学習することは、実際に起こり得る問題です。1人の作成者がコーパスの40%を超えて占めることはできません。そうしないと、プロファイルがチームではなく、最も多くチケットを書く人の特徴を表してしまいます。
各スコアには、その根拠となった理由が示されます。異議を唱えられないコーパスを、信頼することはできません。
わからないこと
チケットが優れているかどうか。 ここでは意味を読み取らず、数を数えるだけです。受け入れ基準が妥当かどうかは判断できません。このプロジェクトで実装された40件のチケットにはすべて受け入れ基準があり、今回のチケットにはないことはわかります。
整合性は品質ではありません。 スコアは、過去にこのプロジェクトで実装されたチケットとの距離を示します。何を意味しているかを正確に理解している人が書いた1行のチケットでも、スコアが低くなることがあり、それでまったく問題ありません。このツール自身がそう述べており、同僚に引用するときも、そのことを伝えてください。
トラッカーの内容は何も変更されません。 すべての操作は読み取りのみです。
ランキングのシグナルも、あくまで状況証拠です。適切に書かれたチケットでも、MRがなく対象外としてクローズされればスコアは低くなります。これは許容できる誤りです。目標は最良の30件ではなく、代表的な30件だからです。サンプルが少ない場合は、レポートと、それをもとに作成されたすべてのレビューで、そのことがはっきり示されます。
Jiraには、関連ブランチやマージリクエストを取得するための公開APIがありません。そのため、そこでの「実装済み」シグナルはリモートリンクに頼っています。リモートリンクを投稿しないボードでは、実装済みと停滞中を区別できません。その場合、すべてのチケットを停滞中として報告するのではなく、その旨を伝えます。これはコーパスではなく、APIの制限です。
モデルが書く部分を測定する
このリポジトリ内のすべてのテストではライターをモックしているため、返される文章についてわかることは何もありません。そのためのものが evals/ です。公開ボードから集めたタイトルを固定セットとして保存し、それぞれのケースが収集された時点のボードプロファイルと並べてあります。
ticket-ai eval --baseline evals/baseline.json # run it and mark the result
ticket-ai eval --runs evals/results/latest.jsonl # re-score records, no model
ticket-ai eval --repeats 5 --markdown # spread, as a table for an MR
ケースはプロファイルの構築前に、学習対象から除外されています。 プロファイルの適合に使ったケースでは、モデルとは無関係な理由でモデルの評価が高くなります。
ランナーは記録を行いますが、採点はしません。 ローカルモデルで1ケースを実行するには30~70秒かかるため、採点を別にすることで、指標を変更しても気軽に試せます。そのためのオプションが --runs です。
指標のうち2つは、意図的に review_draft に含めていません。ボードで使われているセクションがあるかどうかは確認しますが、ドラフトがほかに何を作り出したかは確認しません。ボードで使われたことのない見出しを人が追加することはめったにありませんが、モデルは頻繁に追加するからです。もう1つの指標はばらつきです。ケースごとに1つの数値を出すと、非決定的なライターでは裏付けられない精度を示してしまいます。そのため --repeats が用意されており、レポートでは平均と並べて偏差を表示します。
判定モデルと、その数値が単独で語られない理由
数を数えるだけでは、文章がタイトルに沿っているかどうかはわかりません。そこが見逃してはならない失敗です。草案にすべてのセクションがあり、長さの条件を満たし、適切なラベルが付いていても、まったく別の内容について書かれていることがあります。review_draft は満点を付けます。
--judge は、モデルにその質問だけを尋ねます。「これは良いチケットか」ではありません。それは、このツールがカウントで置き換えるために存在する質問です。
ticket-ai eval --judge --labels evals/labels.jsonl
python tools/label_drafts.py evals/results/latest.jsonl # collect the labels
単純一致率だけでは、ほとんど役に立ちません。 草案の10件中9件が話題に沿っている場合、毎回「話題に沿っている」と答える判定モデルは90%の確率で一致しますが、何も学習できていません。Cohen のカッパ係数は、2人がその割合で推測した場合に得られる一致率を差し引くため、その判定モデルのスコアはゼロになります。
ラベルは人が付ける必要があり、それは避けられません。人間側の判定も生成されたキャリブレーションでは、あるモデルと別のモデルを比較することになります。--labels が指定されていない場合、注意書きを一度だけ記録して、数値がそれなしで独り歩きするのを許すのではなく、判定モデルの行には毎回 uncalibrated と表示されます。
データセットは、10件の公開ボードから集めた134件のケースで構成されています。30件はドイツ語で、gitlab.opencode.de の kern-ux/pattern-library と fitko/fim/portal から取得しています。これはトークンなしで閲覧できる、ドイツの公開 GitLab です。3件のボードは Jira(issues.apache.org の KAFKA と CASSANDRA、hibernate.atlassian.net の HHH)で、4件は繰り返し使われるセクションがまったくない、純粋な文章です。
これは多様性そのものが目的ではありません。compose はそれぞれに応じて処理を分岐します。ドイツ語のプロンプト、トラッカーの抽象化、そしてセクションを列挙するか、見出しを一切付けずに書くようモデルに指示する骨組みのブロックです。CI では、この3つすべてが維持されることを検証します。データセットからどれかが失われても、平均値が動かないままそれを測れなくなり、みんなが見る唯一の数値ではその喪失が見えなくなるからです。
jsonl_lines が存在する理由は kern-ux にあります。あるチケットにはリテラルの U+2028 が含まれており、str.splitlines はこれを改行として扱いますが、json.dumps はエスケープしません。そのため、有効なファイルが途中で切れたレコードとして読み込まれ、ファイルに問題があると誤って判断されていました。
ケースにしてよいもののルールについては、evals/README.md をご覧ください。公開ボードのみが対象です。
実行される場所
低コストのチェックは、ハーネスに関わるすべてのプルリクエストで実行されます。すべてのボードを読み込み、どのケースも例示データと重複していないことを確認し、コミット済みの結果ファイルを再採点します。モデルは使わないため、数秒で完了します。
スイート自体は workflow_dispatch で実行します。134件のケースを作成するには時間も費用もかかります。README の誤字を直すブランチでその費用を払っても、何の意味もありません。
terraform/ はレポートの公開先です。CloudFront の背後にある S3 バケットと、GitHub Actions が短期間有効な OIDC トークンで引き受ける IAM ロールがあります。漏えいやローテーションが必要になるAWS キーは、リポジトリのシークレットにありません。信頼ポリシーの対象は単一のリポジトリに限定されています。repo:owner/* にすると、その所有者が今後作成するすべてのリポジトリにロールを渡してしまいます。これらがなくてもすべて動作し、ロール変数が設定されていない場合は公開ステップが自動的にスキップされます。
ドキュメント
| thirty-boards.md | 実際の43件のボード、MCP クライアント、ブラウザー、クリーンインストールで実行してわかったこと。当時、そのほとんどは行単位のカバレッジが100%でした |
| what-it-produces.md | 作成された5件のチケット。編集せずに、それぞれのスコアとともに掲載しています |
| shipped-against-stalled.md | 2つのグループの分け方と、比較の公平性を保つためのガードについて |
| evaluating-compose.md | 固定データセット上のハーネスについて。保留されたデータとその理由、review_draft にない2つの指標、ゲートがノイズを許容する理由、そしてまだ不足しているもの |
| local-models.md | 2 GB のローカルモデルでライターを実行する方法。計測した所要時間と、その代わりに失われるもの |
検証対象
home-assistant/core、pydantic/pydantic、astral-sh/uv、fastapi/fastapi(GitHub)、inkscape/inkscape、gitlab-org/gitlab-runner(GitLab)、HHH(Jira Cloud)の hibernate.atlassian.net、KAFKA(Jira Data Center)の issues.apache.org/jira、および非公開の GitLab ボード 1 件を対象に、エンドツーエンドで読み取り専用の検証を実施しました。
Home Assistant では条件ルールは一切生成されません。これが正しい結果です。課題フォームが必須であるため、すべてのセクションがすでにボード全体のしきい値をクリアしており、条件を追加しても意味がありません。
開発
uv sync
uv run pytest
uv run ruff check .
bash install_check.sh # build the wheel and drive it from a clean venv
uv run python tests/fleet.py # the whole tool across 43 public boards
ライセンス
MIT.
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。