关于 Ticket AI
Ticket AI 是 MCP 服务和命令行工具,可从团队的跟踪系统学习工单写作习惯,并据此衡量草稿。分析通过计数完成,不需要模型;使用模型撰写工单则为可选功能。
主要功能
- 01从已关闭工单学习写作惯例
- 02衡量章节、长度、标签和语言
- 03查找相关工单及其变更文件
- 04审查草稿和已有工单
- 05按优先级查看未关闭工单
- 06可选用模型撰写工单
适用场景
- 01按团队工单惯例检查草稿
- 02撰写工单前查找相关历史工作
- 03按优先级审查未关闭事项
- 04在 CI 中设置工单审查门槛
MCP 相关
提供八个只读 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 张包含验收标准部分,而这张没有。
没有“Akzeptanzkriterien”(验收标准)部分。 40 张示例工单中有 31 张(78%)包含该部分。
大多数工单建议都是免费的,因此也没人理会。“添加验收标准”“写上复现步骤”——人人都听过,却没有人因此改变。统计你们项目中实际发生过的情况,更难被一笔带过;而且这不是对工单的主观看法。
整个过程中都不会调用大模型。 学习团队风格、查找相关工单和衡量草稿都是计数工作,可以在不配置密钥、也不与工单跟踪系统以外的网络通信的情况下运行。编写工单需要大模型,因此这部分是可选的,使用哪个模型由你决定。 在真实看板上运行后的发现 →
安装
uv tool install ticket-ai-mcp
配置仅使用环境变量——通过 --token 传入的令牌会留在 shell 历史记录和进程列表中。
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
一条命令
ticket-ai learn # mine the tracker, cache the profile
ticket-ai style # what it learned
learn 需要一两分钟:排名需要获取每张工单的评论和关联的合并请求,因此每张工单都要额外发出一两次请求。如果设置了 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 会原样采用:不进行筛选,也不会根据你的选择评分。用三个词描述一张工单,就等于说明了团队如何编写工单,生成的风格档案也会如实反映这一点。
作为 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。
如果要搜索的代码库不是助手的工作目录,请将 TICKET_AI_REPO 添加到 env 块中。
以这种方式撰写工单
如果你已经在使用 Claude Code,可以选择这条路径:无需密钥,无需 compose,也无需调用第二次模型。要求撰写工单后,助手会做五件事,其中三件在这里完成:
ticket_template— 工单的框架:包含哪些章节、篇幅多长、使用什么语言、添加哪些标签。ticket_context— 现有信息:相关工单、其合并请求修改过的文件,以及检出目录中提到该主题的文件。- 它会读取这些文件。
ticket_context执行的是文本搜索,而非分析;它会指出该查看哪里,但不会代你查看。 - 它会撰写工单。
review_draft— 衡量写出的内容,并在展示给你之前修正发现的问题。
“AI”在哪里
计数无法生成一段德语文字,因此撰写工单需要模型。其他所有事情都不需要。
| 撰写 | 需要 | |
|---|---|---|
| MCP,在 Claude Code 中 | 是 | 无需任何东西 — 助手本身已经是模型 |
--writer ollama | 是 | 你机器上的模型。无需密钥、无需账户,任何内容都不会离开笔记本电脑 |
--writer openai | 是 | 基础 URL 和密钥。OpenRouter、Azure AI Foundry、vLLM,或任何服务提供商 |
| 不指定 writer(默认) | 否 | 无需任何东西。负责衡量和收集;由你撰写 |
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 会撰写正文、衡量正文、将发现的问题交还给模型一次,并把审查结果输出到 stderr,以便单独重定向正文。--fail-under 会拒绝输出不符合团队风格的草稿。
无论使用哪个模型撰写,草稿都要经过与其他工单相同的衡量流程。这种循环让小型本地模型在这里也能胜任:它会按照通过计数得出的框架撰写,之后再依据你们团队自己的工单进行评估。
改用页面
ticket-ai ui --lang de # or en
一个位于 127.0.0.1:8760 的本地页面,包含三个标签页:团队风格、用于粘贴草稿的文本框,以及按问题严重程度从高到低排列的未结积压工单。仅限环回访问,因为此进程持有跟踪系统令牌,而且没有可更改此设置的标志。
无需构建步骤,也无需 CDN — 所有 CSS 和 JavaScript 都内联在一个 HTML 文件中。
两种语言,彼此独立
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%”。后者是证据;前者只会让人耸耸肩。
划分只依据结果——变更已合并、工单重新打开,或经过一轮澄清提问——绝不依据工单内容,因为先按内容划分,再比较内容,会形成循环论证。因过期由机器人关闭的工单会从两组中都排除:它们反映的是关注度,而不是写作方式。如果某个看板上没有任何因素能区分两组,工具会如实说明;在要求任何人改变写法之前,了解这一点很有价值。docs/shipped-against-stalled.md 说明了限制条件,以及促使工具排除机器人关闭工单的真实看板。
只会一起出现的章节。 某个章节出现在 41% 的工单中,低于任何值得采用的阈值——但如果有 Ziel 章节的工单中有 76% 也包含该章节,而没有 Ziel 章节的工单中只有 20% 包含它,那团队就有一个模板,并将它应用于某一类工作。看板整体比例会完全掩盖这一点。
你声明的表单,对照你收到的工单。 如果检出内容中有 .github/ISSUE_TEMPLATE 或 .gitlab/issue_templates,gaps 会逐项对照表单字段与工单中实际包含这些字段的频率——差距可能出现在两个方向。某个必填字段只出现在 10% 的工单中,说明表单要求填写人们难以提供的内容。大多数工单都包含某个表单未提及的章节,说明项目逐渐形成了一种约定,却从未将它写下来。
编写工单时需要什么。 提供一个主题,它会返回相关的历史工单、这些工单对应的合并请求实际更改的文件,以及检出内容中提及该主题的文件。第二项信息只存在于跟踪器的历史记录中——无论读多少代码都无法找到,而且它通常是了解工作最终会落在哪些文件中的最快方式。
挖掘方式
只抽样已关闭的工单:一张未关闭的工单可能写得非常好,但目前还没有任何迹象表明有人能据此采取行动。每张工单的评分依据如下:
| 信号 | 权重 | 原因 |
|---|---|---|
| 关联了已合并的 MR | 0.30 | 最有力的证据,表明有人能按工单所写的内容完成开发 |
| 描述内容充实 | 0.20 | 占位内容无法为模板提供参考 |
| 包含章节 | 0.15 | 我们要学习的正是模板 |
| 没有澄清问题 | 0.15 | 十一条“你是什么意思?”的评论意味着内容不清楚 |
| 从未重新打开 | 0.10 | 重新打开意味着工单尚未被理解就关闭了 |
| 带有标签 | 0.10 |
在一小时内创建并关闭的工单,权重减半——它们通常是重复项或拼写修正,其形态并不能代表真实工作。机器人作者的工单会直接剔除;从 Renovate 学习项目风格是一个真实的失效模式。任何一位作者提供的语料不得超过 40%,否则分析得出的项目风格描述的就只是最高产的工单编写者,而非整个团队。
每个分数都会附上产生该分数的原因。无法据理质疑的语料,也不会让人信服。
它无法告诉你的事
你的工单写得好不好。 这里的所有操作都不看含义,只做计数。它无法判断你的验收标准是否合理,但可以告诉你,这个项目中已交付的 40 个工单都有验收标准,而这个工单没有。
一致不代表质量。 评分反映的是与此处历史上实际完成的工单之间的差距。一个了解自己意图的人写的一行工单,可能得分很低,但完全没有问题。工具也这样评价自己;当你向同事引用它时,也应该这么说。
不会更改跟踪工具中的任何内容。 所有操作都是读取。
排名信号也只是间接依据。一个写得很好的工单,因为超出范围而关闭且没有 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
在构建配置文件之前,案例已从中留出。 如果配置文件拟合过某个案例,模型的表现就会因此显得更好,但这与模型本身毫无关系。
运行器会记录结果,但不会评分。 在本地模型上运行一个案例需要 30 到 70 秒,因此将评分单独进行,就能免费尝试更改指标。--runs 就是用于此路径的参数。
其中两个指标有意不纳入 review_draft。它会检查看板使用的章节是否齐全,但不会问草稿还编造了什么内容,因为人们很少添加看板从未用过的标题,而模型却经常这么做。另一个指标是离散度:对于非确定性撰写者,每个案例只给出一个数字会让结果看起来像精确值,因此提供了 --repeats,报告会在均值旁列出标准差。
评判器,以及为何它的分数从不单独出现
计数无法判断正文是否围绕标题展开,而这正是值得发现的问题:一份草稿可以包含所有章节、达到长度要求、使用正确的标签,却描述着完全不同的内容。review_draft 会给它满分。
--judge 只问模型这个问题,不问其他问题。不是“这是不是一张好的工单”——这个工具的存在,就是为了用计数取代这个问题。
ticket-ai eval --judge --labels evals/labels.jsonl
python tools/label_drafts.py evals/results/latest.jsonl # collect the labels
单看原始一致率几乎毫无用处。 如果十份草稿中有九份切题,那么一个每次都回答“切题”的评判器就能达到百分之九十的一致率,却什么也没学到。Cohen 的 kappa 会扣除两个人按照这些比例猜测时也能达到的一致率,因此这个评判器的得分为零。
标签来自人工标注,这一点无法回避。如果校准时的人工部分是生成出来的,那么比较的就只是一个模型和另一个模型。没有 --labels 时,评判器一栏每次都会打印 uncalibrated,而不是只记录一次这条注意事项,然后让分数在脱离注意事项的情况下传播。
数据集包含来自十个公开看板的 134 个案例。其中 30 个是德语案例,来自 gitlab.opencode.de 上的 kern-ux/pattern-library 和 fitko/fim/portal;这是一个无需令牌即可读取的公开德语 GitLab。三个看板使用 Jira(issues.apache.org 上的 KAFKA 和 CASSANDRA,以及 hibernate.atlassian.net 上的 HHH),另有四个看板只写纯文本,完全没有重复出现的章节。
这并不是为了多样而多样。compose 会针对每一种情况走不同分支:德语提示词、跟踪系统抽象,以及一个骨架区块——它要么列出章节,要么告诉模型完全不要写标题。CI 会断言这三种情况都保留下来,因为如果数据集丢掉其中一种,它就会在平均值不变的情况下不再衡量该情况,而人们关注的那个数字却看不出任何异常。
kern-ux 正是 jsonl_lines 存在的原因:其中一张工单包含字面上的 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 客户端、一个浏览器和全新安装环境中运行后发现了什么。当时几乎所有内容都具备完整的行覆盖率 |
| what-it-produces.md | 它编写的五张工单,未经编辑,并附有每张工单获得的分数 |
| shipped-against-stalled.md | 两组如何划分,以及哪些防护措施能确保比较公平 |
| evaluating-compose.md | 在冻结数据集上运行的测试框架:哪些数据被留出以及原因、review_draft 缺少的两项指标、为什么门槛容许噪声,以及仍有哪些不足 |
| local-models.md | 在 2 GB 本地模型上运行写作器:实测耗时,以及它需要牺牲什么 |
验证对象
已对 home-assistant/core、pydantic/pydantic、astral-sh/uv、fastapi/fastapi(GitHub)、inkscape/inkscape 和 gitlab-org/gitlab-runner(GitLab)、hibernate.atlassian.net HHH(Jira Cloud)、issues.apache.org/jira KAFKA(Jira Data Center),以及一个私有 GitLab 看板进行端到端只读验证。
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 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。