mergescope 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mergescope-0.1.0/.github/workflows/publish.yml +31 -0
- mergescope-0.1.0/.gitignore +20 -0
- mergescope-0.1.0/LICENSE +21 -0
- mergescope-0.1.0/PKG-INFO +161 -0
- mergescope-0.1.0/README.md +132 -0
- mergescope-0.1.0/mergescope.yaml +81 -0
- mergescope-0.1.0/pyproject.toml +49 -0
- mergescope-0.1.0/src/mergescope/__init__.py +1 -0
- mergescope-0.1.0/src/mergescope/__main__.py +3 -0
- mergescope-0.1.0/src/mergescope/agent.py +166 -0
- mergescope-0.1.0/src/mergescope/cli.py +139 -0
- mergescope-0.1.0/src/mergescope/config.py +111 -0
- mergescope-0.1.0/src/mergescope/jira_ids.py +21 -0
- mergescope-0.1.0/src/mergescope/mcp_client.py +113 -0
- mergescope-0.1.0/src/mergescope/report.py +81 -0
- mergescope-0.1.0/tests/test_agent.py +56 -0
- mergescope-0.1.0/tests/test_config.py +54 -0
- mergescope-0.1.0/tests/test_jira_ids.py +39 -0
- mergescope-0.1.0/tests/test_mcp_client.py +88 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
20
|
+
- run: uv sync --dev
|
|
21
|
+
- run: uv run pytest tests/ -v
|
|
22
|
+
|
|
23
|
+
publish:
|
|
24
|
+
needs: test
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: astral-sh/setup-uv@v6
|
|
30
|
+
- run: uv build
|
|
31
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
mergescope-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Caminoa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mergescope
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audit merged PRs against Jira tickets using a LangGraph agent and MCP tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/martin5211/MergeScope
|
|
6
|
+
Project-URL: Repository, https://github.com/martin5211/MergeScope
|
|
7
|
+
Project-URL: Issues, https://github.com/martin5211/MergeScope/issues
|
|
8
|
+
Author: Martin Caminoa
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: langchain-anthropic<2.0,>=0.3
|
|
22
|
+
Requires-Dist: langchain-aws<2.0,>=0.2
|
|
23
|
+
Requires-Dist: langchain-mcp-adapters<2.0,>=0.1
|
|
24
|
+
Requires-Dist: langchain-openai<2.0,>=0.3
|
|
25
|
+
Requires-Dist: langgraph<2.0,>=0.4
|
|
26
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
27
|
+
Requires-Dist: rich<15.0,>=13.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# MergeScope
|
|
31
|
+
|
|
32
|
+
Audits merged PRs against Jira tickets. Extracts Jira IDs from PR titles, descriptions, branch names, and commit messages, then validates each ticket's fixVersion against your expected release.
|
|
33
|
+
|
|
34
|
+
Powered by a LangGraph agent that talks to GitHub and Jira through MCP servers. Works standalone or with Amazon Q.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uvx mergescope --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or permanently:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv tool install mergescope
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mergescope \
|
|
52
|
+
-f 2026-03-01 \
|
|
53
|
+
-t 2026-03-31 \
|
|
54
|
+
-v "1.2.0" \
|
|
55
|
+
-r company/backend
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Flag | Description |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `-f`, `--from-date` | Start date (YYYY-MM-DD) |
|
|
61
|
+
| `-t`, `--to-date` | End date (YYYY-MM-DD) |
|
|
62
|
+
| `-v`, `--fix-version` | Expected Jira fixVersion |
|
|
63
|
+
| `-r`, `--repo` | GitHub repo (owner/repo) |
|
|
64
|
+
| `-c`, `--config` | Config file (default: `mergescope.yaml`) |
|
|
65
|
+
| `--mcp-config` | Amazon Q MCP config path |
|
|
66
|
+
| `--llm-provider` | `anthropic`, `bedrock`, `copilot`, or `local` |
|
|
67
|
+
| `--llm-model` | Model ID |
|
|
68
|
+
| `--llm-base-url` | API base URL (copilot/local) |
|
|
69
|
+
| `--language` | Response language (e.g. Spanish) |
|
|
70
|
+
| `--verbose` | Debug logging |
|
|
71
|
+
|
|
72
|
+
### Multilingual prompts
|
|
73
|
+
|
|
74
|
+
Set a custom prompt in any language via `mergescope.yaml`:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
language: "es"
|
|
78
|
+
response_language: "Spanish"
|
|
79
|
+
prompt_template: |
|
|
80
|
+
Necesito auditar tickets mergeados entre {from_date} y {to_date}.
|
|
81
|
+
La fixVersion esperada es {fix_version}.
|
|
82
|
+
Extrae los IDs de JIRA desde los merge commits del repositorio {repo}
|
|
83
|
+
y valida su fixVersion.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Add a `mergescope.yaml` in your project root:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
repo: "nice/java-project"
|
|
92
|
+
jira_project_prefix: "PROJ"
|
|
93
|
+
jira_base_url: "https://company.jira.com"
|
|
94
|
+
|
|
95
|
+
llm:
|
|
96
|
+
provider: "anthropic" # anthropic, bedrock, copilot, local
|
|
97
|
+
model: "claude-sonnet-4-6-20250620"
|
|
98
|
+
|
|
99
|
+
mcp:
|
|
100
|
+
servers:
|
|
101
|
+
github:
|
|
102
|
+
name: "github-mcp-server"
|
|
103
|
+
jira:
|
|
104
|
+
name: "atlassian-mcp-server"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### LLM providers
|
|
108
|
+
|
|
109
|
+
| Provider | Model example | Notes |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `anthropic` | `claude-sonnet-4-6-20250620` | Uses `ANTHROPIC_API_KEY` |
|
|
112
|
+
| `bedrock` | `us.anthropic.claude-sonnet-4-20250514-v1:0` | Uses AWS credentials |
|
|
113
|
+
| `copilot` | `claude-sonnet-4` | Set `base_url` to Copilot endpoint |
|
|
114
|
+
| `local` | `qwen3-8b` | LM Studio, Ollama, or any OpenAI-compatible server |
|
|
115
|
+
|
|
116
|
+
### MCP servers
|
|
117
|
+
|
|
118
|
+
Two modes — pick what fits your setup:
|
|
119
|
+
|
|
120
|
+
**With Amazon Q** (default): just set server names. MergeScope finds them in `~/.aws/amazonq/mcp.json` or `.amazonq/mcp.json`.
|
|
121
|
+
|
|
122
|
+
**Standalone** (no Amazon Q): define `command`, `args`, and `env` directly:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
mcp:
|
|
126
|
+
servers:
|
|
127
|
+
github:
|
|
128
|
+
name: "github-mcp-server"
|
|
129
|
+
command: "npx"
|
|
130
|
+
args: ["-y", "@anthropic/github-mcp-server"]
|
|
131
|
+
env:
|
|
132
|
+
GITHUB_TOKEN: "ghp_..."
|
|
133
|
+
jira:
|
|
134
|
+
name: "atlassian-mcp-server"
|
|
135
|
+
command: "npx"
|
|
136
|
+
args: ["-y", "@anthropic/atlassian-mcp-server"]
|
|
137
|
+
env:
|
|
138
|
+
JIRA_API_TOKEN: "..."
|
|
139
|
+
JIRA_URL: "https://company.jira.com"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
You can mix both — some servers inline, others from Amazon Q.
|
|
143
|
+
|
|
144
|
+
### Environment variables
|
|
145
|
+
|
|
146
|
+
`MERGESCOPE_REPO`, `MERGESCOPE_LLM_PROVIDER`, `MERGESCOPE_LLM_MODEL`, `MERGESCOPE_LLM_BASE_URL`, `MERGESCOPE_LLM_API_KEY`, `MERGESCOPE_JIRA_BASE_URL`, `MERGESCOPE_LANGUAGE`
|
|
147
|
+
|
|
148
|
+
## Requirements
|
|
149
|
+
|
|
150
|
+
- Python 3.11+
|
|
151
|
+
- GitHub and Jira MCP servers (standalone or via Amazon Q)
|
|
152
|
+
- An LLM: Anthropic API key, AWS credentials, GitHub Copilot, or a local server
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
git clone https://github.com/martin5211/MergeScope.git
|
|
158
|
+
cd MergeScope
|
|
159
|
+
uv venv && uv pip install -e .
|
|
160
|
+
pytest
|
|
161
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# MergeScope
|
|
2
|
+
|
|
3
|
+
Audits merged PRs against Jira tickets. Extracts Jira IDs from PR titles, descriptions, branch names, and commit messages, then validates each ticket's fixVersion against your expected release.
|
|
4
|
+
|
|
5
|
+
Powered by a LangGraph agent that talks to GitHub and Jira through MCP servers. Works standalone or with Amazon Q.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uvx mergescope --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or permanently:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv tool install mergescope
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
mergescope \
|
|
23
|
+
-f 2026-03-01 \
|
|
24
|
+
-t 2026-03-31 \
|
|
25
|
+
-v "1.2.0" \
|
|
26
|
+
-r company/backend
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Flag | Description |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `-f`, `--from-date` | Start date (YYYY-MM-DD) |
|
|
32
|
+
| `-t`, `--to-date` | End date (YYYY-MM-DD) |
|
|
33
|
+
| `-v`, `--fix-version` | Expected Jira fixVersion |
|
|
34
|
+
| `-r`, `--repo` | GitHub repo (owner/repo) |
|
|
35
|
+
| `-c`, `--config` | Config file (default: `mergescope.yaml`) |
|
|
36
|
+
| `--mcp-config` | Amazon Q MCP config path |
|
|
37
|
+
| `--llm-provider` | `anthropic`, `bedrock`, `copilot`, or `local` |
|
|
38
|
+
| `--llm-model` | Model ID |
|
|
39
|
+
| `--llm-base-url` | API base URL (copilot/local) |
|
|
40
|
+
| `--language` | Response language (e.g. Spanish) |
|
|
41
|
+
| `--verbose` | Debug logging |
|
|
42
|
+
|
|
43
|
+
### Multilingual prompts
|
|
44
|
+
|
|
45
|
+
Set a custom prompt in any language via `mergescope.yaml`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
language: "es"
|
|
49
|
+
response_language: "Spanish"
|
|
50
|
+
prompt_template: |
|
|
51
|
+
Necesito auditar tickets mergeados entre {from_date} y {to_date}.
|
|
52
|
+
La fixVersion esperada es {fix_version}.
|
|
53
|
+
Extrae los IDs de JIRA desde los merge commits del repositorio {repo}
|
|
54
|
+
y valida su fixVersion.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Add a `mergescope.yaml` in your project root:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
repo: "nice/java-project"
|
|
63
|
+
jira_project_prefix: "PROJ"
|
|
64
|
+
jira_base_url: "https://company.jira.com"
|
|
65
|
+
|
|
66
|
+
llm:
|
|
67
|
+
provider: "anthropic" # anthropic, bedrock, copilot, local
|
|
68
|
+
model: "claude-sonnet-4-6-20250620"
|
|
69
|
+
|
|
70
|
+
mcp:
|
|
71
|
+
servers:
|
|
72
|
+
github:
|
|
73
|
+
name: "github-mcp-server"
|
|
74
|
+
jira:
|
|
75
|
+
name: "atlassian-mcp-server"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### LLM providers
|
|
79
|
+
|
|
80
|
+
| Provider | Model example | Notes |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `anthropic` | `claude-sonnet-4-6-20250620` | Uses `ANTHROPIC_API_KEY` |
|
|
83
|
+
| `bedrock` | `us.anthropic.claude-sonnet-4-20250514-v1:0` | Uses AWS credentials |
|
|
84
|
+
| `copilot` | `claude-sonnet-4` | Set `base_url` to Copilot endpoint |
|
|
85
|
+
| `local` | `qwen3-8b` | LM Studio, Ollama, or any OpenAI-compatible server |
|
|
86
|
+
|
|
87
|
+
### MCP servers
|
|
88
|
+
|
|
89
|
+
Two modes — pick what fits your setup:
|
|
90
|
+
|
|
91
|
+
**With Amazon Q** (default): just set server names. MergeScope finds them in `~/.aws/amazonq/mcp.json` or `.amazonq/mcp.json`.
|
|
92
|
+
|
|
93
|
+
**Standalone** (no Amazon Q): define `command`, `args`, and `env` directly:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
mcp:
|
|
97
|
+
servers:
|
|
98
|
+
github:
|
|
99
|
+
name: "github-mcp-server"
|
|
100
|
+
command: "npx"
|
|
101
|
+
args: ["-y", "@anthropic/github-mcp-server"]
|
|
102
|
+
env:
|
|
103
|
+
GITHUB_TOKEN: "ghp_..."
|
|
104
|
+
jira:
|
|
105
|
+
name: "atlassian-mcp-server"
|
|
106
|
+
command: "npx"
|
|
107
|
+
args: ["-y", "@anthropic/atlassian-mcp-server"]
|
|
108
|
+
env:
|
|
109
|
+
JIRA_API_TOKEN: "..."
|
|
110
|
+
JIRA_URL: "https://company.jira.com"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
You can mix both — some servers inline, others from Amazon Q.
|
|
114
|
+
|
|
115
|
+
### Environment variables
|
|
116
|
+
|
|
117
|
+
`MERGESCOPE_REPO`, `MERGESCOPE_LLM_PROVIDER`, `MERGESCOPE_LLM_MODEL`, `MERGESCOPE_LLM_BASE_URL`, `MERGESCOPE_LLM_API_KEY`, `MERGESCOPE_JIRA_BASE_URL`, `MERGESCOPE_LANGUAGE`
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
- Python 3.11+
|
|
122
|
+
- GitHub and Jira MCP servers (standalone or via Amazon Q)
|
|
123
|
+
- An LLM: Anthropic API key, AWS credentials, GitHub Copilot, or a local server
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
git clone https://github.com/martin5211/MergeScope.git
|
|
129
|
+
cd MergeScope
|
|
130
|
+
uv venv && uv pip install -e .
|
|
131
|
+
pytest
|
|
132
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# MergeScope configuration
|
|
2
|
+
# Copy this file to your project root and adjust values.
|
|
3
|
+
|
|
4
|
+
# GitHub repository (owner/repo)
|
|
5
|
+
repo: ""
|
|
6
|
+
|
|
7
|
+
# Jira settings
|
|
8
|
+
jira_project_prefix: "" # e.g. "PROJ" — leave empty to match all projects
|
|
9
|
+
jira_base_url: "" # e.g. "https://company.jira.com"
|
|
10
|
+
|
|
11
|
+
# Language for the audit prompt and report
|
|
12
|
+
language: "en"
|
|
13
|
+
response_language: "English"
|
|
14
|
+
|
|
15
|
+
# Custom prompt template (supports {repo}, {from_date}, {to_date}, {fix_version})
|
|
16
|
+
# prompt_template: |
|
|
17
|
+
# Necesito auditar tickets mergeados entre {from_date} y {to_date}.
|
|
18
|
+
# La fixVersion esperada es {fix_version}.
|
|
19
|
+
# Extrae los IDs de JIRA desde los merge commits del repositorio {repo}
|
|
20
|
+
# y valida su fixVersion.
|
|
21
|
+
|
|
22
|
+
# LLM provider and model
|
|
23
|
+
# Supported providers: anthropic, bedrock, copilot, local
|
|
24
|
+
llm:
|
|
25
|
+
provider: "anthropic"
|
|
26
|
+
model: "claude-sonnet-4-6-20250620"
|
|
27
|
+
# base_url: "" # override API endpoint (required for copilot/local)
|
|
28
|
+
# api_key: "" # override API key (or use env vars below)
|
|
29
|
+
|
|
30
|
+
# Provider examples:
|
|
31
|
+
#
|
|
32
|
+
# Anthropic (default):
|
|
33
|
+
# provider: "anthropic"
|
|
34
|
+
# model: "claude-sonnet-4-6-20250620"
|
|
35
|
+
# # uses ANTHROPIC_API_KEY env var
|
|
36
|
+
#
|
|
37
|
+
# AWS Bedrock:
|
|
38
|
+
# provider: "bedrock"
|
|
39
|
+
# model: "us.anthropic.claude-sonnet-4-20250514-v1:0"
|
|
40
|
+
# # uses AWS credentials
|
|
41
|
+
#
|
|
42
|
+
# GitHub Copilot:
|
|
43
|
+
# provider: "copilot"
|
|
44
|
+
# model: "claude-sonnet-4"
|
|
45
|
+
# base_url: "https://api.githubcopilot.com"
|
|
46
|
+
# # uses GITHUB_TOKEN or MERGESCOPE_LLM_API_KEY env var
|
|
47
|
+
#
|
|
48
|
+
# Local LLM (LM Studio, Ollama, etc.):
|
|
49
|
+
# provider: "local"
|
|
50
|
+
# model: "qwen3-9b"
|
|
51
|
+
# base_url: "http://localhost:1234/v1"
|
|
52
|
+
|
|
53
|
+
# MCP server configuration
|
|
54
|
+
# Two modes: inline (standalone) or Amazon Q lookup (name-only).
|
|
55
|
+
# When "command" is set, the server runs directly — no Amazon Q needed.
|
|
56
|
+
# When only "name" is set, it looks up that server in Amazon Q config.
|
|
57
|
+
mcp:
|
|
58
|
+
config_path: "" # auto-discovers Amazon Q config if empty
|
|
59
|
+
servers:
|
|
60
|
+
github:
|
|
61
|
+
name: "github-mcp-server"
|
|
62
|
+
jira:
|
|
63
|
+
name: "atlassian-mcp-server"
|
|
64
|
+
|
|
65
|
+
# Standalone example (no Amazon Q required):
|
|
66
|
+
#
|
|
67
|
+
# mcp:
|
|
68
|
+
# servers:
|
|
69
|
+
# github:
|
|
70
|
+
# name: "github-mcp-server"
|
|
71
|
+
# command: "npx"
|
|
72
|
+
# args: ["-y", "@anthropic/github-mcp-server"]
|
|
73
|
+
# env:
|
|
74
|
+
# GITHUB_TOKEN: "ghp_..."
|
|
75
|
+
# jira:
|
|
76
|
+
# name: "atlassian-mcp-server"
|
|
77
|
+
# command: "npx"
|
|
78
|
+
# args: ["-y", "@anthropic/atlassian-mcp-server"]
|
|
79
|
+
# env:
|
|
80
|
+
# JIRA_API_TOKEN: "..."
|
|
81
|
+
# JIRA_URL: "https://company.atlassian.net"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mergescope"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Audit merged PRs against Jira tickets using a LangGraph agent and MCP tools"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "Martin Caminoa" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"langgraph>=0.4,<2.0",
|
|
26
|
+
"langchain-mcp-adapters>=0.1,<2.0",
|
|
27
|
+
"langchain-anthropic>=0.3,<2.0",
|
|
28
|
+
"langchain-aws>=0.2,<2.0",
|
|
29
|
+
"langchain-openai>=0.3,<2.0",
|
|
30
|
+
"pyyaml>=6.0,<7.0",
|
|
31
|
+
"rich>=13.0,<15.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
mergescope = "mergescope.cli:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/martin5211/MergeScope"
|
|
39
|
+
Repository = "https://github.com/martin5211/MergeScope"
|
|
40
|
+
Issues = "https://github.com/martin5211/MergeScope/issues"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = ["pytest>=9.0"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/mergescope"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
9
|
+
from langgraph.graph import MessagesState, StateGraph, START
|
|
10
|
+
from langgraph.prebuilt import ToolNode, tools_condition
|
|
11
|
+
|
|
12
|
+
from mergescope.config import MergeScopeConfig
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
SYSTEM_PROMPT = """\
|
|
17
|
+
You are MergeScope, an audit agent that cross-references GitHub merge activity with Jira tickets.
|
|
18
|
+
|
|
19
|
+
You have access to GitHub and Jira tools via MCP servers. Use the available tools to complete the audit.
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
1. **Fetch merge activity**: Use the GitHub tools to list merged pull requests (or merge commits) in the given repository and date range. Get enough detail: PR title, body/description, branch name, commit messages, and commit SHAs.
|
|
24
|
+
|
|
25
|
+
2. **Extract Jira IDs**: From EACH pull request, look for Jira ticket IDs in ALL of these places:
|
|
26
|
+
- PR title
|
|
27
|
+
- PR body / description (both short and long)
|
|
28
|
+
- Branch name (e.g. `feat/PROJ-123-something`, `PROJ-456`, `bugfix/proj-789`)
|
|
29
|
+
- Individual commit messages
|
|
30
|
+
Jira IDs follow the pattern: 2+ letters, a hyphen, then digits (e.g. PROJ-123, AB-1). They may appear in brackets [PROJ-123], lowercase, or mixed case. Normalize all to UPPERCASE and deduplicate.
|
|
31
|
+
|
|
32
|
+
3. **Validate in Jira**: For each unique Jira ID found, use the Jira/Atlassian tools to fetch:
|
|
33
|
+
- The ticket summary (title)
|
|
34
|
+
- The fixVersion field
|
|
35
|
+
Compare fixVersion against the expected version provided.
|
|
36
|
+
|
|
37
|
+
4. **Return structured JSON** with this exact schema:
|
|
38
|
+
```json
|
|
39
|
+
{{
|
|
40
|
+
"tickets": [
|
|
41
|
+
{{
|
|
42
|
+
"jira_id": "PROJ-123",
|
|
43
|
+
"title": "Ticket summary from Jira",
|
|
44
|
+
"fix_version": "actual fixVersion or null",
|
|
45
|
+
"expected_version": "the expected version",
|
|
46
|
+
"status": "MATCH | MISMATCH | NOT_FOUND | NO_FIX_VERSION"
|
|
47
|
+
}}
|
|
48
|
+
],
|
|
49
|
+
"unlinked_commits": [
|
|
50
|
+
{{
|
|
51
|
+
"sha": "full commit SHA",
|
|
52
|
+
"message": "first line of commit message",
|
|
53
|
+
"url": "https://github.com/owner/repo/commit/SHA"
|
|
54
|
+
}}
|
|
55
|
+
]
|
|
56
|
+
}}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Status rules:
|
|
60
|
+
- `MATCH`: fixVersion equals expected version
|
|
61
|
+
- `MISMATCH`: fixVersion exists but differs from expected
|
|
62
|
+
- `NOT_FOUND`: Jira ticket does not exist or lookup failed
|
|
63
|
+
- `NO_FIX_VERSION`: ticket exists but has no fixVersion set
|
|
64
|
+
|
|
65
|
+
Include in `unlinked_commits` every merge commit or squash commit that has NO Jira ID anywhere (title, body, branch, message).
|
|
66
|
+
|
|
67
|
+
{language_instruction}
|
|
68
|
+
|
|
69
|
+
Respond ONLY with the JSON object. No markdown fences, no explanation.\
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _build_system_prompt(config: MergeScopeConfig) -> str:
|
|
74
|
+
lang = config.response_language
|
|
75
|
+
if lang and lang.lower() != "english":
|
|
76
|
+
instruction = f"Write all ticket titles and the JSON string values in {lang} if the source is in that language, otherwise keep the original language."
|
|
77
|
+
else:
|
|
78
|
+
instruction = ""
|
|
79
|
+
return SYSTEM_PROMPT.format(language_instruction=instruction)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_PROVIDERS = ("anthropic", "bedrock", "copilot", "local")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _create_llm(config: MergeScopeConfig):
|
|
86
|
+
provider = config.llm.provider.lower()
|
|
87
|
+
model_id = config.llm.model
|
|
88
|
+
base_url = config.llm.base_url or None
|
|
89
|
+
api_key = config.llm.api_key or None
|
|
90
|
+
|
|
91
|
+
if provider == "anthropic":
|
|
92
|
+
from langchain_anthropic import ChatAnthropic
|
|
93
|
+
return ChatAnthropic(model=model_id)
|
|
94
|
+
|
|
95
|
+
if provider == "bedrock":
|
|
96
|
+
from langchain_aws import ChatBedrock
|
|
97
|
+
return ChatBedrock(model_id=model_id)
|
|
98
|
+
|
|
99
|
+
if provider == "copilot":
|
|
100
|
+
from langchain_openai import ChatOpenAI
|
|
101
|
+
return ChatOpenAI(
|
|
102
|
+
model=model_id,
|
|
103
|
+
base_url=base_url or "https://api.githubcopilot.com",
|
|
104
|
+
api_key=api_key or None,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if provider == "local":
|
|
108
|
+
from langchain_openai import ChatOpenAI
|
|
109
|
+
return ChatOpenAI(
|
|
110
|
+
model=model_id,
|
|
111
|
+
base_url=base_url or "http://localhost:1234/v1",
|
|
112
|
+
api_key=api_key or "lm-studio",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Unsupported LLM provider: '{provider}'. "
|
|
117
|
+
f"Choose from: {', '.join(_PROVIDERS)}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_agent(tools: list, config: MergeScopeConfig):
|
|
122
|
+
model = _create_llm(config)
|
|
123
|
+
model_with_tools = model.bind_tools(tools)
|
|
124
|
+
|
|
125
|
+
async def call_model(state: MessagesState) -> dict:
|
|
126
|
+
response = await model_with_tools.ainvoke(state["messages"])
|
|
127
|
+
return {"messages": [response]}
|
|
128
|
+
|
|
129
|
+
graph = StateGraph(MessagesState)
|
|
130
|
+
graph.add_node("call_model", call_model)
|
|
131
|
+
graph.add_node("tools", ToolNode(tools))
|
|
132
|
+
graph.add_edge(START, "call_model")
|
|
133
|
+
graph.add_conditional_edges("call_model", tools_condition)
|
|
134
|
+
graph.add_edge("tools", "call_model")
|
|
135
|
+
|
|
136
|
+
return graph.compile()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def build_prompt(config: MergeScopeConfig, repo: str, from_date: str, to_date: str, fix_version: str) -> list:
|
|
140
|
+
system = _build_system_prompt(config)
|
|
141
|
+
human = config.prompt_template.format(
|
|
142
|
+
repo=repo,
|
|
143
|
+
from_date=from_date,
|
|
144
|
+
to_date=to_date,
|
|
145
|
+
fix_version=fix_version,
|
|
146
|
+
)
|
|
147
|
+
return [
|
|
148
|
+
SystemMessage(content=system),
|
|
149
|
+
HumanMessage(content=human),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def parse_agent_output(content: str) -> dict[str, Any]:
|
|
154
|
+
text = content.strip()
|
|
155
|
+
fence_match = re.search(r"```(?:json)?\s*\n?(.*?)\n?\s*```", text, re.DOTALL)
|
|
156
|
+
if fence_match:
|
|
157
|
+
text = fence_match.group(1).strip()
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
return json.loads(text)
|
|
161
|
+
except json.JSONDecodeError as exc:
|
|
162
|
+
logger.error("Failed to parse agent output as JSON: %s", exc)
|
|
163
|
+
logger.debug("Raw output:\n%s", content)
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"Agent did not return valid JSON. Raw output logged at DEBUG level."
|
|
166
|
+
) from exc
|