Skill: liuwei19820201/jira-auto-analyze (version 1.0.0)

--- name: jira-auto-analyze description: 此技能用于自动分析JIRA进项系统线上工单（filter=13123），检查工单内容是否包含四项必填信息，并根据工单内容自动分配给相应的经办人。当用户需要自动处理JIRA新工单、检查工单信息完整性、或根据规则自动分配任务时使用此技能。 --- # 📋 JIRA工单自动分析处理技能 ## 概述 此技能实现JIRA进项系统线上工单的自动化处理流程，包括： 1. **自动检查工单信息完整性**：验证是否包含环境、通道类型、版本号、日志四项必填信息 2. **智能分配工单**：根据工单内容自动分配给相应负责人 3. **自动回复处理**：对处理结果进行自动回复 ## 使用场景 ### 自动触发场景 在WorkBuddy中输入以下关键词时自动加载： - "自动分析JIRA工单" - "检查工单信息完整性" - "JIRA工单自动处理" - "工单信息检查" - "自动分配工单" - "处理新工单" - "JIRA自动化处理" ### 具体应用场景 1. **新工单自动处理**：新创建的工单自动检查信息完整性并分配 2. **信息验证**：确保工单包含必要的技术信息 3. **团队协作**：根据业务规则自动分配给相应负责人 4. **流程标准化**：统一工单处理流程和回复标准 ## 核心功能 ### 1. 必填信息检查 自动检查工单是否包含以下四项信息： - **环境**：星舰、飞船、云（三选一） - **通道类型**：web连接器、rpa、乐企（三选一） - **项目版本号**：版本号格式（如1.2.3、v2、版本5等） - *特殊规则*：如果是"云"环境，则不需要版本号 - **相关日志**：日志、log、trace、error、stack等关键词 ### 2. 自动处理规则 - **信息不全的工单**：自动打回给联系人，要求补充信息 - **信息完整的工单**：根据内容自动分配给相应负责人 ### 3. 分配规则 1. **星舰工单** → 崔征明（关键词：星舰，优先级最高） 2. **认证相关工单** → 张献文（关键词：认证、勾选、授权等） 3. **乐企相关工单** → 付强（关键词：乐企、leqi等） 4. **综服通道相关工单** → 魏旭峰（关键词：综服、通道、银行等） 5. **其他工单** → 刘巍（默认处理） **优先级说明**： - 星舰工单具有最高优先级，一旦发现"星舰"关键词立即分配给崔征明 - 其他规则按关键词匹配分数排序 - 如果工单信息不完整，自动退回给提单人并提示规范文档链接 ## 使用方法 ### 在WorkBuddy中使用 当用户提到相关关键词时，自动加载此技能并执行： ```bash # 自动执行分析处理 python3 scripts/jira_auto_analyze.py # 模拟运行（不实际修改JIRA） python3 scripts/jira_auto_analyze.py --dry-run ``` ### 命令行使用 ```bash # 进入技能目录 cd ~/.workbuddy/skills/jira-auto-analyze # 安装依赖（首次使用） python3 scripts/setup.py # 执行自动分析处理 python3 scripts/jira_auto_analyze.py # 模拟运行模式 python3 scripts/jira_auto_analyze.py --dry-run # 使用自定义配置文件 python3 scripts/jira_auto_analyze.py --config /path/to/config.json ``` ## 技术架构 ### 文件结构 ``` jira-auto-analyze/ ├── SKILL.md # 技能文档（本文件） ├── scripts/ │ ├── jira_auto_analyze.py # 核心分析处理脚本 │ ├── utils.py # 工具函数库 │ └── setup.py # 安装脚本 ├── config/ │ └── config.json # 配置文件 ├── references/ │ ├── jira_structure.md # JIRA页面结构参考 │ └── usage_guide.md # 详细使用指南 └── logs/ # 日志目录 ├── jira_analyze.log # 执行日志 ├── operation_log.md # 操作记录 └── assignment_log.md # 分配日志 ``` ### 依赖环境 - Python 3.7+ - Playwright (浏览器自动化框架) - Chromium浏览器 ## 执行流程 ### 1. 登录JIRA - 访问 http://jira.51baiwang.com - 使用配置的用户名密码登录 - 验证登录状态 ### 2. 访问工单列表 - 导航到 filter=13123 页面 - 提取所有工单信息 - 默认只处理"新建"状态的工单 ### 3. 分析工单内容 对于每个工单： 1. 提取工单标题和描述 2. 检查四项必填信息 3. 标记工单为有效或无效 ### 4. 确定处理方式 - **无效工单**：准备打回评论 - **有效工单**：根据关键词匹配分配规则 ### 5. 执行处理 - **打回工单**：添加评论要求补充信息 - **分配工单**：修改经办人字段并添加回复 ### 6. 记录日志 - 记录所有操作结果 - 包含详细的操作信息 ## 配置说明 ### JIRA配置 ```json "config": { "jira_url": "http://jira.51baiwang.com", // JIRA地址 "filter_id": "13123", // 工单filter ID "username": "liuwei1", // 登录用户名 "password": "Lw@123456", // 登录密码 "rejection_message": "请提供相关环境、通道类型、版本号及日志信息", // 打回消息 "check_new_only": true // 只处理新工单 } ``` ### 规则配置 每个规则包含： - `rule_name`: 规则名称 - `keywords`: 匹配关键词列表 - `assignee`: 负责人姓名 - `jira_username`: JIRA用户名 - `reply_message`: 回复消息 ## 安全注意事项 1. **密码安全**: JIRA密码存储在配置文件中，请确保文件安全 2. **操作权限**: 需要JIRA账户有修改工单和添加评论的权限 3. **操作确认**: 默认会显示分析结果，需要确认后才执行实际操作 4. **模拟运行**: 首次使用时建议使用`--dry-run`参数进行测试 ## 故障排除 ### 常见问题 1. **Playwright安装失败**: 检查Python版本，重新安装Playwright和Chromium 2. **JIRA登录失败**: 检查用户名密码和网络连接 3. **页面元素找不到**: JIRA页面结构可能已更新，检查选择器 4. **工单提取失败**: 检查filter ID和工单表格结构 ### 日志文件 - `logs/jira_analyze.log`: 执行日志 - `logs/operation_log.md`: 操作记录 - `logs/assignment_log.md`: 分配日志 ## 扩展与定制 ### 添加新规则 编辑`config/config.json`文件，在`rules`数组中添加新规则： ```json { "rule_name": "新规则名称", "keywords": ["关键词1", "关键词2"], "assignee": "负责人姓名", "jira_username": "jira用户名", "reply_message": "回复消息内容" } ``` ### 修改必填信息检查规则 修改`scripts/utils.py`中的`check_required_fields`函数，调整关键词或正则表达式。 ### 调整匹配算法 修改`scripts/utils.py`中的`calculate_match_score`函数，调整匹配分数计算逻辑。 --- **创建时间**: 2026年4月3日 **适用系统**: JIRA 51baiwang内部系统 **技术依赖**: Python + Playwright + Chromium **维护团队**: 进项系统产研团队

path: check_skill.py
content: #!/usr/bin/env python3
"""
检查技能结构
"""

import os
import json

print("📁 检查JIRA自动分析技能结构")
print("=" * 60)

# 检查技能根目录
skill_dir = "/Users/baiwang/.workbuddy/skills/jira-auto-analyze"
if not os.path.exists(skill_dir):
    print(f"❌ 技能目录不存在: {skill_dir}")
    exit(1)

print(f"✅ 技能目录: {skill_dir}")

# 检查核心文件
files_to_check = [
    ("SKILL.md", "技能主文档"),
    ("INSTALL.md", "安装指南"),
    ("scripts/jira_auto_analyze.py", "主脚本"),
    ("scripts/utils.py", "工具函数"),
    ("config/config.json", "配置文件"),
]

all_files_exist = True
for file_path, description in files_to_check:
    full_path = os.path.join(skill_dir, file_path)
    if os.path.exists(full_path):
        file_size = os.path.getsize(full_path)
        print(f"✅ {description}: {file_path} ({file_size} bytes)")
    else:
        print(f"❌ {description}缺失: {file_path}")
        all_files_exist = False

# 检查配置文件内容
config_path = os.path.join(skill_dir, "config/config.json")
if os.path.exists(config_path):
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
        
        print(f"\n📋 配置文件内容:")
        print(f"   服务地址: {config.get('jira_url', '未设置')}")
        print(f"   用户名: {config.get('username', '未设置')}")
        print(f"   密码长度: {len(config.get('password', ''))} 字符")
        print(f"   分配规则数量: {len(config.get('rules', []))}")
        
        if 'rules' in config:
            print(f"   分配规则:")
            for rule in config['rules']:
                rule_name = rule.get('rule_name', '未知规则')
                assignee = rule.get('assignee', '未知负责人')
                print(f"      - {rule_name} -> {assignee}")
                
    except Exception as e:
        print(f"❌ 读取配置文件失败: {e}")
        all_files_exist = False

# 检查SKILL.md内容
skill_md_path = os.path.join(skill_dir, "SKILL.md")
if os.path.exists(skill_md_path):
    try:
        with open(skill_md_path, 'r', encoding='utf-8') as f:
            content = f.read(1000)  # 读取前1000字符
        
        if "# JIRA工单自动分析处理技能" in content:
            print(f"\n📄 SKILL.md包含技能标题")
        
        # 检查关键部分
        sections_to_check = ['使用场景', '核心功能', '使用方法']
        for section in sections_to_check:
            if f"## {section}" in content:
                print(f"✅ SKILL.md包含'{section}'部分")
            else:
                print(f"⚠️  SKILL.md缺少'{section}'部分")
                
    except Exception as e:
        print(f"❌ 读取SKILL.md失败: {e}")

print("\n" + "=" * 60)
if all_files_exist:
    print("🎉 技能结构完整，准备就绪！")
    print("\n安装步骤:")
    print("1. 确保已安装Python 3.7+和Playwright")
    print("2. 将技能文件夹复制到 ~/.workbuddy/skills/ 目录")
    print("3. 在WorkBuddy中使用 'use_skill jira-auto-analyze' 加载技能")
    print("4. 运行 'python3 scripts/jira_auto_analyze.py' 开始处理工单")
else:
    print("⚠️  技能结构不完整，请检查缺失的文件")

path: USAGE.md
content: # JIRA自动分析技能使用指南

## 技能概述

JIRA自动分析技能是一个基于Playwright的浏览器自动化工具，用于自动处理JIRA进项系统线上工单。该技能能够：
1. 自动登录JIRA系统（http://jira.51baiwang.com）
2. 检查工单是否包含四项必填信息
3. 根据工单内容自动分配给相应负责人
4. 自动回复工单处理消息

## 快速开始

### 1. 安装依赖
```bash
# 确保已安装Python 3.7+
python3 --version

# 安装Playwright浏览器自动化框架
python3 -m pip install playwright
python3 -m playwright install chromium
```

### 2. 技能安装
将技能文件夹复制到用户技能目录：
```bash
# 查看用户技能目录
echo ~/.workbuddy/skills/

# 如果目录不存在，创建它
mkdir -p ~/.workbuddy/skills/

# 复制技能文件夹
cp -r jira-auto-analyze ~/.workbuddy/skills/
```

### 3. 在WorkBuddy中加载技能
```bash
# 在WorkBuddy对话中使用以下命令加载技能
use_skill jira-auto-analyze
```

### 4. 运行技能
```bash
# 进入技能目录
cd ~/.workbuddy/skills/jira-auto-analyze

# 运行主脚本（测试模式，不实际操作）
python3 scripts/jira_auto_analyze.py --dry-run

# 运行主脚本（实际模式，自动处理工单）
python3 scripts/jira_auto_analyze.py
```

## 功能详解

### 1. 必填信息检查
技能会检查每个工单是否包含以下四项信息：
- **环境**: 星舰、飞船、云
- **通道类型**: web连接器、rpa、乐企
- **项目版本号**: 如 1.2.3、v2.1.0、版本3.0.0（云工单不需要版本号）
- **相关日志**: 日志、log、trace、error、stack等关键词

### 2. 自动分配规则（按优先级排序）
- **星舰工单** → 崔征明（关键词：星舰，最高优先级）
- **认证相关工单** → 张献文（关键词：认证、勾选、授权、权限、token、登录、Auth）
- **乐企相关工单** → 付强（关键词：乐企、leqi、LEQI）
- **综服通道相关工单** → 魏旭峰（关键词：综服、通道、银行、工行、中行、农行、建行、招行）
- **其他工单** → 刘巍（默认处理人）

**特殊规则**：
- 星舰工单具有最高优先级，一旦发现"星舰"关键词立即处理
- 回复消息：`星舰工单请及时处理，有问题可沟通`
- 如果工单信息不完整，自动退回给提单人并提示规范文档链接

### 3. 自动回复消息
- **信息不完整**: "请依据工单提交规范提交，规范文档链接：http://confluence.51baiwang.com/pages/viewpage.action?pageId=80049485\n\n请补充以下信息：环境（星舰、飞船、云）、通道类型（web连接器、rpa、乐企）、项目版本号、相关日志"
- **星舰工单**: "星舰工单请及时处理，有问题可沟通"
- **认证工单**: "请献文协助处理此工单"
- **乐企工单**: "请付强协助处理此工单"
- **综服工单**: "请旭峰协助处理此工单"
- **其他工单**: "收到，我会及时处理，请稍后"

## 配置文件

配置文件位于 `config/config.json`，包含以下设置：

```json
{
  "rules": [...],  // 分配规则定义
  "config": {
    "jira_url": "http://jira.51baiwang.com",
    "filter_id": "13123",
    "username": "liuwei1",
    "password": "Lw@123456",
    "rejection_message": "请提供相关环境、通道类型、版本号及日志信息",
    "check_new_only": true
  }
}
```

## 命令行参数

```bash
# 测试模式，只显示分析结果，不实际执行操作
python3 scripts/jira_auto_analyze.py --dry-run

# 详细输出模式
python3 scripts/jira_auto_analyze.py --verbose

# 指定配置文件路径
python3 scripts/jira_auto_analyze.py --config path/to/config.json

# 只处理特定数量的工单
python3 scripts/jira_auto_analyze.py --limit 5
```

## 技能结构

```
jira-auto-analyze/
├── SKILL.md              # 技能主文档
├── INSTALL.md           # 安装指南
├── USAGE.md            # 使用指南（本文档）
├── config/
│   └── config.json     # 配置文件
├── scripts/
│   ├── jira_auto_analyze.py  # 主脚本
│   ├── utils.py        # 工具函数
│   ├── demo.py         # 演示脚本
│   └── test_functions.py    # 测试脚本
├── references/         # 参考文档
└── assets/            # 资源文件
```

## 演示脚本

查看技能功能演示：
```bash
python3 scripts/demo.py
```

演示脚本会展示：
- 必填信息检查功能
- 规则匹配与自动分配
- 完整工单处理流程

## 常见问题

### Q1: 技能无法连接到JIRA
- 检查网络连接
- 验证JIRA地址是否正确：http://jira.51baiwang.com
- 确认用户名密码是否正确

### Q2: Playwright安装失败
- 确保已安装Python 3.7+
- 尝试重新安装：`python3 -m pip install --upgrade playwright`
- 手动下载Chromium：`python3 -m playwright install chromium`

### Q3: 技能无法找到工单
- 确认filter_id是否正确（13123）
- 检查JIRA页面结构是否变化
- 使用--verbose参数查看详细输出

### Q4: 如何修改分配规则
编辑 `config/config.json` 文件中的 `rules` 部分，添加或修改规则。

## 安全注意事项

1. **密码安全**: 配置文件中的密码以明文存储，建议定期更换
2. **操作确认**: 首次使用时建议使用 `--dry-run` 参数测试
3. **权限控制**: 确保只有授权人员可以访问技能配置
4. **日志记录**: 技能会自动记录操作日志，便于审计

## 更新与维护

### 更新技能
```bash
cd ~/.workbuddy/skills/jira-auto-analyze
git pull  # 如果使用git管理
# 或手动复制新版本文件
```

### 检查更新
```bash
python3 scripts/jira_auto_analyze.py --check-update
```

### 反馈与支持
如有问题或建议，请联系技能开发者。

---

**版本**: 1.0.0  
**最后更新**: 2026-04-03  
**适用系统**: macOS, Linux, Windows  
**依赖**: Python 3.7+, Playwright, Chromium

path: INSTALL.md
content: # JIRA工单自动分析处理技能 - 安装指南

## 安全审查结果

### 审查状态: ✅ **批准安装** (P2 - 安全)

### 安全评估摘要
- **代码质量**: 良好，无恶意代码
- **凭证安全**: 密码明文存储（建议使用环境变量）
- **操作安全**: 提供模拟运行模式，需要用户确认
- **风险等级**: 低风险，可控

### 安全建议
1. 首次使用前使用`--dry-run`模式测试
2. 考虑将密码存储在环境变量中
3. 确保JIRA账户有相应权限
4. 定期审查分配规则准确性

## 完整安装步骤

### 步骤1: 检查技能目录
```bash
# 检查技能是否已创建
ls -la ~/.workbuddy/skills/jira-auto-analyze/
```

### 步骤2: 安装依赖
```bash
cd ~/.workbuddy/skills/jira-auto-analyze

# 运行安装脚本
python3 scripts/setup.py

# 或手动安装依赖
pip install playwright
python3 -m playwright install chromium
```

### 步骤3: 配置修改（可选）
编辑配置文件：`config/config.json`
```json
{
  "config": {
    "jira_url": "http://jira.51baiwang.com",
    "filter_id": "13123",
    "username": "liuwei1",          # 修改为你的JIRA用户名
    "password": "Lw@123456",        # 修改为你的JIRA密码
    "rejection_message": "请提供相关环境、通道类型、版本号及日志信息",
    "check_new_only": true
  }
}
```

### 步骤4: 测试运行
```bash
# 模拟运行测试（不实际修改JIRA）
python3 scripts/jira_auto_analyze.py --dry-run

# 查看测试结果，确认分析逻辑正确
```

### 步骤5: 实际执行
```bash
# 实际执行（会修改JIRA数据）
python3 scripts/jira_auto_analyze.py

# 按照提示确认操作
```

## 验证安装

### 1. 检查文件结构
```bash
cd ~/.workbuddy/skills/jira-auto-analyze
find . -type f -name "*.py" | sort
```

预期输出:
```
./scripts/jira_auto_analyze.py
./scripts/setup.py
./scripts/utils.py
```

### 2. 测试导入模块
```bash
python3 -c "from scripts.utils import check_required_fields; print('模块导入成功')"
```

### 3. 测试必填信息检查
```bash
python3 -c "
from scripts.utils import check_required_fields
test_text = '星舰环境，web连接器，版本1.2.3，错误日志'
result = check_required_fields(test_text)
print(f'测试结果: {result[\"is_valid\"]}')
print(f'缺失字段: {result[\"missing_fields\"]}')
"
```

## 配置说明

### 环境变量配置（推荐）
为避免密码明文存储，可以使用环境变量：

```bash
# 设置环境变量
export JIRA_USERNAME="liuwei1"
export JIRA_PASSWORD="Lw@123456"

# 修改脚本使用环境变量
# 在jira_auto_analyze.py中修改：
# self.jira_credentials = {
#     'username': os.environ.get('JIRA_USERNAME', 'liuwei1'),
#     'password': os.environ.get('JIRA_PASSWORD', 'Lw@123456')
# }
```

### 分配规则定制
编辑`config/config.json`中的`rules`数组，添加或修改分配规则：

```json
{
  "rule_name": "新规则名称",
  "keywords": ["关键词1", "关键词2"],
  "assignee": "负责人姓名",
  "jira_username": "jira用户名",
  "reply_message": "回复消息内容"
}
```

## 故障排除

### 常见问题

#### Q1: Playwright安装失败
```bash
# 检查Python版本
python3 --version

# 重新安装
pip uninstall playwright
pip install playwright
python3 -m playwright install chromium
```

#### Q2: JIRA登录失败
1. 检查用户名密码是否正确
2. 检查网络连接
3. 检查JIRA URL是否正确

#### Q3: 页面元素找不到
1. 检查`references/jira_structure.md`中的元素选择器
2. JIRA页面结构可能已更新
3. 可能需要调整等待时间

#### Q4: 权限不足
确保JIRA账户有：
- 查看filter=13123的权限
- 修改工单经办人的权限
- 添加评论的权限

### 日志文件
- `logs/jira_analyze.log`: 执行过程日志
- `logs/operation_log.md`: 操作记录
- `logs/assignment_log.md`: 分配日志

查看日志帮助诊断问题：
```bash
tail -f ~/.workbuddy/skills/jira-auto-analyze/logs/jira_analyze.log
```

## 自动化集成

### 定时执行
可以设置定时任务自动运行：

```bash
# 每天上午9点运行
0 9 * * * cd ~/.workbuddy/skills/jira-auto-analyze && python3 scripts/jira_auto_analyze.py

# 每小时运行一次（仅模拟）
0 * * * * cd ~/.workbuddy/skills/jira-auto-analyze && python3 scripts/jira_auto_analyze.py --dry-run
```

### 在WorkBuddy中自动触发
技能已配置自动触发关键词：
- "自动分析JIRA工单"
- "检查工单信息完整性"
- "JIRA工单自动处理"
- "自动分配工单"

## 卸载说明

如需卸载此技能：

```bash
# 1. 停止所有相关进程
pkill -f "jira_auto_analyze"

# 2. 删除技能目录
rm -rf ~/.workbuddy/skills/jira-auto-analyze

# 3. 清理依赖（可选）
pip uninstall playwright
```

## 技术支持

### 文档参考
- `references/usage_guide.md`: 详细使用指南
- `references/jira_structure.md`: JIRA页面结构参考
- `SKILL.md`: 技能主文档

### 问题反馈
1. 检查日志文件定位问题
2. 使用`--dry-run`模式测试复现
3. 联系技能维护团队

---

**安装完成时间**: 2026年4月3日  
**技能版本**: v1.0.0  
**维护团队**: 进项系统产研团队

path: README.md
content: # JIRA工单自动分析处理技能

## 快速开始

### 1. 安装依赖
```bash
cd ~/.workbuddy/skills/jira-auto-analyze
python3 scripts/setup.py
```

### 2. 配置修改（可选）
编辑配置文件：`config/config.json`
- 修改JIRA用户名密码
- 调整分配规则
- 自定义回复消息

### 3. 测试运行
```bash
# 模拟运行（不实际修改JIRA）
python3 scripts/jira_auto_analyze.py --dry-run

# 实际执行
python3 scripts/jira_auto_analyze.py
```

## 功能特点

✅ **自动检查工单信息完整性**
- 环境（星舰、飞船、云）
- 通道类型（web连接器、rpa、乐企）
- 项目版本号（云环境除外）
- 相关日志信息

✅ **智能分配工单**
- 认证相关工单 → 张献文
- 乐企相关工单 → 付强
- 综服通道工单 → 魏旭峰
- 其他工单 → 刘巍

✅ **自动回复处理**
- 信息不全：自动打回要求补充
- 信息完整：分配后自动回复

✅ **详细日志记录**
- 执行过程日志
- 操作结果记录
- 错误追踪信息

## 使用示例

### 场景1：处理新工单
```bash
# 执行自动分析处理
python3 scripts/jira_auto_analyze.py

# 输出示例：
📊 JIRA工单分析处理结果
📋 发现 5 个工单
   - 新工单: 3
   - 有效工单: 2
   - 需打回工单: 1
```

### 场景2：模拟测试
```bash
# 模拟运行测试
python3 scripts/jira_auto_analyze.py --dry-run

# 查看分析结果，不实际修改JIRA
```

## 配置文件说明

### 主要配置项
```json
{
  "config": {
    "jira_url": "http://jira.51baiwang.com",
    "filter_id": "13123",
    "username": "liuwei1",
    "password": "Lw@123456",
    "rejection_message": "请提供相关环境、通道类型、版本号及日志信息",
    "check_new_only": true
  }
}
```

### 分配规则配置
```json
{
  "rules": [
    {
      "rule_name": "认证相关工单",
      "keywords": ["认证", "勾选", "授权"],
      "assignee": "张献文",
      "jira_username": "zhangxianwen",
      "reply_message": "请献文协助处理此工单"
    }
    // ... 更多规则
  ]
}
```

## 常见问题

### Q1: 如何修改JIRA账号密码？
A: 编辑`config/config.json`文件，修改`username`和`password`字段。

### Q2: 如何添加新的分配规则？
A: 在`config/config.json`的`rules`数组中添加新规则对象。

### Q3: Playwright安装失败怎么办？
A: 运行以下命令重新安装：
```bash
pip uninstall playwright
pip install playwright
python3 -m playwright install chromium
```

### Q4: 如何只查看分析结果而不执行？
A: 使用`--dry-run`参数：
```bash
python3 scripts/jira_auto_analyze.py --dry-run
```

## 文件结构

```
jira-auto-analyze/
├── README.md                 # 本文件
├── SKILL.md                  # 技能主文档
├── scripts/
│   ├── jira_auto_analyze.py  # 核心脚本
│   ├── utils.py              # 工具函数
│   └── setup.py              # 安装脚本
├── config/
│   └── config.json           # 配置文件
├── references/
│   ├── jira_structure.md     # JIRA结构参考
│   └── usage_guide.md        # 详细指南
└── logs/                     # 日志目录
```

## 技术支持

- **问题反馈**: 检查`logs/`目录下的日志文件
- **配置问题**: 参考`references/usage_guide.md`
- **技术细节**: 查看`references/jira_structure.md`

## 版本历史

- v1.0.0 (2026-04-03): 初始版本发布
  - 实现四项必填信息检查
  - 实现自动分配规则
  - 实现自动回复功能
  - 添加详细日志记录

---

**注意**: 首次使用前请确保已正确安装所有依赖，并建议先使用`--dry-run`模式进行测试。

path: scripts/skill_init_test.py
content: #!/usr/bin/env python3
"""
技能初始化测试
"""

import os
import sys
import json

def test_config_loading():
    """测试配置文件加载"""
    print("🧪 测试配置文件加载")
    print("-" * 60)
    
    config_path = "config/config.json"
    
    if not os.path.exists(config_path):
        print(f"❌ 配置文件不存在: {config_path}")
        return False
    
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
        
        required_keys = ['jira_url', 'username', 'password', 'rules']
        
        for key in required_keys:
            if key not in config:
                print(f"❌ 配置缺少必要字段: {key}")
                return False
        
        print(f"✅ 配置文件加载成功")
        print(f"   JIRA地址: {config['jira_url']}")
        print(f"   用户名: {config['username']}")
        print(f"   密码: {'*' * len(config['password'])}")
        print(f"   分配规则数量: {len(config['rules'])}")
        
        # 显示规则
        print(f"\n📋 分配规则:")
        for rule in config['rules']:
            print(f"   - {rule['rule_name']} -> {rule['assignee']}")
        
        return True
        
    except Exception as e:
        print(f"❌ 配置文件加载失败: {e}")
        return False

def test_skill_structure():
    """测试技能目录结构"""
    print("\n🧪 测试技能目录结构")
    print("-" * 60)
    
    required_dirs = [
        '.',
        'config',
        'scripts',
        'references'
    ]
    
    required_files = [
        'SKILL.md',
        'INSTALL.md',
        'config/config.json',
        'scripts/jira_auto_analyze.py',
        'scripts/utils.py',
        'scripts/test_functions.py'
    ]
    
    all_passed = True
    
    for dir_path in required_dirs:
        if os.path.exists(dir_path):
            print(f"✅ 目录存在: {dir_path}")
        else:
            print(f"❌ 目录缺失: {dir_path}")
            all_passed = False
    
    for file_path in required_files:
        if os.path.exists(file_path):
            print(f"✅ 文件存在: {file_path}")
        else:
            print(f"❌ 文件缺失: {file_path}")
            all_passed = False
    
    return all_passed

def test_skill_md():
    """测试SKILL.md文件"""
    print("\n🧪 测试SKILL.md文件")
    print("-" * 60)
    
    skill_md_path = "SKILL.md"
    
    if not os.path.exists(skill_md_path):
        print(f"❌ SKILL.md文件不存在")
        return False
    
    try:
        with open(skill_md_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        required_sections = [
            '# JIRA工单自动分析处理技能',
            '## 使用场景',
            '## 核心功能',
            '## 使用方法'
        ]
        
        all_sections_found = True
        for section in required_sections:
            if section in content:
                print(f"✅ 包含必要部分: {section}")
            else:
                print(f"❌ 缺少必要部分: {section}")
                all_sections_found = False
        
        return all_sections_found
        
    except Exception as e:
        print(f"❌ 读取SKILL.md失败: {e}")
        return False

def main():
    """主测试函数"""
    print("🚀 JIRA自动分析技能初始化测试")
    print("=" * 60)
    
    # 切换工作目录到技能根目录
    original_dir = os.getcwd()
    skill_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    os.chdir(skill_dir)
    
    try:
        test_results = []
        
        test_results.append(("目录结构", test_skill_structure()))
        test_results.append(("配置文件", test_config_loading()))
        test_results.append(("技能文档", test_skill_md()))
        
        print("\n📊 测试总结")
        print("=" * 60)
        
        total_tests = len(test_results)
        passed_tests = sum(1 for _, passed in test_results if passed)
        
        for test_name, passed in test_results:
            status = "✅ 通过" if passed else "❌ 失败"
            print(f"{status} {test_name}")
        
        print("-" * 60)
        print(f"总计: {passed_tests}/{total_tests} 项测试通过")
        
        if passed_tests == total_tests:
            print("\n🎉 技能初始化测试全部通过！")
            print("技能已准备就绪，可以安装使用。")
            return 0
        else:
            print("\n⚠️  部分测试失败，请修复上述问题")
            return 1
            
    finally:
        os.chdir(original_dir)

if __name__ == "__main__":
    sys.exit(main())

path: scripts/simple_debug.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简单JIRA访问调试
"""

import time
import logging
from playwright.sync_api import sync_playwright

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def simple_debug():
    try:
        playwright = sync_playwright().start()
        browser = playwright.chromium.launch(headless=False)
        page = browser.new_page()
        
        # 访问JIRA
        url = "http://jira.51baiwang.com"
        logger.info(f"访问: {url}")
        
        try:
            page.goto(url, timeout=30000)
            logger.info(f"页面加载完成，状态码: 200")
            logger.info(f"页面标题: {page.title()}")
            logger.info(f"当前URL: {page.url}")
            
            # 等待并截图
            time.sleep(3)
            page.screenshot(path="/tmp/jira_page.png")
            logger.info("截图已保存到 /tmp/jira_page.png")
            
            # 检查页面内容
            content = page.content()
            if "login" in content.lower() or "登录" in content.lower():
                logger.info("页面包含登录相关元素")
            
            # 查找所有输入框
            inputs = page.query_selector_all("input")
            logger.info(f"找到 {len(inputs)} 个输入框")
            
            for i, input_elem in enumerate(inputs[:5]):  # 只显示前5个
                input_type = input_elem.get_attribute("type") or "unknown"
                input_id = input_elem.get_attribute("id") or "no-id"
                input_name = input_elem.get_attribute("name") or "no-name"
                logger.info(f"输入框 {i+1}: type={input_type}, id={input_id}, name={input_name}")
            
        except Exception as e:
            logger.error(f"访问页面失败: {str(e)}")
        
        # 保持页面打开以便观察
        logger.info("保持页面打开10秒...")
        time.sleep(10)
        
        browser.close()
        playwright.stop()
        
    except Exception as e:
        logger.error(f"调试失败: {str(e)}")
        import traceback
        traceback.print_exc()

if __name__ == '__main__':
    simple_debug()

path: scripts/test_xingjian_rules.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试星舰工单处理规则
验证新增的星舰工单自动分配功能
"""

import sys
import os

# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from utils import check_required_fields, match_rule, calculate_match_score, load_config

def test_xingjian_detection():
    """测试星舰工单检测功能"""
    
    print("🧪 测试星舰工单处理规则")
    print("=" * 50)
    
    # 测试用例
    test_cases = [
        {
            "name": "完整星舰工单",
            "text": "星舰环境web连接器版本1.2.3日志文件见附件",
            "expected_rule": "星舰工单",
            "expected_assignee": "崔征明",
            "expected_reply": "星舰工单请及时处理，有问题可沟通"
        },
        {
            "name": "星舰工单但信息不全",
            "text": "星舰环境有问题，请处理",
            "expected_valid": False,
            "expected_missing_fields": ["通道类型（web连接器、rpa、乐企）", "项目相关服务模块版本号", "相关日志信息"]
        },
        {
            "name": "非星舰完整工单",
            "text": "飞船环境rpa版本2.0有日志错误",
            "expected_rule": "其他工单",
            "expected_assignee": "刘巍"
        },
        {
            "name": "认证相关工单",
            "text": "飞船环境web连接器版本1.0认证失败日志如下",
            "expected_rule": "认证相关工单",
            "expected_assignee": "张献文"
        }
    ]
    
    # 加载配置
    config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json')
    config = load_config(config_path)
    rules = config.get('rules', [])
    
    all_passed = True
    
    for test_case in test_cases:
        print(f"\n📋 测试: {test_case['name']}")
        print(f"   文本: {test_case['text'][:50]}...")
        
        # 检查必填信息
        check_result = check_required_fields(test_case['text'])
        
        if 'expected_valid' in test_case:
            # 测试信息完整性检查
            is_valid = check_result['is_valid']
            expected = test_case['expected_valid']
            
            if is_valid == expected:
                print(f"   ✅ 信息完整性检查通过")
                
                if not is_valid:
                    print(f"   🟡 缺失字段: {', '.join(check_result['missing_fields'])}")
            else:
                print(f"   ❌ 信息完整性检查失败")
                print(f"      预期: {expected}, 实际: {is_valid}")
                all_passed = False
                
        elif check_result['is_valid']:
            # 测试规则匹配
            matched_rule = match_rule(test_case['text'], rules)
            
            if matched_rule:
                rule_name = matched_rule.get('rule_name', '未知')
                assignee = matched_rule.get('assignee', '未知')
                reply = matched_rule.get('reply_message', '未知')
                
                # 检查规则匹配
                if rule_name == test_case.get('expected_rule'):
                    print(f"   ✅ 规则匹配通过: {rule_name}")
                else:
                    print(f"   ❌ 规则匹配失败")
                    print(f"      预期: {test_case.get('expected_rule')}, 实际: {rule_name}")
                    all_passed = False
                
                # 检查分配人
                if assignee == test_case.get('expected_assignee'):
                    print(f"   ✅ 分配人正确: {assignee}")
                else:
                    print(f"   ❌ 分配人错误")
                    print(f"      预期: {test_case.get('expected_assignee')}, 实际: {assignee}")
                    all_passed = False
                
                # 检查回复消息（如果指定）
                if 'expected_reply' in test_case:
                    if reply == test_case['expected_reply']:
                        print(f"   ✅ 回复消息正确: {reply}")
                    else:
                        print(f"   ❌ 回复消息错误")
                        print(f"      预期: {test_case['expected_reply']}")
                        print(f"      实际: {reply}")
                        all_passed = False
            else:
                print(f"   ❌ 未匹配到任何规则")
                all_passed = False
        else:
            print(f"   ❌ 工单信息不完整，无法测试规则匹配")
            print(f"      缺失字段: {', '.join(check_result['missing_fields'])}")
            all_passed = False
    
    # 测试优先级
    print("\n" + "=" * 50)
    print("🧪 测试星舰工单优先级")
    
    # 同时包含星舰和其他关键词的工单
    mixed_text = "星舰环境web连接器版本1.0认证失败，有日志"
    
    matched_rule = match_rule(mixed_text, rules)
    if matched_rule and matched_rule.get('rule_name') == '星舰工单':
        print(f"   ✅ 星舰工单优先级测试通过")
        print(f"      即使包含'认证'关键词，仍优先匹配星舰工单")
    else:
        print(f"   ❌ 星舰工单优先级测试失败")
        all_passed = False
    
    # 测试打回消息
    print("\n" + "=" * 50)
    print("🧪 测试打回消息格式")
    
    from utils import create_rejection_comment
    
    missing_fields = ["环境（星舰、飞船、云）", "通道类型（web连接器、rpa、乐企）"]
    rejection_msg = create_rejection_comment(missing_fields, config)
    
    if "http://confluence.51baiwang.com/pages/viewpage.action?pageId=80049485" in rejection_msg:
        print(f"   ✅ 打回消息包含规范文档链接")
    else:
        print(f"   ❌ 打回消息缺少规范文档链接")
        all_passed = False
    
    if "缺少以下必填信息" in rejection_msg:
        print(f"   ✅ 打回消息格式正确")
    else:
        print(f"   ❌ 打回消息格式错误")
        all_passed = False
    
    print("\n" + "=" * 50)
    if all_passed:
        print("🎉 所有测试通过！星舰工单处理规则已正确配置")
    else:
        print("⚠️  部分测试失败，请检查配置")
    
    return all_passed

def main():
    """主函数"""
    success = test_xingjian_detection()
    sys.exit(0 if success else 1)

if __name__ == "__main__":
    main()

path: scripts/jira_auto_analyze.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIRA工单自动分析处理脚本
自动分析新工单内容，检查四项必填信息，根据规则自动处理
"""

import os
import sys
import json
import re
import time
import argparse
from datetime import datetime
from typing import Dict, List, Tuple, Optional, Any
import logging

# 添加父目录到路径，以便导入utils
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

try:
    from playwright.sync_api import sync_playwright, Page, Browser, BrowserContext
    PLAYWRIGHT_AVAILABLE = True
except ImportError:
    PLAYWRIGHT_AVAILABLE = False
    print("⚠️  警告：Playwright未安装，请运行：pip install playwright")
    print("然后运行：python3 -m playwright install chromium")

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(os.path.join(os.path.dirname(__file__), '..', 'logs', 'jira_analyze.log')),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class JIRAAutoAnalyzer:
    """JIRA工单自动分析处理器"""
    
    def __init__(self, config_path: str = None, dry_run: bool = False):
        """初始化分析器
        
        Args:
            config_path: 配置文件路径
            dry_run: 模拟运行模式（不实际修改）
        """
        self.dry_run = dry_run
        self.config = self._load_config(config_path)
        self.rules = self.config.get('rules', [])
        self.jira_config = self.config.get('config', {})
        
        # JIRA登录凭证
        self.jira_credentials = {
            'username': self.jira_config.get('username', 'liuwei1'),
            'password': self.jira_config.get('password', 'Lw@123456')
        }
        
        # 浏览器实例
        self.browser: Optional[Browser] = None
        self.context: Optional[BrowserContext] = None
        self.page: Optional[Page] = None
        
        # 统计信息
        self.stats = {
            'total_issues': 0,
            'new_issues': 0,
            'valid_issues': 0,
            'rejected_issues': 0,
            'assigned_issues': 0,
            'processed_issues': 0
        }
        
        # 必填信息检查项
        self.required_fields = {
            '环境': ['星舰', '飞船', '云'],
            '通道类型': ['web连接器', 'rpa', '乐企'],
            '项目版本号': r'(\d+\.\d+\.\d+|v\d+|版本\d+)',
            '相关日志': ['日志', 'log', 'trace', 'error', 'stack']
        }
        
        logger.info(f"初始化JIRA自动分析器，模式：{'模拟运行' if dry_run else '实际执行'}")
        
    def _load_config(self, config_path: str = None) -> Dict:
        """加载配置文件"""
        if config_path is None:
            # 默认配置文件路径
            config_path = os.path.join(
                os.path.dirname(__file__), '..', 'config', 'config.json'
            )
        
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            logger.info(f"成功加载配置文件：{config_path}")
            return config
        except FileNotFoundError:
            logger.error(f"配置文件不存在：{config_path}")
            # 返回默认配置
            return self._get_default_config()
        except json.JSONDecodeError:
            logger.error(f"配置文件格式错误：{config_path}")
            return self._get_default_config()
    
    def _get_default_config(self) -> Dict:
        """获取默认配置"""
        return {
            "rules": [
                {
                    "rule_name": "认证相关工单",
                    "keywords": ["认证", "勾选", "授权", "权限", "token", "登录", "Auth"],
                    "assignee": "张献文",
                    "jira_username": "zhangxianwen",
                    "reply_message": "请献文协助处理此工单"
                },
                {
                    "rule_name": "乐企相关工单",
                    "keywords": ["乐企", "leqi", "LEQI"],
                    "assignee": "付强",
                    "jira_username": "fuqiang",
                    "reply_message": "请付强协助处理此工单"
                },
                {
                    "rule_name": "综服通道相关工单",
                    "keywords": ["综服", "通道", "银行", "工行", "中行", "农行", "建行", "招行"],
                    "assignee": "魏旭峰",
                    "jira_username": "weixufeng",
                    "reply_message": "请旭峰协助处理此工单"
                },
                {
                    "rule_name": "其他工单",
                    "keywords": [],
                    "assignee": "刘巍",
                    "jira_username": "liuwei1",
                    "reply_message": "收到，我会及时处理，请稍后"
                }
            ],
            "config": {
                "jira_url": "http://jira.51baiwang.com",
                "filter_id": "13123",
                "username": "liuwei1",
                "password": "Lw@123456",
                "rejection_message": "请提供相关环境、通道类型、版本号及日志信息",
                "check_new_only": True  # 只检查新工单
            }
        }
    
    def _init_browser(self):
        """初始化浏览器"""
        if not PLAYWRIGHT_AVAILABLE:
            raise RuntimeError("Playwright未安装，无法运行浏览器自动化")
        
        playwright = sync_playwright().start()
        self.browser = playwright.chromium.launch(
            headless=True,  # 无头模式
            args=['--disable-blink-features=AutomationControlled']  # 避免被检测为自动化
        )
        
        # 创建上下文
        self.context = self.browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
        )
        
        self.page = self.context.new_page()
        logger.info("浏览器初始化完成")
    
    def _login_jira(self) -> bool:
        """登录JIRA系统"""
        try:
            jira_url = self.jira_config.get('jira_url', 'http://jira.51baiwang.com')
            logger.info(f"正在访问JIRA: {jira_url}")
            
            self.page.goto(jira_url, wait_until='networkidle')
            time.sleep(2)
            
            # 检查是否已经登录
            if self.page.locator('#header-details-user-fullname').count() > 0:
                logger.info("检测到已登录状态")
                return True
            
            # 执行登录
            logger.info("执行登录操作...")
            
            # 输入用户名
            username_input = self.page.locator('#login-form-username')
            if username_input.count() > 0:
                username_input.fill(self.jira_credentials['username'])
                time.sleep(0.5)
            
            # 输入密码
            password_input = self.page.locator('#login-form-password')
            if password_input.count() > 0:
                password_input.fill(self.jira_credentials['password'])
                time.sleep(0.5)
            
            # 点击登录按钮
            submit_button = self.page.locator('#login-form-submit')
            if submit_button.count() > 0:
                submit_button.click()
                time.sleep(3)
            
            # 验证登录是否成功
            if self.page.locator('#header-details-user-fullname').count() > 0:
                logger.info("JIRA登录成功")
                return True
            else:
                logger.error("JIRA登录失败")
                return False
                
        except Exception as e:
            logger.error(f"登录JIRA时发生错误: {str(e)}")
            return False
    
    def _navigate_to_filter(self) -> bool:
        """导航到指定的filter页面"""
        try:
            filter_id = self.jira_config.get('filter_id', '13123')
            filter_url = f"{self.jira_config.get('jira_url')}/issues/?filter={filter_id}"
            
            logger.info(f"正在访问filter页面: {filter_url}")
            self.page.goto(filter_url, wait_until='networkidle')
            time.sleep(5)  # 等待表格加载
            
            # 检查是否成功加载
            if self.page.locator('tr.issuerow').count() > 0:
                logger.info(f"成功加载filter页面，找到工单表格")
                return True
            else:
                logger.warning("未找到工单表格，可能需要等待更长时间或检查页面结构")
                # 尝试等待更长时间
                time.sleep(10)
                if self.page.locator('tr.issuerow').count() > 0:
                    logger.info("重新检查后找到工单表格")
                    return True
                return False
                
        except Exception as e:
            logger.error(f"导航到filter页面时发生错误: {str(e)}")
            return False
    
    def _extract_issues(self) -> List[Dict]:
        """从页面提取工单信息"""
        issues = []
        
        try:
            # 查找所有工单行
            issue_rows = self.page.locator('tr.issuerow')
            count = issue_rows.count()
            logger.info(f"找到 {count} 个工单行")
            
            for i in range(min(count, 50)):  # 限制最多处理50个
                try:
                    row = issue_rows.nth(i)
                    
                    # 提取各列数据（根据已知的列索引）
                    cells = row.locator('td')
                    
                    # 状态（第0列）
                    status = cells.nth(0).inner_text().strip() if cells.nth(0).count() > 0 else ""
                    
                    # 工单号（第1列）
                    issue_key_elem = cells.nth(1).locator('a')
                    issue_key = issue_key_elem.inner_text().strip() if issue_key_elem.count() > 0 else ""
                    issue_link = issue_key_elem.get_attribute('href') if issue_key_elem.count() > 0 else ""
                    
                    # 概要（第2列）
                    summary = cells.nth(2).inner_text().strip() if cells.nth(2).count() > 0 else ""
                    
                    # 经办人（第8列）
                    assignee = cells.nth(8).inner_text().strip() if cells.nth(8).count() > 0 else ""
                    
                    # 产品模块（第12列）
                    product = cells.nth(12).inner_text().strip() if cells.nth(12).count() > 0 else ""
                    
                    # 创建日期（第3列）
                    created_date = cells.nth(3).inner_text().strip() if cells.nth(3).count() > 0 else ""
                    
                    # 只处理新工单（状态为"新建"）
                    check_new_only = self.jira_config.get('check_new_only', True)
                    if check_new_only and status != "新建":
                        continue
                    
                    issue = {
                        'row_index': i,
                        'status': status,
                        'issue_key': issue_key,
                        'issue_link': issue_link,
                        'summary': summary,
                        'assignee': assignee,
                        'product': product,
                        'created_date': created_date,
                        'is_valid': False,
                        'missing_fields': [],
                        'suggested_assignee': None,
                        'suggested_reply': None,
                        'rule_matched': None,
                        'needs_action': False
                    }
                    
                    issues.append(issue)
                    
                except Exception as e:
                    logger.warning(f"提取第 {i} 行工单时出错: {str(e)}")
                    continue
            
            self.stats['total_issues'] = len(issues)
            logger.info(f"成功提取 {len(issues)} 个工单")
            
        except Exception as e:
            logger.error(f"提取工单信息时发生错误: {str(e)}")
        
        return issues
    
    def _analyze_issue_content(self, issue: Dict) -> Dict:
        """分析工单内容，检查四项必填信息"""
        summary = issue.get('summary', '')
        description = self._get_issue_description(issue)
        full_text = f"{summary} {description}".lower()
        
        missing_fields = []
        
        # 检查环境信息
        env_found = False
        for env in self.required_fields['环境']:
            if env in full_text:
                env_found = True
                break
        if not env_found:
            missing_fields.append('环境（星舰、飞船、云）')
        
        # 检查通道类型
        channel_found = False
        for channel in self.required_fields['通道类型']:
            if channel in full_text:
                channel_found = True
                break
        if not channel_found:
            missing_fields.append('通道类型（web连接器、rpa、乐企）')
        
        # 检查项目版本号（云工单除外）
        version_found = False
        if '云' in full_text:
            # 云工单不需要版本号
            version_found = True
        else:
            version_pattern = self.required_fields['项目版本号']
            if re.search(version_pattern, full_text):
                version_found = True
        if not version_found:
            missing_fields.append('项目相关服务模块版本号')
        
        # 检查相关日志
        log_found = False
        for log_keyword in self.required_fields['相关日志']:
            if log_keyword in full_text:
                log_found = True
                break
        if not log_found:
            missing_fields.append('相关日志信息')
        
        issue['missing_fields'] = missing_fields
        issue['is_valid'] = len(missing_fields) == 0
        
        if issue['is_valid']:
            issue['needs_action'] = True
            logger.info(f"工单 {issue.get('issue_key')} 验证通过")
        else:
            issue['needs_action'] = True  # 需要打回
            logger.info(f"工单 {issue.get('issue_key')} 缺少字段: {missing_fields}")
        
        return issue
    
    def _get_issue_description(self, issue: Dict) -> str:
        """获取工单详细描述"""
        try:
            issue_key = issue.get('issue_key')
            if not issue_key:
                return ""
            
            # 打开工单详情页
            issue_url = f"{self.jira_config.get('jira_url')}/browse/{issue_key}"
            self.page.goto(issue_url, wait_until='networkidle')
            time.sleep(2)
            
            # 提取描述信息
            description = ""
            
            # 尝试多个可能的描述元素
            selectors = [
                '#description-val',
                '.description',
                '.issue-body-content',
                '.user-content-block'
            ]
            
            for selector in selectors:
                desc_elem = self.page.locator(selector)
                if desc_elem.count() > 0:
                    description = desc_elem.inner_text()
                    break
            
            # 返回filter页面
            filter_id = self.jira_config.get('filter_id', '13123')
            filter_url = f"{self.jira_config.get('jira_url')}/issues/?filter={filter_id}"
            self.page.goto(filter_url, wait_until='networkidle')
            time.sleep(3)
            
            return description.lower()
            
        except Exception as e:
            logger.warning(f"获取工单 {issue.get('issue_key', '未知')} 描述时出错: {str(e)}")
            return ""
    
    def _determine_assignment(self, issue: Dict) -> Dict:
        """根据工单内容确定分配规则"""
        if not issue.get('is_valid'):
            return issue
        
        summary = issue.get('summary', '').lower()
        best_match = None
        best_score = 0
        
        # 应用所有规则进行匹配
        for rule in self.rules:
            keywords = rule.get('keywords', [])
            match_score = self._calculate_match_score(summary, keywords)
            
            if match_score > best_score:
                best_score = match_score
                best_match = rule
        
        # 如果匹配到规则，使用该规则；否则使用"其他工单"规则
        if best_match and best_score > 0:
            issue['rule_matched'] = best_match.get('rule_name')
            issue['suggested_assignee'] = best_match.get('assignee')
            issue['suggested_jira_username'] = best_match.get('jira_username')
            issue['suggested_reply'] = best_match.get('reply_message')
        else:
            # 使用"其他工单"规则
            other_rule = next((r for r in self.rules if r['rule_name'] == '其他工单'), None)
            if other_rule:
                issue['rule_matched'] = other_rule.get('rule_name')
                issue['suggested_assignee'] = other_rule.get('assignee')
                issue['suggested_jira_username'] = other_rule.get('jira_username')
                issue['suggested_reply'] = other_rule.get('reply_message')
        
        return issue
    
    def _calculate_match_score(self, text: str, keywords: List[str]) -> float:
        """计算文本与关键词的匹配分数"""
        if not text or not keywords:
            return 0.0
        
        text_lower = text.lower()
        matches = 0
        
        for keyword in keywords:
            keyword_lower = keyword.lower()
            if keyword_lower in text_lower:
                matches += 1
                # 如果关键词较长，给予更高权重
                weight = min(len(keyword) / 10, 1.0)
                matches += weight
        
        # 计算分数：匹配的关键词数量 / 总关键词数量
        score = matches / len(keywords) if keywords else 0
        return min(score, 1.0)  # 确保不超过1.0
    
    def _reject_issue(self, issue: Dict) -> bool:
        """打回工单给联系人"""
        if self.dry_run:
            logger.info(f"[模拟] 将工单 {issue['issue_key']} 打回给联系人")
            return True
        
        try:
            issue_key = issue.get('issue_key')
            if not issue_key:
                logger.error("工单号为空，无法打回")
                return False
            
            # 打开工单详情页
            issue_url = f"{self.jira_config.get('jira_url')}/browse/{issue_key}"
            self.page.goto(issue_url, wait_until='networkidle')
            time.sleep(2)
            
            # 添加评论打回
            rejection_message = self.jira_config.get('rejection_message', '请提供相关环境、通道类型、版本号及日志信息')
            comment = f"{rejection_message}\n\n缺少的信息：{', '.join(issue['missing_fields'])}"
            
            # 点击"评论"按钮
            comment_button = self.page.locator('#comment-issue')
            if comment_button.count() > 0:
                comment_button.click()
                time.sleep(1)
            
            # 输入评论内容
            comment_textarea = self.page.locator('#comment')
            if comment_textarea.count() > 0:
                comment_textarea.fill(comment)
                time.sleep(1)
            
            # 提交评论
            submit_button = self.page.locator('#issue-comment-add-submit')
            if submit_button.count() > 0:
                submit_button.click()
                time.sleep(2)
            
            logger.info(f"成功打回工单 {issue_key}")
            return True
            
        except Exception as e:
            logger.error(f"打回工单 {issue.get('issue_key', '未知')} 时发生错误: {str(e)}")
            return False
    
    def _assign_and_reply(self, issue: Dict) -> bool:
        """分配工单并回复"""
        if self.dry_run:
            logger.info(f"[模拟] 将工单 {issue['issue_key']} 分配给 {issue['suggested_assignee']} 并回复")
            return True
        
        try:
            issue_key = issue.get('issue_key')
            if not issue_key:
                logger.error("工单号为空，无法分配")
                return False
            
            # 打开工单详情页
            issue_url = f"{self.jira_config.get('jira_url')}/browse/{issue_key}"
            self.page.goto(issue_url, wait_until='networkidle')
            time.sleep(2)
            
            # 1. 分配工单
            # 点击"编辑"按钮
            edit_button = self.page.locator('#edit-issue')
            if edit_button.count() > 0:
                edit_button.click()
                time.sleep(2)
            
            # 修改经办人字段
            assignee_field = self.page.locator('#assignee-field')
            if assignee_field.count() > 0:
                assignee_field.fill(issue['suggested_jira_username'])
                time.sleep(1)
                
                # 提交更改
                submit_button = self.page.locator('#edit-issue-submit')
                if submit_button.count() > 0:
                    submit_button.click()
                    time.sleep(3)
            
            # 2. 添加回复评论
            reply_message = issue.get('suggested_reply', '')
            if reply_message:
                # 点击"评论"按钮
                comment_button = self.page.locator('#comment-issue')
                if comment_button.count() > 0:
                    comment_button.click()
                    time.sleep(1)
                
                # 输入评论内容
                comment_textarea = self.page.locator('#comment')
                if comment_textarea.count() > 0:
                    comment_textarea.fill(reply_message)
                    time.sleep(1)
                
                # 提交评论
                submit_button = self.page.locator('#issue-comment-add-submit')
                if submit_button.count() > 0:
                    submit_button.click()
                    time.sleep(2)
            
            logger.info(f"成功分配工单 {issue_key} 给 {issue['suggested_assignee']} 并回复")
            return True
            
        except Exception as e:
            logger.error(f"分配工单 {issue.get('issue_key', '未知')} 时发生错误: {str(e)}")
            return False
    
    def _log_operation(self, issue: Dict, operation: str, success: bool):
        """记录操作日志"""
        log_dir = os.path.join(os.path.dirname(__file__), '..', 'logs')
        os.makedirs(log_dir, exist_ok=True)
        
        log_file = os.path.join(log_dir, 'operation_log.md')
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        status = "✅ 成功" if success else "❌ 失败"
        
        log_entry = f"""
## {timestamp} - {issue.get('issue_key', '未知工单')} - {operation}

- **工单**: {issue.get('issue_key')}
- **概要**: {issue.get('summary')}
- **状态**: {issue.get('status')}
- **操作**: {operation}
- **详情**: 
  - 是否有效: {'是' if issue.get('is_valid') else '否'}
  - 缺少字段: {', '.join(issue.get('missing_fields', []))}
  - 匹配规则: {issue.get('rule_matched', '无')}
  - 建议经办人: {issue.get('suggested_assignee', '无')}
- **操作结果**: {status}
- **操作模式**: {'模拟运行' if self.dry_run else '实际执行'}

---
"""
        
        try:
            with open(log_file, 'a', encoding='utf-8') as f:
                f.write(log_entry)
        except Exception as e:
            logger.error(f"写入日志文件失败: {str(e)}")
    
    def run(self):
        """运行自动分析处理流程"""
        logger.info("🚀 开始JIRA工单自动分析处理流程")
        
        try:
            # 1. 初始化浏览器
            self._init_browser()
            
            # 2. 登录JIRA
            if not self._login_jira():
                logger.error("登录失败，终止流程")
                return
            
            # 3. 导航到filter页面
            if not self._navigate_to_filter():
                logger.error("访问filter页面失败，终止流程")
                return
            
            # 4. 提取工单
            issues = self._extract_issues()
            if not issues:
                logger.warning("未提取到任何新工单")
                return
            
            self.stats['new_issues'] = len(issues)
            
            # 5. 分析工单内容
            logger.info("🔍 开始分析工单内容...")
            for i, issue in enumerate(issues):
                issues[i] = self._analyze_issue_content(issue)
                if issues[i]['is_valid']:
                    self.stats['valid_issues'] += 1
                    # 确定分配规则
                    issues[i] = self._determine_assignment(issues[i])
                else:
                    self.stats['rejected_issues'] += 1
            
            # 6. 显示分析结果
            self._display_analysis(issues)
            
            # 7. 执行处理
            if not self.dry_run and (self.stats['valid_issues'] > 0 or self.stats['rejected_issues'] > 0):
                total_actions = self.stats['valid_issues'] + self.stats['rejected_issues']
                confirm = input(f"\n⚠️  确认要处理 {total_actions} 个工单吗？(y/N): ")
                if confirm.lower() != 'y':
                    logger.info("用户取消操作")
                    return
            
            logger.info("🔄 开始执行处理...")
            for issue in issues:
                if not issue.get('needs_action'):
                    continue
                
                if issue.get('is_valid'):
                    # 有效工单：分配并回复
                    success = self._assign_and_reply(issue)
                    if success:
                        self.stats['assigned_issues'] += 1
                        self.stats['processed_issues'] += 1
                    self._log_operation(issue, "分配并回复", success)
                else:
                    # 无效工单：打回
                    success = self._reject_issue(issue)
                    if success:
                        self.stats['processed_issues'] += 1
                    self._log_operation(issue, "打回工单", success)
            
            # 8. 显示最终统计
            self._display_final_stats()
            
        except Exception as e:
            logger.error(f"运行自动分析处理流程时发生错误: {str(e)}")
            import traceback
            traceback.print_exc()
        finally:
            # 清理资源
            if self.page:
                self.page.close()
            if self.context:
                self.context.close()
            if self.browser:
                self.browser.close()
            logger.info("浏览器资源已清理")
    
    def _display_analysis(self, issues: List[Dict]):
        """显示分析结果"""
        print("\n" + "="*100)
        print("📊 JIRA工单分析处理结果")
        print("="*100)
        
        valid_issues = [i for i in issues if i.get('is_valid')]
        rejected_issues = [i for i in issues if not i.get('is_valid')]
        
        print(f"\n📋 发现 {self.stats['total_issues']} 个工单")
        print(f"   - 新工单: {self.stats['new_issues']}")
        print(f"   - 有效工单: {len(valid_issues)}")
        print(f"   - 需打回工单: {len(rejected_issues)}")
        
        if rejected_issues:
            print("\n❌ 需要打回的工单:")
            print("-"*100)
            for issue in rejected_issues:
                print(f"\n{issue['issue_key']} - {issue['summary'][:50]}...")
                print(f"  缺少信息: {', '.join(issue['missing_fields'])}")
        
        if valid_issues:
            print("\n✅ 有效的工单:")
            print("-"*100)
            for issue in valid_issues:
                print(f"\n{issue['issue_key']} - {issue['summary'][:50]}...")
                print(f"  匹配规则: {issue.get('rule_matched', '其他工单')}")
                print(f"  建议分配: {issue.get('suggested_assignee', '刘巍')}")
                print(f"  回复内容: {issue.get('suggested_reply', '收到，我会及时处理，请稍后')}")
        
        # 按规则统计
        if valid_issues:
            rule_stats = {}
            for issue in valid_issues:
                rule = issue.get('rule_matched', '其他工单')
                rule_stats[rule] = rule_stats.get(rule, 0) + 1
            
            print("\n🎯 按规则分配统计:")
            for rule, count in rule_stats.items():
                print(f"- {rule}: {count} 个工单")
    
    def _display_final_stats(self):
        """显示最终统计信息"""
        print("\n" + "="*100)
        print("🎯 处理完成")
        print("="*100)
        
        print(f"\n📊 最终统计:")
        print(f"- 总工单数: {self.stats['total_issues']}")
        print(f"- 新工单数: {self.stats['new_issues']}")
        print(f"- 有效工单: {self.stats['valid_issues']}")
        print(f"- 打回工单: {self.stats['rejected_issues']}")
        print(f"- 分配工单: {self.stats['assigned_issues']}")
        print(f"- 处理工单: {self.stats['processed_issues']}")
        
        if self.dry_run:
            print("\n💡 注意：本次为模拟运行模式，未实际修改JIRA数据")
        
        print(f"\n⏰ 完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print("="*100)

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='JIRA工单自动分析处理工具')
    parser.add_argument('--dry-run', action='store_true', help='模拟运行，不实际修改数据')
    parser.add_argument('--config', type=str, help='配置文件路径')
    
    args = parser.parse_args()
    
    # 检查依赖
    if not PLAYWRIGHT_AVAILABLE:
        print("错误：请先安装Playwright")
        print("运行: pip install playwright")
        print("然后运行: python3 -m playwright install chromium")
        sys.exit(1)
    
    # 创建分析器
    analyzer = JIRAAutoAnalyzer(
        config_path=args.config,
        dry_run=args.dry_run
    )
    
    # 运行分析处理流程
    analyzer.run()

if __name__ == '__main__':
    main()

path: scripts/quick_verify.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
快速验证星舰工单处理规则
"""

import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from utils import match_rule, load_config

def verify_xingjian_priority():
    """验证星舰工单优先级"""
    config = load_config(os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json'))
    rules = config['rules']
    
    # 测试混合工单
    test_texts = [
        ("纯星舰工单", "星舰环境有问题", "星舰工单"),
        ("星舰+认证", "星舰环境认证失败", "星舰工单"),
        ("星舰+乐企", "星舰环境乐企问题", "星舰工单"),
        ("认证+星舰", "认证问题星舰环境", "星舰工单"),
        ("纯认证工单", "飞船认证问题", "认证相关工单"),
        ("纯乐企工单", "乐企有问题", "乐企相关工单"),
    ]
    
    print("🔍 验证星舰工单优先级")
    print("=" * 50)
    
    all_pass = True
    for name, text, expected_rule in test_texts:
        matched = match_rule(text, rules)
        actual_rule = matched['rule_name'] if matched else "无匹配"
        
        if actual_rule == expected_rule:
            print(f"✅ {name}: {actual_rule}")
        else:
            print(f"❌ {name}: 期望={expected_rule}, 实际={actual_rule}")
            all_pass = False
    
    print("\n📋 规则优先级（从高到低）：")
    sorted_rules = sorted(rules, key=lambda x: x.get('priority', 0), reverse=True)
    for rule in sorted_rules:
        print(f"  {rule['rule_name']} (priority={rule.get('priority', 1)})")
    
    return all_pass

if __name__ == '__main__':
    success = verify_xingjian_priority()
    sys.exit(0 if success else 1)

path: scripts/demo_xingjian_rules.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
星舰工单处理规则演示脚本
展示新增的星舰工单自动处理功能
"""

import sys
import os

# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from utils import check_required_fields, match_rule, create_rejection_comment, load_config

def main():
    """演示星舰工单处理规则"""
    
    print("🚀 JIRA星舰工单处理规则演示")
    print("=" * 60)
    
    # 加载配置
    config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json')
    config = load_config(config_path)
    rules = config.get('rules', [])
    
    # 演示案例
    demo_cases = [
        {
            "title": "案例1：完整星舰工单",
            "description": "星舰环境web连接器版本2.1.0，认证失败，日志见附件",
            "notes": "包含所有必填信息，应自动分配给崔征明"
        },
        {
            "title": "案例2：星舰工单但信息不全",
            "description": "星舰有问题，帮忙处理一下",
            "notes": "缺少通道类型、版本号、日志信息，应自动退回"
        },
        {
            "title": "案例3：混合关键词工单（星舰+认证）",
            "description": "星舰环境rpa版本1.5认证授权失败，有error日志",
            "notes": "星舰优先级最高，仍分配给崔征明"
        },
        {
            "title": "案例4：非星舰认证工单",
            "description": "飞船环境web连接器版本3.0认证问题，日志如下",
            "notes": "应分配给张献文"
        },
        {
            "title": "案例5：信息完全不完整",
            "description": "系统有问题，请处理",
            "notes": "应退回并提示规范文档链接"
        }
    ]
    
    for i, case in enumerate(demo_cases, 1):
        print(f"\n📋 {i}. {case['title']}")
        print(f"📝 工单描述：{case['description']}")
        print(f"📌 预期：{case['notes']}")
        print("-" * 40)
        
        # 检查信息完整性
        check_result = check_required_fields(case['description'])
        
        if check_result['is_valid']:
            print("✅ 工单信息完整")
            
            # 匹配规则
            matched_rule = match_rule(case['description'], rules)
            
            if matched_rule:
                print(f"📊 匹配规则：{matched_rule.get('rule_name')}")
                print(f"👤 分配负责人：{matched_rule.get('assignee')}")
                print(f"💬 自动回复：{matched_rule.get('reply_message')}")
                
                # 显示匹配详情
                env = check_result['field_details']['环境']['value']
                channel = check_result['field_details']['通道类型']['value']
                version = check_result['field_details']['项目版本号']['value']
                log = check_result['field_details']['相关日志']['value']
                
                print(f"🔍 提取信息：")
                print(f"   - 环境：{env}")
                print(f"   - 通道类型：{channel}")
                print(f"   - 版本号：{version}")
                print(f"   - 日志：{log}")
            else:
                print("❌ 未匹配到任何规则")
        else:
            print("❌ 工单信息不完整")
            print(f"🔍 缺失信息：{', '.join(check_result['missing_fields'])}")
            
            # 生成打回消息
            rejection_msg = create_rejection_comment(check_result['missing_fields'], config)
            print(f"💬 自动退回消息：")
            print(f"   {rejection_msg[:100]}...")
    
    # 显示当前规则配置
    print("\n" + "=" * 60)
    print("📋 当前配置规则（按优先级排序）：")
    
    sorted_rules = sorted(rules, key=lambda x: x.get('priority', 0), reverse=True)
    for rule in sorted_rules:
        priority = rule.get('priority', 1)
        name = rule.get('rule_name')
        keywords = rule.get('keywords', [])
        assignee = rule.get('assignee')
        
        priority_star = "⭐" * min(priority, 5)
        print(f"\n{priority_star} {name}")
        print(f"   负责人：{assignee}")
        if keywords:
            print(f"   关键词：{', '.join(keywords)}")
        if 'reply_message' in rule:
            print(f"   回复：{rule['reply_message']}")
    
    # 显示打回配置
    print("\n" + "=" * 60)
    print("📝 信息不完整工单处理配置：")
    rejection_msg = config.get('config', {}).get('rejection_message', '')
    if rejection_msg:
        print(f"💬 打回消息：")
        print(f"   {rejection_msg}")
    
    print("\n" + "=" * 60)
    print("🎯 规则总结：")
    print("1. 星舰工单具有最高优先级（priority=10）")
    print("2. 信息完整的星舰工单自动分配给崔征明")
    print("3. 信息不完整的工单自动退回，包含规范文档链接")
    print("4. 其他规则按关键词匹配分数排序")
    print("5. 所有处理都自动记录日志")
    
    print("\n🚀 星舰工单处理规则配置完成！")

if __name__ == '__main__':
    main()

path: scripts/setup.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIRA自动分析技能安装脚本
"""

import os
import sys
import subprocess
import logging
from pathlib import Path

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def check_python_version():
    """检查Python版本"""
    import platform
    python_version = platform.python_version()
    logger.info(f"Python版本: {python_version}")
    
    version_parts = python_version.split('.')
    major_version = int(version_parts[0])
    minor_version = int(version_parts[1])
    
    if major_version < 3 or (major_version == 3 and minor_version < 7):
        logger.error("需要Python 3.7或更高版本")
        return False
    
    return True

def install_playwright():
    """安装Playwright"""
    logger.info("正在安装Playwright...")
    
    try:
        # 安装Playwright Python包
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", "playwright"],
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            logger.info("Playwright安装成功")
        else:
            logger.error(f"Playwright安装失败: {result.stderr}")
            return False
        
        # 安装Chromium浏览器
        logger.info("正在安装Chromium浏览器...")
        result = subprocess.run(
            [sys.executable, "-m", "playwright", "install", "chromium"],
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            logger.info("Chromium浏览器安装成功")
        else:
            logger.error(f"Chromium浏览器安装失败: {result.stderr}")
            return False
        
        return True
        
    except Exception as e:
        logger.error(f"安装过程中发生错误: {str(e)}")
        return False

def create_directories():
    """创建必要的目录"""
    skill_dir = Path(__file__).parent.parent
    directories = [
        skill_dir / "logs",
        skill_dir / "config",
        skill_dir / "scripts",
        skill_dir / "references",
        skill_dir / "assets"
    ]
    
    for directory in directories:
        try:
            directory.mkdir(exist_ok=True)
            logger.info(f"创建目录: {directory}")
        except Exception as e:
            logger.error(f"创建目录 {directory} 失败: {str(e)}")
            return False
    
    return True

def check_config_file():
    """检查配置文件"""
    config_file = Path(__file__).parent.parent / "config" / "config.json"
    
    if not config_file.exists():
        logger.warning(f"配置文件不存在: {config_file}")
        logger.info("将使用默认配置")
        
        # 创建默认配置文件
        default_config = {
            "rules": [
                {
                    "rule_name": "认证相关工单",
                    "keywords": ["认证", "勾选", "授权", "权限", "token", "登录", "Auth"],
                    "assignee": "张献文",
                    "jira_username": "zhangxianwen",
                    "reply_message": "请献文协助处理此工单"
                },
                {
                    "rule_name": "乐企相关工单",
                    "keywords": ["乐企", "leqi", "LEQI"],
                    "assignee": "付强",
                    "jira_username": "fuqiang",
                    "reply_message": "请付强协助处理此工单"
                },
                {
                    "rule_name": "综服通道相关工单",
                    "keywords": ["综服", "通道", "银行", "工行", "中行", "农行", "建行", "招行"],
                    "assignee": "魏旭峰",
                    "jira_username": "weixufeng",
                    "reply_message": "请旭峰协助处理此工单"
                },
                {
                    "rule_name": "其他工单",
                    "keywords": [],
                    "assignee": "刘巍",
                    "jira_username": "liuwei1",
                    "reply_message": "收到，我会及时处理，请稍后"
                }
            ],
            "config": {
                "jira_url": "http://jira.51baiwang.com",
                "filter_id": "13123",
                "username": "liuwei1",
                "password": "Lw@123456",
                "rejection_message": "请提供相关环境、通道类型、版本号及日志信息",
                "check_new_only": True
            }
        }
        
        try:
            import json
            config_file.parent.mkdir(exist_ok=True)
            with open(config_file, 'w', encoding='utf-8') as f:
                json.dump(default_config, f, ensure_ascii=False, indent=2)
            logger.info(f"已创建默认配置文件: {config_file}")
        except Exception as e:
            logger.error(f"创建配置文件失败: {str(e)}")
            return False
    
    return True

def test_script():
    """测试脚本是否能正常运行"""
    logger.info("正在测试脚本...")
    
    try:
        # 测试导入模块
        sys.path.insert(0, str(Path(__file__).parent))
        from jira_auto_analyze import PLAYWRIGHT_AVAILABLE
        
        if PLAYWRIGHT_AVAILABLE:
            logger.info("Playwright导入测试成功")
        else:
            logger.warning("Playwright未安装，无法运行浏览器自动化")
        
        # 测试工具函数
        from utils import check_required_fields, calculate_match_score
        
        # 测试必填信息检查
        test_text = "星舰环境，web连接器，版本1.2.3，错误日志"
        result = check_required_fields(test_text)
        if result['is_valid']:
            logger.info("必填信息检查测试成功")
        else:
            logger.warning("必填信息检查测试失败，但可能只是测试数据问题")
        
        # 测试匹配分数计算
        text = "认证相关工单需要处理"
        keywords = ["认证", "勾选", "授权"]
        score = calculate_match_score(text, keywords)
        logger.info(f"匹配分数计算测试成功，分数: {score:.2f}")
        
        logger.info("脚本测试通过")
        return True
        
    except Exception as e:
        logger.error(f"脚本测试失败: {str(e)}")
        return False

def setup_complete():
    """安装完成提示"""
    print("\n" + "="*60)
    print("✅ JIRA自动分析技能安装完成")
    print("="*60)
    
    skill_dir = Path(__file__).parent.parent
    
    print("\n📁 技能目录结构:")
    print(f"  {skill_dir}/")
    print(f"  ├── SKILL.md                 # 技能文档")
    print(f"  ├── scripts/")
    print(f"  │   ├── jira_auto_analyze.py # 核心脚本")
    print(f"  │   ├── utils.py             # 工具函数")
    print(f"  │   └── setup.py             # 安装脚本")
    print(f"  ├── config/")
    print(f"  │   └── config.json          # 配置文件")
    print(f"  ├── references/")
    print(f"  │   ├── jira_structure.md    # JIRA结构参考")
    print(f"  │   └── usage_guide.md       # 使用指南")
    print(f"  └── logs/                    # 日志目录")
    
    print("\n🚀 使用方法:")
    print("  1. 编辑配置文件 (如果需要修改JIRA账号密码):")
    print(f"     {skill_dir}/config/config.json")
    print("  2. 模拟运行测试:")
    print(f"     cd {skill_dir} && python3 scripts/jira_auto_analyze.py --dry-run")
    print("  3. 实际执行:")
    print(f"     cd {skill_dir} && python3 scripts/jira_auto_analyze.py")
    
    print("\n🔧 配置说明:")
    print("  - 修改JIRA账号密码: 编辑config.json中的username和password")
    print("  - 修改分配规则: 编辑config.json中的rules数组")
    print("  - 修改打回消息: 编辑config.json中的rejection_message")
    
    print("\n📝 注意事项:")
    print("  - 首次使用建议先运行 --dry-run 模式测试")
    print("  - 确保JIRA账户有修改工单和添加评论的权限")
    print("  - 操作前会显示分析结果，需要确认后才执行")
    
    print("\n" + "="*60)

def main():
    """主安装函数"""
    print("🚀 开始安装JIRA自动分析技能")
    print("="*60)
    
    # 检查Python版本
    if not check_python_version():
        return 1
    
    # 创建目录
    if not create_directories():
        return 1
    
    # 检查配置文件
    if not check_config_file():
        return 1
    
    # 安装Playwright
    print("\n" + "-"*60)
    print("📦 安装依赖")
    print("-"*60)
    
    install_choice = input("是否安装Playwright和Chromium浏览器？(y/N): ")
    if install_choice.lower() == 'y':
        if not install_playwright():
            print("⚠️  依赖安装失败，可能影响功能使用")
            continue_choice = input("是否继续安装？(y/N): ")
            if continue_choice.lower() != 'y':
                return 1
    else:
        print("跳过依赖安装，请确保已安装Playwright和Chromium浏览器")
    
    # 测试脚本
    print("\n" + "-"*60)
    print("🧪 测试功能")
    print("-"*60)
    
    test_choice = input("是否运行功能测试？(y/N): ")
    if test_choice.lower() == 'y':
        if not test_script():
            print("⚠️  功能测试失败，可能影响正常使用")
            continue_choice = input("是否继续安装？(y/N): ")
            if continue_choice.lower() != 'y':
                return 1
    
    # 安装完成
    setup_complete()
    
    return 0

if __name__ == '__main__':
    sys.exit(main())

path: scripts/utils.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIRA自动分析工具函数
"""

import re
import json
import logging
from typing import Dict, List, Optional

logger = logging.getLogger(__name__)

def check_required_fields(text: str) -> Dict:
    """检查文本中是否包含四项必填信息
    
    Args:
        text: 要检查的文本
        
    Returns:
        dict: 包含检查结果和缺失字段
    """
    text_lower = text.lower()
    
    # 必填字段定义
    required_fields = {
        '环境': {
            'keywords': ['星舰', '飞船', '云'],
            'found': False,
            'value': None
        },
        '通道类型': {
            'keywords': ['web连接器', 'rpa', '乐企'],
            'found': False,
            'value': None
        },
        '项目版本号': {
            'pattern': r'(\d+\.\d+\.\d+|v\d+|版本\d+)',
            'found': False,
            'value': None
        },
        '相关日志': {
            'keywords': ['日志', 'log', 'trace', 'error', 'stack'],
            'found': False,
            'value': None
        }
    }
    
    missing_fields = []
    
    # 检查环境信息
    for env in required_fields['环境']['keywords']:
        if env in text_lower:
            required_fields['环境']['found'] = True
            required_fields['环境']['value'] = env
            break
    
    if not required_fields['环境']['found']:
        missing_fields.append('环境（星舰、飞船、云）')
    
    # 检查通道类型
    for channel in required_fields['通道类型']['keywords']:
        if channel in text_lower:
            required_fields['通道类型']['found'] = True
            required_fields['通道类型']['value'] = channel
            break
    
    if not required_fields['通道类型']['found']:
        missing_fields.append('通道类型（web连接器、rpa、乐企）')
    
    # 检查项目版本号（云工单除外）
    if '云' in text_lower:
        # 云工单不需要版本号
        required_fields['项目版本号']['found'] = True
        required_fields['项目版本号']['value'] = '云环境无需版本号'
    else:
        version_match = re.search(required_fields['项目版本号']['pattern'], text_lower)
        if version_match:
            required_fields['项目版本号']['found'] = True
            required_fields['项目版本号']['value'] = version_match.group(1)
    
    if not required_fields['项目版本号']['found']:
        missing_fields.append('项目相关服务模块版本号')
    
    # 检查相关日志
    for log_keyword in required_fields['相关日志']['keywords']:
        if log_keyword in text_lower:
            required_fields['相关日志']['found'] = True
            required_fields['相关日志']['value'] = log_keyword
            break
    
    if not required_fields['相关日志']['found']:
        missing_fields.append('相关日志信息')
    
    return {
        'is_valid': len(missing_fields) == 0,
        'missing_fields': missing_fields,
        'field_details': required_fields
    }

def match_rule(text: str, rules: List[Dict]) -> Optional[Dict]:
    """根据文本内容匹配分配规则，支持优先级处理
    
    Args:
        text: 要匹配的文本
        rules: 规则列表
        
    Returns:
        dict: 匹配的规则，如果没有匹配则返回None
    """
    text_lower = text.lower()
    best_match = None
    best_score = 0
    
    # 首先按优先级排序，高优先级规则先处理
    sorted_rules = sorted(rules, key=lambda x: x.get('priority', 0), reverse=True)
    
    for rule in sorted_rules:
        keywords = rule.get('keywords', [])
        if not keywords:  # 跳过空关键词规则（如"其他工单"）
            continue
            
        match_score = calculate_match_score(text_lower, keywords)
        
        # 如果匹配分数大于0，且大于当前最佳分数，则更新最佳匹配
        if match_score > best_score:
            best_score = match_score
            best_match = rule
            
        # 对于星舰工单，如果有匹配，立即返回（最高优先级）
        if rule.get('rule_name') == '星舰工单' and match_score > 0:
            return rule
    
    # 如果匹配分数大于0，返回匹配的规则
    if best_match and best_score > 0:
        return best_match
    
    # 否则返回"其他工单"规则
    other_rule = next((r for r in rules if r.get('rule_name') == '其他工单'), None)
    return other_rule

def calculate_match_score(text: str, keywords: List[str]) -> float:
    """计算文本与关键词的匹配分数
    
    Args:
        text: 文本内容
        keywords: 关键词列表
        
    Returns:
        float: 匹配分数（0-1）
    """
    if not text or not keywords:
        return 0.0
    
    matches = 0
    
    for keyword in keywords:
        keyword_lower = keyword.lower()
        if keyword_lower in text:
            matches += 1
            # 如果关键词较长，给予更高权重
            weight = min(len(keyword) / 10, 1.0)
            matches += weight
    
    # 计算分数：匹配的关键词数量 / 总关键词数量
    score = matches / len(keywords) if keywords else 0
    return min(score, 1.0)  # 确保不超过1.0

def load_config(config_path: str) -> Dict:
    """加载配置文件
    
    Args:
        config_path: 配置文件路径
        
    Returns:
        dict: 配置信息
    """
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
        logger.info(f"成功加载配置文件：{config_path}")
        return config
    except FileNotFoundError:
        logger.error(f"配置文件不存在：{config_path}")
        raise
    except json.JSONDecodeError:
        logger.error(f"配置文件格式错误：{config_path}")
        raise

def save_config(config: Dict, config_path: str):
    """保存配置文件
    
    Args:
        config: 配置信息
        config_path: 配置文件路径
    """
    try:
        os.makedirs(os.path.dirname(config_path), exist_ok=True)
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(config, f, ensure_ascii=False, indent=2)
        logger.info(f"成功保存配置文件：{config_path}")
    except Exception as e:
        logger.error(f"保存配置文件失败：{str(e)}")
        raise

def format_issue_display(issue: Dict) -> str:
    """格式化工单显示信息
    
    Args:
        issue: 工单信息
        
    Returns:
        str: 格式化后的显示信息
    """
    lines = []
    lines.append(f"工单号: {issue.get('issue_key', '未知')}")
    lines.append(f"状态: {issue.get('status', '未知')}")
    lines.append(f"概要: {issue.get('summary', '无')[:80]}...")
    
    if issue.get('is_valid'):
        lines.append(f"有效性: ✅ 通过")
        lines.append(f"匹配规则: {issue.get('rule_matched', '其他工单')}")
        lines.append(f"建议分配: {issue.get('suggested_assignee', '刘巍')}")
        lines.append(f"回复内容: {issue.get('suggested_reply', '收到，我会及时处理，请稍后')}")
    else:
        lines.append(f"有效性: ❌ 未通过")
        lines.append(f"缺少信息: {', '.join(issue.get('missing_fields', []))}")
    
    return "\n".join(lines)

def create_rejection_comment(missing_fields: List[str], config: Dict) -> str:
    """创建打回评论，包含规范文档链接
    
    Args:
        missing_fields: 缺失字段列表
        config: 配置信息
        
    Returns:
        str: 评论内容
    """
    base_message = config.get('rejection_message', '请提供相关环境、通道类型、版本号及日志信息')
    
    if missing_fields:
        # 格式化缺失信息
        missing_info = '\n'.join([f"- {field}" for field in missing_fields])
        details = f"\n\n**缺少以下必填信息：**\n{missing_info}\n\n请参考工单提交规范补充完整信息：http://confluence.51baiwang.com/pages/viewpage.action?pageId=80049485"
        return base_message + details
    else:
        return base_message

path: scripts/debug_jira_login.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIRA登录调试脚本
用于调试JIRA登录问题
"""

import os
import sys
import time
import logging
from playwright.sync_api import sync_playwright

# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

def debug_jira_login():
    """调试JIRA登录"""
    logger.info("🚀 开始JIRA登录调试")
    
    try:
        playwright = sync_playwright().start()
        browser = playwright.chromium.launch(
            headless=False,  # 使用有头模式以便观察
            args=['--disable-blink-features=AutomationControlled']
        )
        
        # 创建上下文
        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
        )
        
        page = context.new_page()
        logger.info("浏览器初始化完成")
        
        # 访问JIRA
        jira_url = "http://jira.51baiwang.com"
        logger.info(f"正在访问JIRA: {jira_url}")
        
        page.goto(jira_url, wait_until='networkidle')
        time.sleep(3)
        
        # 截图当前页面
        screenshot_path = os.path.join(os.path.dirname(__file__), '..', 'logs', 'jira_login_page.png')
        page.screenshot(path=screenshot_path, full_page=True)
        logger.info(f"页面截图已保存: {screenshot_path}")
        
        # 检查页面内容
        logger.info("检查页面内容...")
        page_content = page.content()
        
        # 检查登录表单
        login_form_selectors = [
            '#login-form',
            '#login',
            'form[action*="login"]',
            'input[name="os_username"]',
            'input[name="username"]',
            '#login-form-username'
        ]
        
        for selector in login_form_selectors:
            count = page.locator(selector).count()
            if count > 0:
                logger.info(f"找到登录表单元素: {selector} (数量: {count})")
        
        # 检查是否已登录
        user_elements = page.locator('#header-details-user-fullname')
        if user_elements.count() > 0:
            logger.info("检测到已登录状态")
            username = user_elements.inner_text()
            logger.info(f"当前登录用户: {username}")
        else:
            logger.info("未检测到登录状态")
            
            # 尝试查找用户名输入框
            username_selectors = [
                '#login-form-username',
                '#username',
                'input[name="os_username"]',
                'input[name="username"]',
                'input[type="text"][id*="user"]',
                'input[type="text"][name*="user"]'
            ]
            
            for selector in username_selectors:
                elements = page.locator(selector)
                count = elements.count()
                if count > 0:
                    logger.info(f"找到用户名输入框: {selector}")
                    # 尝试输入用户名
                    try:
                        elements.first.fill("liuwei1")
                        logger.info(f"已输入用户名到 {selector}")
                    except Exception as e:
                        logger.error(f"输入用户名失败: {str(e)}")
            
            # 尝试查找密码输入框
            password_selectors = [
                '#login-form-password',
                '#password',
                'input[name="os_password"]',
                'input[name="password"]',
                'input[type="password"]'
            ]
            
            for selector in password_selectors:
                elements = page.locator(selector)
                count = elements.count()
                if count > 0:
                    logger.info(f"找到密码输入框: {selector}")
                    # 尝试输入密码
                    try:
                        elements.first.fill("Lw@123456")
                        logger.info(f"已输入密码到 {selector}")
                    except Exception as e:
                        logger.error(f"输入密码失败: {str(e)}")
            
            # 尝试查找登录按钮
            submit_selectors = [
                '#login-form-submit',
                '#login-submit',
                'input[type="submit"][value*="登录"]',
                'input[type="submit"][value*="Login"]',
                'button[type="submit"]',
                '.login-button'
            ]
            
            for selector in submit_selectors:
                elements = page.locator(selector)
                count = elements.count()
                if count > 0:
                    logger.info(f"找到登录按钮: {selector}")
                    # 截图
                    page.screenshot(path=os.path.join(os.path.dirname(__file__), '..', 'logs', 'before_login.png'))
                    
                    # 尝试点击
                    try:
                        elements.first.click()
                        logger.info(f"已点击登录按钮: {selector}")
                        time.sleep(3)
                        
                        # 检查登录是否成功
                        if page.locator('#header-details-user-fullname').count() > 0:
                            logger.info("登录成功！")
                        else:
                            logger.info("登录后状态检查...")
                            page.screenshot(path=os.path.join(os.path.dirname(__file__), '..', 'logs', 'after_login.png'))
                    except Exception as e:
                        logger.error(f"点击登录按钮失败: {str(e)}")
        
        # 等待一段时间观察
        logger.info("等待10秒观察页面...")
        time.sleep(10)
        
        # 最终截图
        final_screenshot = os.path.join(os.path.dirname(__file__), '..', 'logs', 'final_page.png')
        page.screenshot(path=final_screenshot, full_page=True)
        logger.info(f"最终页面截图: {final_screenshot}")
        
        # 输出页面标题和URL
        logger.info(f"页面标题: {page.title()}")
        logger.info(f"当前URL: {page.url}")
        
        # 清理资源
        page.close()
        context.close()
        browser.close()
        playwright.stop()
        
        logger.info("调试完成")
        
    except Exception as e:
        logger.error(f"调试过程中发生错误: {str(e)}")
        import traceback
        traceback.print_exc()

if __name__ == '__main__':
    debug_jira_login()

path: scripts/test_functions.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIRA自动分析功能测试脚本
用于测试核心功能而不需要实际访问JIRA
"""

import sys
import os

# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 定义测试函数，避免导入问题
def check_required_fields(text: str) -> dict:
    """检查文本中是否包含四项必填信息"""
    text_lower = text.lower()
    
    required_fields = {
        '环境': ['星舰', '飞船', '云'],
        '通道类型': ['web连接器', 'rpa', '乐企'],
        '项目版本号': r'(\d+\.\d+\.\d+|v\d+|版本\d+)',
        '相关日志': ['日志', 'log', 'trace', 'error', 'stack']
    }
    
    missing_fields = []
    
    # 检查环境信息
    env_found = False
    for env in required_fields['环境']:
        if env in text_lower:
            env_found = True
            break
    if not env_found:
        missing_fields.append('环境（星舰、飞船、云）')
    
    # 检查通道类型
    channel_found = False
    for channel in required_fields['通道类型']:
        if channel in text_lower:
            channel_found = True
            break
    if not channel_found:
        missing_fields.append('通道类型（web连接器、rpa、乐企）')
    
    # 检查项目版本号（云工单除外）
    import re
    version_found = False
    if '云' in text_lower:
        # 云工单不需要版本号
        version_found = True
    else:
        version_pattern = required_fields['项目版本号']
        if re.search(version_pattern, text_lower):
            version_found = True
    if not version_found:
        missing_fields.append('项目相关服务模块版本号')
    
    # 检查相关日志
    log_found = False
    for log_keyword in required_fields['相关日志']:
        if log_keyword in text_lower:
            log_found = True
            break
    if not log_found:
        missing_fields.append('相关日志信息')
    
    return {
        'is_valid': len(missing_fields) == 0,
        'missing_fields': missing_fields
    }

def calculate_match_score(text: str, keywords: list) -> float:
    """计算文本与关键词的匹配分数"""
    if not text or not keywords:
        return 0.0
    
    text_lower = text.lower()
    matches = 0
    
    for keyword in keywords:
        keyword_lower = keyword.lower()
        if keyword_lower in text_lower:
            matches += 1
            # 如果关键词较长，给予更高权重
            weight = min(len(keyword) / 10, 1.0)
            matches += weight
    
    # 计算分数：匹配的关键词数量 / 总关键词数量
    score = matches / len(keywords) if keywords else 0
    return min(score, 1.0)  # 确保不超过1.0

def match_rule(text: str, rules: list) -> dict:
    """根据文本内容匹配分配规则"""
    text_lower = text.lower()
    best_match = None
    best_score = 0
    
    for rule in rules:
        keywords = rule.get('keywords', [])
        if not keywords:  # 跳过空关键词规则（如"其他工单"）
            continue
            
        match_score = calculate_match_score(text_lower, keywords)
        
        if match_score > best_score:
            best_score = match_score
            best_match = rule
    
    # 如果匹配分数大于0，返回匹配的规则
    if best_match and best_score > 0:
        return best_match
    
    # 否则返回"其他工单"规则
    other_rule = next((r for r in rules if r.get('rule_name') == '其他工单'), None)
    return other_rule

def format_issue_display(issue: dict) -> str:
    """格式化工单显示信息"""
    lines = []
    lines.append(f"工单号: {issue.get('issue_key', '未知')}")
    lines.append(f"状态: {issue.get('status', '未知')}")
    lines.append(f"概要: {issue.get('summary', '无')[:80]}...")
    
    if issue.get('is_valid'):
        lines.append(f"有效性: ✅ 通过")
        lines.append(f"匹配规则: {issue.get('rule_matched', '其他工单')}")
        lines.append(f"建议分配: {issue.get('suggested_assignee', '刘巍')}")
        lines.append(f"回复内容: {issue.get('suggested_reply', '收到，我会及时处理，请稍后')}")
    else:
        lines.append(f"有效性: ❌ 未通过")
        lines.append(f"缺少信息: {', '.join(issue.get('missing_fields', []))}")
    
    return "\n".join(lines)

def test_required_fields():
    """测试必填信息检查功能"""
    print("🧪 测试必填信息检查功能")
    print("-" * 60)
    
    test_cases = [
        {
            "name": "完整信息测试",
            "text": "星舰环境，web连接器，版本1.2.3，错误日志",
            "expected": True
        },
        {
            "name": "云环境测试（无版本号）",
            "text": "云环境，rpa通道，错误日志",
            "expected": True
        },
        {
            "name": "缺少环境信息",
            "text": "web连接器，版本v2，日志文件",
            "expected": False
        },
        {
            "name": "缺少通道类型",
            "text": "飞船环境，版本1.0.0，trace日志",
            "expected": False
        },
        {
            "name": "缺少版本号（非云环境）",
            "text": "星舰环境，乐企通道，error日志",
            "expected": False
        },
        {
            "name": "缺少日志信息",
            "text": "飞船环境，rpa通道，版本2.1.0",
            "expected": False
        },
        {
            "name": "英文关键词测试",
            "text": "starship environment, web connector, version 2.3.1, error log",
            "expected": True
        }
    ]
    
    all_passed = True
    for test_case in test_cases:
        result = check_required_fields(test_case["text"])
        passed = result["is_valid"] == test_case["expected"]
        
        status = "✅ 通过" if passed else "❌ 失败"
        print(f"{status} {test_case['name']}")
        
        if not passed:
            print(f"  输入: {test_case['text']}")
            print(f"  预期: {test_case['expected']}, 实际: {result['is_valid']}")
            print(f"  缺失字段: {result['missing_fields']}")
            all_passed = False
        
        print()
    
    return all_passed

def test_match_score():
    """测试匹配分数计算功能"""
    print("🧪 测试匹配分数计算功能")
    print("-" * 60)
    
    test_cases = [
        {
            "name": "完全匹配",
            "text": "认证勾选授权",
            "keywords": ["认证", "勾选", "授权"],
            "expected_min": 0.8
        },
        {
            "name": "部分匹配",
            "text": "认证相关工单",
            "keywords": ["认证", "勾选", "授权"],
            "expected_min": 0.3
        },
        {
            "name": "无匹配",
            "text": "普通工单",
            "keywords": ["认证", "勾选", "授权"],
            "expected_max": 0.2
        },
        {
            "name": "乐企匹配",
            "text": "乐企接口调用失败",
            "keywords": ["乐企", "leqi", "LEQI"],
            "expected_min": 0.8
        }
    ]
    
    all_passed = True
    for test_case in test_cases:
        score = calculate_match_score(test_case["text"], test_case["keywords"])
        
        if "expected_min" in test_case:
            passed = score >= test_case["expected_min"]
            condition = f">= {test_case['expected_min']}"
        else:
            passed = score <= test_case["expected_max"]
            condition = f"<= {test_case['expected_max']}"
        
        status = "✅ 通过" if passed else "❌ 失败"
        print(f"{status} {test_case['name']}")
        print(f"  分数: {score:.2f} {condition}")
        
        if not passed:
            all_passed = False
        
        print()
    
    return all_passed

def test_rule_matching():
    """测试规则匹配功能"""
    print("🧪 测试规则匹配功能")
    print("-" * 60)
    
    # 测试规则
    rules = [
        {
            "rule_name": "认证相关工单",
            "keywords": ["认证", "勾选", "授权", "权限", "token", "登录", "Auth"],
            "assignee": "张献文",
            "jira_username": "zhangxianwen",
            "reply_message": "请献文协助处理此工单"
        },
        {
            "rule_name": "乐企相关工单",
            "keywords": ["乐企", "leqi", "LEQI"],
            "assignee": "付强",
            "jira_username": "fuqiang",
            "reply_message": "请付强协助处理此工单"
        },
        {
            "rule_name": "综服通道相关工单",
            "keywords": ["综服", "通道", "银行", "工行", "中行", "农行", "建行", "招行"],
            "assignee": "魏旭峰",
            "jira_username": "weixufeng",
            "reply_message": "请旭峰协助处理此工单"
        },
        {
            "rule_name": "其他工单",
            "keywords": [],
            "assignee": "刘巍",
            "jira_username": "liuwei1",
            "reply_message": "收到，我会及时处理，请稍后"
        }
    ]
    
    test_cases = [
        {
            "name": "认证工单匹配",
            "text": "认证失败需要处理",
            "expected_rule": "认证相关工单"
        },
        {
            "name": "乐企工单匹配",
            "text": "乐企接口调用异常",
            "expected_rule": "乐企相关工单"
        },
        {
            "name": "综服工单匹配",
            "text": "工行综服通道问题",
            "expected_rule": "综服通道相关工单"
        },
        {
            "name": "其他工单匹配",
            "text": "普通性能优化需求",
            "expected_rule": "其他工单"
        }
    ]
    
    all_passed = True
    for test_case in test_cases:
        matched_rule = match_rule(test_case["text"], rules)
        
        if matched_rule:
            rule_name = matched_rule.get("rule_name", "未知规则")
            passed = rule_name == test_case["expected_rule"]
            status = "✅ 通过" if passed else "❌ 失败"
            
            print(f"{status} {test_case['name']}")
            print(f"  匹配规则: {rule_name}")
            print(f"  负责人: {matched_rule.get('assignee')}")
            print(f"  回复: {matched_rule.get('reply_message')}")
            
            if not passed:
                print(f"  预期规则: {test_case['expected_rule']}")
                all_passed = False
        else:
            print(f"❌ 失败 {test_case['name']}")
            print(f"  未匹配到任何规则")
            all_passed = False
        
        print()
    
    return all_passed

def test_issue_format():
    """测试工单格式化功能"""
    print("🧪 测试工单格式化功能")
    print("-" * 60)
    
    test_issue = {
        "issue_key": "XS-12345",
        "status": "新建",
        "summary": "【工行】综服通道认证失败，版本v1.2.3，错误日志见附件",
        "is_valid": True,
        "missing_fields": [],
        "rule_matched": "综服通道相关工单",
        "suggested_assignee": "魏旭峰",
        "suggested_reply": "请旭峰协助处理此工单"
    }
    
    formatted = format_issue_display(test_issue)
    print("格式化输出:")
    print("-" * 40)
    print(formatted)
    print("-" * 40)
    
    # 检查关键信息是否包含
    required_info = [
        "XS-12345",
        "新建",
        "综服通道认证失败",
        "✅ 通过",
        "综服通道相关工单",
        "魏旭峰"
    ]
    
    all_found = True
    for info in required_info:
        if info in formatted:
            print(f"✅ 包含: {info}")
        else:
            print(f"❌ 缺失: {info}")
            all_found = False
    
    print()
    return all_found

def main():
    """主测试函数"""
    print("🚀 JIRA自动分析功能测试套件")
    print("=" * 60)
    
    test_results = []
    
    # 运行所有测试
    test_results.append(("必填信息检查", test_required_fields()))
    test_results.append(("匹配分数计算", test_match_score()))
    test_results.append(("规则匹配", test_rule_matching()))
    test_results.append(("工单格式化", test_issue_format()))
    
    # 显示测试总结
    print("📊 测试总结")
    print("=" * 60)
    
    total_tests = len(test_results)
    passed_tests = sum(1 for _, passed in test_results if passed)
    
    for test_name, passed in test_results:
        status = "✅ 通过" if passed else "❌ 失败"
        print(f"{status} {test_name}")
    
    print("-" * 60)
    print(f"总计: {passed_tests}/{total_tests} 项测试通过")
    
    if passed_tests == total_tests:
        print("🎉 所有测试通过！")
        return 0
    else:
        print("⚠️  部分测试失败，请检查相关问题")
        return 1

if __name__ == "__main__":
    sys.exit(main())

path: scripts/simple_test.py
content: #!/usr/bin/env python3
"""
简化的JIRA自动分析功能测试
"""

import re

def check_required_fields_simple(text):
    """简化版必填信息检查"""
    text_lower = text.lower()
    
    # 检查环境信息
    env_keywords = ['星舰', '飞船', '云']
    env_found = any(keyword in text_lower for keyword in env_keywords)
    
    # 检查通道类型
    channel_keywords = ['web连接器', 'rpa', '乐企']
    channel_found = any(keyword in text_lower for keyword in channel_keywords)
    
    # 检查项目版本号（云工单除外）
    if '云' in text_lower:
        version_found = True  # 云工单不需要版本号
    else:
        version_pattern = r'(\d+\.\d+\.\d+|v\d+|版本\d+)'
        version_found = bool(re.search(version_pattern, text_lower))
    
    # 检查相关日志
    log_keywords = ['日志', 'log', 'trace', 'error', 'stack']
    log_found = any(keyword in text_lower for keyword in log_keywords)
    
    missing_fields = []
    if not env_found:
        missing_fields.append('环境')
    if not channel_found:
        missing_fields.append('通道类型')
    if not version_found:
        missing_fields.append('项目版本号')
    if not log_found:
        missing_fields.append('相关日志')
    
    return {
        'is_valid': len(missing_fields) == 0,
        'missing_fields': missing_fields
    }

def test_simple():
    """简化测试"""
    print("🧪 简化测试 - 必填信息检查功能")
    print("-" * 60)
    
    test_cases = [
        {
            "name": "完整信息测试",
            "text": "星舰环境，web连接器，版本1.2.3，错误日志",
            "expected_valid": True
        },
        {
            "name": "云环境测试（无版本号）",
            "text": "云环境，rpa通道，错误日志",
            "expected_valid": True
        },
        {
            "name": "缺少环境信息",
            "text": "web连接器，版本v2，日志文件",
            "expected_valid": False
        },
        {
            "name": "缺少通道类型",
            "text": "飞船环境，版本1.0.0，trace日志",
            "expected_valid": False
        },
    ]
    
    all_passed = True
    for test_case in test_cases:
        result = check_required_fields_simple(test_case["text"])
        passed = result["is_valid"] == test_case["expected_valid"]
        
        status = "✅ 通过" if passed else "❌ 失败"
        print(f"{status} {test_case['name']}")
        print(f"  输入: {test_case['text'][:50]}...")
        print(f"  预期: {'有效' if test_case['expected_valid'] else '无效'}, 实际: {'有效' if result['is_valid'] else '无效'}")
        
        if not result["is_valid"]:
            print(f"  缺失字段: {', '.join(result['missing_fields'])}")
        
        if not passed:
            all_passed = False
        
        print()
    
    print("-" * 60)
    if all_passed:
        print("🎉 所有简化测试通过！")
    else:
        print("⚠️  部分测试失败")
    
    return all_passed

if __name__ == "__main__":
    test_simple()

path: scripts/demo.py
content: #!/usr/bin/env python3
"""
JIRA自动分析技能演示脚本
展示如何使用技能功能而不实际连接JIRA
"""

import json
import os
import sys

# 添加当前目录到路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 导入工具函数
from utils import check_required_fields, match_rule

def get_config():
    """读取配置文件"""
    config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config", "config.json")
    
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"❌ 读取配置文件失败: {e}")
        return {}

def demo_required_fields_check():
    """演示必填信息检查功能"""
    print("🔍 演示：必填信息检查功能")
    print("-" * 60)
    
    test_cases = [
        "星舰环境，web连接器，版本1.2.3，错误日志",
        "云环境，rpa通道，错误日志",
        "飞船环境，乐企通道，版本v2.1.0，trace日志",
        "web连接器，版本1.0.0，日志文件"  # 缺少环境信息
    ]
    
    for i, text in enumerate(test_cases, 1):
        result = check_required_fields(text)
        status = "✅ 通过" if result["is_valid"] else "❌ 未通过"
        
        print(f"测试 {i}: {status}")
        print(f"  工单内容: {text[:60]}...")
        
        if result["is_valid"]:
            print(f"  结果: 工单信息完整")
        else:
            print(f"  结果: 缺少以下信息: {', '.join(result['missing_fields'])}")
            print(f"  建议回复: 请提供相关环境、通道类型、版本号及日志信息")
        
        print()

def demo_rule_matching():
    """演示规则匹配功能"""
    print("🎯 演示：规则匹配与自动分配")
    print("-" * 60)
    
    config = get_config()
    rules = config.get("rules", [])
    
    test_cases = [
        {
            "name": "认证相关工单",
            "text": "用户认证失败，无法登录系统"
        },
        {
            "name": "乐企相关工单",
            "text": "乐企接口调用超时错误"
        },
        {
            "name": "综服通道工单",
            "text": "工行综服通道连接异常"
        },
        {
            "name": "其他工单",
            "text": "系统性能优化建议"
        }
    ]
    
    for test_case in test_cases:
        matched_rule = match_rule(test_case["text"], rules)
        
        if matched_rule:
            rule_name = matched_rule.get("rule_name", "未知规则")
            assignee = matched_rule.get("assignee", "未知负责人")
            reply = matched_rule.get("reply_message", "无回复内容")
            
            print(f"📋 {test_case['name']}")
            print(f"  工单内容: {test_case['text']}")
            print(f"  匹配规则: {rule_name}")
            print(f"  自动指派给: {assignee}")
            print(f"  自动回复: {reply}")
        else:
            print(f"❌ {test_case['name']}: 未匹配到任何规则")
        
        print()

def demo_workflow():
    """演示完整工作流程"""
    print("🔄 演示：完整工单处理流程")
    print("=" * 60)
    
    test_issues = [
        {
            "id": 1,
            "summary": "【认证】用户登录权限验证失败，版本1.3.2，错误日志见附件",
            "description": "星舰环境，web连接器，版本1.3.2，认证失败日志显示权限不足",
            "expected": "认证相关工单 -> 张献文"
        },
        {
            "id": 2,
            "summary": "乐企接口调用超时，版本v2.0.1",
            "description": "飞船环境，乐企通道，版本v2.0.1，调用超时日志",
            "expected": "乐企相关工单 -> 付强"
        },
        {
            "id": 3,
            "summary": "工行综服通道无法连接",
            "description": "云环境，web连接器，连接失败日志",
            "expected": "综服通道相关工单 -> 魏旭峰"
        },
        {
            "id": 4,
            "summary": "系统性能优化建议",
            "description": "星舰环境，rpa通道，版本1.5.0，性能日志",
            "expected": "其他工单 -> 刘巍"
        },
        {
            "id": 5,
            "summary": "系统异常，需要紧急处理",
            "description": "异常日志显示系统崩溃",  # 缺少环境信息和版本号
            "expected": "信息不完整 -> 打回给联系人"
        }
    ]
    
    config = get_config()
    rules = config.get("rules", [])
    
    for issue in test_issues:
        print(f"📄 处理工单 #{issue['id']}: {issue['summary'][:40]}...")
        
        # 1. 检查必填信息
        check_result = check_required_fields(issue["description"])
        
        if not check_result["is_valid"]:
            print(f"   ❌ 信息不完整: 缺少 {', '.join(check_result['missing_fields'])}")
            print(f"   📤 操作: 打回给联系人")
            print(f"   💬 回复: {config.get('config', {}).get('rejection_message', '请补充信息')}")
        else:
            print(f"   ✅ 信息完整: 包含所有必填项")
            
            # 2. 匹配分配规则
            matched_rule = match_rule(issue["description"], rules)
            
            if matched_rule:
                rule_name = matched_rule.get("rule_name", "其他工单")
                assignee = matched_rule.get("assignee", "刘巍")
                reply = matched_rule.get("reply_message", "收到，我会及时处理，请稍后")
                
                print(f"   🎯 匹配规则: {rule_name}")
                print(f"   👤 自动指派给: {assignee}")
                print(f"   💬 自动回复: {reply}")
            else:
                print(f"   ⚠️  未匹配到具体规则，使用默认规则")
                print(f"   👤 自动指派给: 刘巍")
                print(f"   💬 自动回复: 收到，我会及时处理，请稍后")
        
        print()

def show_configuration():
    """显示技能配置"""
    print("⚙️  技能配置信息")
    print("-" * 60)
    
    config = get_config()
    
    if "config" in config:
        jira_config = config["config"]
        print(f"🔗 JIRA地址: {jira_config.get('jira_url')}")
        print(f"👤 登录用户: {jira_config.get('username')}")
        print(f"🔑 密码长度: {len(jira_config.get('password', ''))} 字符")
        print(f"📊 Filter ID: {jira_config.get('filter_id')}")
        print(f"📝 打回消息: {jira_config.get('rejection_message')}")
        print(f"🔄 仅检查新工单: {jira_config.get('check_new_only', False)}")
    
    print(f"\n📋 分配规则数量: {len(config.get('rules', []))}")
    for rule in config.get("rules", []):
        print(f"  • {rule.get('rule_name')}: {len(rule.get('keywords', []))}个关键词 -> {rule.get('assignee')}")

def main():
    """主函数"""
    print("🚀 JIRA自动分析技能演示")
    print("=" * 60)
    
    # 显示配置
    show_configuration()
    print()
    
    # 演示功能
    demo_required_fields_check()
    demo_rule_matching()
    demo_workflow()
    
    print("=" * 60)
    print("🎉 演示完成！")
    print("\n使用说明:")
    print("1. 确保已安装Playwright: python3 -m playwright install chromium")
    print("2. 运行主脚本: python3 scripts/jira_auto_analyze.py")
    print("3. 脚本将自动登录JIRA，分析工单，并处理符合条件的工单")
    print("4. 使用 --dry-run 参数进行测试模式，不实际执行操作")

if __name__ == "__main__":
    main()

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)