Skip to main content
Glama
JustWang1110

Jira AI Tool Server

by JustWang1110
README.md
# Jira AI Tool Server

一个面向后续插件化的大模型 Jira 能力内核。

## 当前状态

当前版本提供 17 个只读工具,外加 7 个受控写入 / 本地配置工具:

- `jira.search_issues`
- `jira.get_issue`
- `jira.get_comments`
- `jira.get_transitions`
- `jira.get_permissions`
- `jira.get_project_snapshot`
- `jira.find_stale_issues`
- `jira.get_assignee_workload`
- `jira.get_status_distribution`
- `jira.find_high_priority_open_issues`
- `jira.analyze_project_risks`
- `jira.build_weekly_summary`
- `jira.list_projects`
- `jira.refresh_project_index`
- `jira.resolve_project`
- `jira.search_named_filter`
- `jira.search_issues_by_filters`
- `jira.upsert_name_mapping`
- `jira.add_comment`
- `jira.add_review_summary_comment`
- `jira.transition_issue`
- `jira.update_issue_fields`
- `jira.sync_review_outcome`
- `jira.ide_sync_review_outcome`

并提供统一调度层:

- `ToolDispatcher.dispatch(tool_name, payload)`
- `ToolDispatcher.list_tools()`
- `ToolDispatcher.describe(tool_name)`

以及一个最小 HTTP 服务:

- `GET /health`
- `GET /tools`
- `GET /tools/{tool_name}`
- `POST /invoke/{tool_name}`

运行时还会依据 `schemas/` 中的输入契约执行校验;  
这意味着模型或外部调用方即使构造了错误参数,也会先被拦在 dispatcher 层。

同样地,工具输出也会按 response schema 反向校验;  
如果某个实现悄悄返回了不符合契约的结构,dispatcher 会把它收敛成 `INVALID_TOOL_OUTPUT`。

调用链路也已支持审计记录;HTTP 服务启动时可把事件追加写入 JSONL 文件,
为未来写操作、审批和排障保留事实依据。

## 目录

```text
docs/       协议与说明
schemas/    JSON Schema
tools/      工具注册表
src/        最小可运行内核
tests/      基础测试
```

## 环境变量

```text
JIRA_BASE_URL
JIRA_USERNAME
JIRA_PASSWORD
JIRA_TIMEOUT_SECONDS
HTTP_HOST
HTTP_PORT
AUDIT_FILE_PATH
```

## 本地配置文件

复制示例文件:

```bash
copy config\local.example.json config\local.json
```

第一次使用时,可在 `config/local.json` 里填写明文密码:

```json
{
  "jira": {
    "base_url": "https://jira.example.com",
    "username": "your-username",
    "password": "首次启动前填写明文"
  }
}
```

项目启动后会自动移除明文字段,并改写为平台对应的安全引用:  

- Windows:使用 DPAPI,把密文写回配置文件
- macOS:把密码写入系统 Keychain,配置文件只保存引用

因此:

- Windows 密文通常只能由当前用户、当前机器解开
- macOS 密码由 Keychain 代管,配置文件不再保存密码本体

`config/local.json` 已被加入 `.gitignore`,不要提交到仓库。

### 项目别名与缓存

为了让“昆山人民项目”这类口语化说法稳定映射到 Jira 项目,
配置文件支持维护人工别名:

```json
{
  "projects": {
    "index_file_path": "data/project-index.json",
    "ttl_seconds": 86400,
    "aliases": {
      "昆山人民": "IHGS2512222C"
    }
  }
}
```

运行时默认优先读取本地索引;只有索引缺失、过期,或显式要求刷新时,
才会重新向 Jira 拉取项目列表。

项目分析工具同时支持:

```json
{"project_key": "IHGS2512222C"}
```

或:

```json
{"project_name": "昆山人民"}
```

当传入 `project_name` 时,工具内部会自动走本地索引与别名解析。

### 项目优先分单与路由规则(最小改造)

如果希望“某些项目优先分给固定人员”,可在 `projects.directory` 的项目条目中增加:

```json
{
  "key": "IHZB2404585A",
  "name": "上海曙光医院安徽医院",
  "product_category": "RS6",
  "province": "安徽省",
  "preferred_assignee": "w_tang"
}
```

- `preferred_assignee`:项目级优先处理人(Jira 账号)
- 命中该字段后,可直接优先分配给该账号
- 若未配置该字段,再走通用自动命中规则

配套的“最小改造版”路由配置可放在 `local.json` 根节点(与现有 `projects`、`members` 并列):

```json
{
  "routing": {
    "enabled": true,
    "project_preferred_assignee_field": "preferred_assignee",
    "match_order": [
      "project_preferred_assignee",
      "province_product_category_module",
      "province_product_category",
      "product_category_module",
      "module_only"
    ],
    "min_hit": 5,
    "effort_estimation": {
      "method": "mean",
      "source_field": "折算工时(小时)",
      "min_sample": 5,
      "fallback_order": [
        "province_product_category_module",
        "province_product_category",
        "product_category_module",
        "module_only",
        "product_category_global"
      ],
      "precision": 1
    },
    "outlier_filter": {
      "enabled": true,
      "lower_percentile": 5,
      "upper_percentile": 95
    },
    "scoring_weights": {
      "hit": 0.6,
      "efficiency": 0.25,
      "load": 0.15
    }
  }
}
```

带中文备注的可读版(示例):

```json
{
  "_comment": "分单命中规则配置(含工时预估)",
  "routing": {
    "enabled": true,
    "_comment_enabled": "总开关,false 时不启用自动分单",
    "project_preferred_assignee_field": "preferred_assignee",
    "_comment_project_preferred_assignee_field": "项目条目中的优先分配字段名",
    "match_order": [
      "project_preferred_assignee",
      "province_product_category_module",
      "province_product_category",
      "product_category_module",
      "module_only"
    ],
    "_comment_match_order": "命中顺序:从上到下依次尝试,命中即停止",
    "min_hit": 5,
    "_comment_min_hit": "候选人最小样本量,低于该值不参与排序",
    "effort_estimation": {
      "method": "mean",
      "_comment_method": "工时预估方式:平均折算工时",
      "source_field": "折算工时(小时)",
      "min_sample": 5,
      "_comment_min_sample": "样本不足时按 fallback_order 逐级回退",
      "precision": 1
    }
  }
}
```

### 命中流程示例(输入 Jira 单 -> 输出命中人 + 预估工时)

以下示例用于验证“项目优先分单 + 自动规则回退 + 平均工时预估”是否按预期工作。

输入(示例 Jira 单):

```json
{
  "issue_key": "L1NJ2007002C-855",
  "project_key": "L1NJ2007002C",
  "project_name": "华东-仪征市人民医院",
  "summary": "术语维护中的限制提醒,住院能正常弹窗,门诊未弹窗",
  "module": "HIS"
}
```

处理流程:

1. 读取 `projects.directory`,按 `project_key/project_name` 定位项目元数据
2. 检查是否存在 `preferred_assignee`
   - 若存在:直接命中该账号
   - 若不存在:进入自动规则
3. 自动规则按 `match_order` 依次尝试命中:
   - `province + product_category + module`
   - `province + product_category`
   - `product_category + module`
   - `module_only`
4. 在命中层级内按历史样本排序候选人(命中量、效率、负载)
5. 预估工时按当前命中层级样本计算**平均折算工时**
   - 样本量小于 `min_sample` 时按 `fallback_order` 逐级回退

输出(示例):

```json
{
  "matched_by": "province_product_category_module",
  "assignee": "yu-ran",
  "assignee_source": "auto_rule",
  "estimated_hours": 3.1,
  "estimation_method": "mean(折算工时(小时))",
  "sample_size": 134,
  "candidates_top3": [
    {"assignee": "yu-ran", "score": 126.72},
    {"assignee": "cheng.l.neu", "score": 119.86},
    {"assignee": "ji-t", "score": 79.42}
  ]
}
```

若项目配置了优先人(例如 `preferred_assignee = w_tang`),输出示例:

```json
{
  "matched_by": "project_preferred_assignee",
  "assignee": "w_tang",
  "assignee_source": "project_config",
  "estimated_hours": 2.4,
  "estimation_method": "mean(同项目/同类样本)",
  "sample_size": 39
}
```

### 命名筛选器

对于“本地化问题”这类团队内部已有共识的概念,可直接在配置中固化:

```json
{
  "named_filters": {
    "本地化问题": "resolution = Unresolved AND 问题分类 = 本地化"
  }
}
```

之后工具可用:

```json
{
  "filter_name": "本地化问题",
  "project_name": "昆山人民",
  "module_name": "医生站",
  "extra_jql": "assignee is EMPTY"
}
```

也支持按项目属性做跨项目命名筛选统计:

```json
{
  "filter_name": "本地化问题",
  "project_filters": {
    "product": "RS6"
  },
  "module_name": "医生站"
}
```

其中:

- `filter_name` 表示问题性质,例如“本地化问题”
- `module_name` 表示模块维度,底层会翻译成 Jira JQL 中的 `component`
- `project_filters` 与 `search_issues_by_filters` 一致,支持 `key/name/team/product/product_category/province/region/industry/owner`

### 按 Jira 界面维度查询

如果你希望精确匹配界面筛选项,可直接使用:

```json
{
  "project_name": "昆山人民",
  "statuses": ["待处理", "处理中"],
  "modules": ["医生站"],
  "issue_categories": ["本地化"],
  "assignees": ["晓刚"]
}
```

它会生成:

```jql
project = IHGS2512222C
AND status in (待处理, 处理中)
AND component in (医生站)
AND 问题分类 in (本地化)
AND assignee in (kuangxg)
ORDER BY priority DESC, updated DESC
```

如果你希望按项目属性做跨项目统计(例如统计 `RS6` 产品的问题总数),可在 `~/.jira-ai/local.json` 的 `projects.directory` 维护项目元数据,然后在工具中传:

```json
{
  "project_filters": {
    "product": "RS6"
  },
  "statuses": ["待处理", "处理中"],
  "max_results": 100
}
```

`project_filters` 支持字段:

- `key`
- `name`
- `team`
- `product`
- `product_category`
- `province`
- `region`
- `industry`
- `owner`

每个字段支持传单个字符串或字符串数组;系统会先把匹配到的项目集合翻译成 `project in (...)`,再拼接其他 Jira 维度条件。

同样的 `project_filters` 也可用于以下工具,实现按属性维度的跨项目统计与分析:

- `jira.get_project_snapshot`
- `jira.find_stale_issues`
- `jira.get_assignee_workload`
- `jira.get_status_distribution`
- `jira.find_high_priority_open_issues`
- `jira.analyze_project_risks`
- `jira.build_weekly_summary`

### 导出加班工作簿

可直接用本地 Jira 数据刷新固定模板:

```bash
python -m jira_ai.overtime_export --label 0622加班
```

默认会写回 `C:\Users\MengWang\.jira-ai\第一交付服务中心加班工作任务一览表.xlsx`,也可以通过 `--workbook` 和 `--config` 覆盖路径。

### 中文成员名映射

如果团队成员日常使用中文名沟通,建议在配置中维护成员映射:

```json
{
  "members": {
    "aliases": {
      "晓刚": "kuangxg",
      "张三": "zhangsan"
    },
    "directory": [
      {
        "jira_username": "kuangxg",
        "display_name": "匡晓刚",
        "aliases": ["匡工"],
        "project_keys": ["IHGS2512222C"]
      }
    ]
  }
}
```

这样后续就可以直接说中文名。若模型没有匹配到成员,它会返回候选人,适合继续追问确认。

如需把新的中文别名写回本地配置,可调用:

```json
{
  "mapping_type": "member",
  "alias": "晓刚",
  "target": "kuangxg",
  "display_name": "匡晓刚",
  "project_keys": ["IHGS2512222C"]
}
```

## 启动前自检

```bash
python -m jira_ai.doctor_cli
```

当前会检查:

- 配置是否成功加载
- Jira REST 服务是否可访问

## 首轮真实联调

当 `doctor` 通过后,可以直接执行:

```bash
python -m jira_ai.smoke_cli --issue-key PROJ-123 --project-key PROJ
```

它会依次验证:

1. 权限检查
2. 单任务读取
3. 评论读取
4. 当前可流转状态读取
5. 项目内 JQL 搜索

## 本地验证

```bash
python -m unittest discover -s tests
```

## 插件构建与发布

构建最新 Codex 插件自包含包:

```powershell
.\scripts\build-plugin.ps1
```

发布指定版本并生成 zip:

```powershell
.\scripts\release-plugin.ps1 -Version 0.1.2
```

发布产物会输出到 `dist/`,其中包括:

- `jira-ai-codex-<version>.zip`
- `jira-ai-codex-<version>-RELEASE.md`

每次发布前,脚本会先同步 `plugins/jira-ai-codex/bundle/`,确保插件离开源码仓库后仍可独立运行。

完整发布流程见:

- [plugin-release-workflow.md](/E:/personal/AI/tool-server/docs/plugin-release-workflow.md)
- [codex-plugin-release-checklist.md](/E:/personal/AI/tool-server/docs/codex-plugin-release-checklist.md)

## 调用示例

```python
from jira_ai.service import ToolDispatcher

dispatcher = ToolDispatcher.from_env()
result = dispatcher.dispatch(
    "jira.search_issues",
    {"jql": "project = PROJ ORDER BY updated DESC"}
)
```

也可以通过 CLI 直接调用:

```bash
python -m jira_ai.cli jira.search_issues "{\"jql\":\"project = PROJ\"}"
```

## 启动 HTTP 服务

```bash
python -m jira_ai.http_cli --host 127.0.0.1 --port 8765
```

如需显式指定审计日志文件:

```bash
python -m jira_ai.http_cli --audit-file logs/audit.jsonl
```

所有审计事件在落盘前都会对 `password`、`token`、`authorization` 等常见敏感字段做递归脱敏。

示例:

```bash
curl http://127.0.0.1:8765/tools
curl -X POST http://127.0.0.1:8765/invoke/jira.search_issues ^
  -H "Content-Type: application/json" ^
  -d "{\"jql\":\"project = PROJ\"}"
```

## 受控写入

当前第一条写操作是:

- `jira.add_comment`
- `jira.add_review_summary_comment`
- `jira.transition_issue`

它要求显式审批:

```bash
python -m jira_ai.cli jira.add_comment "{\"issue_key\":\"PROJ-123\",\"body\":\"已完成评审\"}" --approved
```

HTTP 调用时也必须显式带上:

```text
X-Approved: true
```

否则 dispatcher 会返回 `APPROVAL_REQUIRED`,不会真正写入 Jira。

`jira.transition_issue` 还会先读取当前任务可执行的 transition;
只有当你请求的流转名称真实存在于当前工作流中时,才会继续执行。

`jira.update_issue_fields` 则只允许更新配置白名单中的字段。默认白名单:

```json
{
  "write_policy": {
    "allowed_issue_fields": ["labels", "components"]
  }
}
```

如果请求里包含未授权字段,会返回 `FIELD_NOT_ALLOWED`,不会向 Jira 发起修改。

如果你希望把“评审摘要 + 可选流转”一次完成,可以使用:

```json
{
  "issue_key": "PROJ-123",
  "summary": "修复了患者检索逻辑",
  "verdict": "approved",
  "transition_name": "提交测试"
}
```

对应工具:

- `jira.sync_review_outcome`

### IDE 助手对接

如果评审和提交代码发生在 IDE 助手中,推荐直接调用:

- `jira.ide_sync_review_outcome`

输入示例:

```json
{
  "issue_key": "PROJ-123",
  "review_summary": "修复患者检索逻辑,并补充空值保护。",
  "verdict": "approved",
  "findings": ["未发现阻塞性问题"],
  "tests": ["单元测试通过"],
  "commit_sha": "abc123",
  "commit_message": "fix: 修复患者检索逻辑",
  "branch_name": "feature/patient-search",
  "review_url": "https://example/reviews/42",
  "transition_name": "提交测试"
}
```

该工具会先把 IDE 侧信息归一化,再复用内部的评审同步流程。

例如,代码评审摘要可以这样写回:

```json
{
  "issue_key": "PROJ-123",
  "summary": "修复了患者检索逻辑,并补充了空值保护。",
  "verdict": "approved",
  "findings": ["未发现阻塞性问题"],
  "tests": ["已通过单元测试"],
  "links": ["PR #42"]
}
```

## 设计原则

- 核心能力与插件外壳分离
- Jira 原始 JSON 与内部领域模型分离
- 写操作能力以后按显式权限和审批边界扩展

## 多宿主适配

项目不把能力写死在单一模型产品上,而是采用:

```text
统一能力内核 + 宿主无关工具目录 + 薄适配层
```

相关文档:

- `docs/plugin-architecture.md`
- `docs/integrations/codex.md`
- `docs/integrations/mcp.md`
- `docs/integrations/cursor.md`
- `docs/integrations/github-copilot.md`

## MCP server

当前已提供可运行的 stdio MCP server:

```bash
python -m jira_ai.mcp_stdio
```

它支持:

- `initialize`
- `ping`
- `tools/list`
- `tools/call`

## Codex 插件包

当前仓库已经具备 repo-local Codex 插件包结构:

- `plugins/jira-ai-codex/.codex-plugin/plugin.json`
- `plugins/jira-ai-codex/.mcp.json`
- `.agents/plugins/marketplace.json`

安装与发布说明:

- `plugins/jira-ai-codex/INSTALL.md`
- `docs/codex-plugin-release-checklist.md`

## Cursor 插件包

当前仓库已经具备 repo-local Cursor 插件包结构:

- `plugins/jira-ai-cursor/.cursor-plugin/plugin.json`
- `plugins/jira-ai-cursor/mcp.json`
- `.cursor-plugin/marketplace.json`

构建与安装:

```powershell
.\scripts\build-plugin.ps1 -PluginName jira-ai-cursor
.\plugins\jira-ai-cursor\scripts\doctor.ps1
```

然后执行本地安装脚本并重启 Cursor:

```powershell
.\plugins\jira-ai-cursor\scripts\install-local.ps1
```

插件会注册到 `%USERPROFILE%\.cursor\plugins\local\`,不会出现在 Plugins 市场列表;请在 **Settings → MCP** 与 **Rules** 中确认已加载。

安装与发布说明:

- `plugins/jira-ai-cursor/INSTALL.md`
- `docs/integrations/cursor.md`