Turn AI-summarised meeting notes into structured Jira Epics & Stories — in four steps.
JiraMaster is a Flask web application that bridges the gap between your AI assistant (GitHub Copilot, ChatGPT, etc.) and Jira Cloud. Paste meeting notes, generate a tuned prompt, feed it to your LLM, then import, review, and push the resulting Epics and Stories directly to Jira — no coding required.
- Overview
- Landing Page
- Architecture
- Data Flow
- Installation
- Configuration
- Usage — 4-Step Workflow
- Jira Tools
- Cache Manager
- Updating JiraMaster
- Advanced Topics
- Project Structure
- Troubleshooting
- License
JiraMaster solves a common pain point: after a meeting, you have Copilot or ChatGPT produce a structured YAML summary of decisions and action items, but getting that into Jira as properly-linked Epics and Stories is tedious. JiraMaster automates the last mile:
- Generate a tunable prompt tailored to your meeting notes and Copilot mode
- Import the YAML/JSON output your LLM produces
- Edit every field — titles, acceptance criteria, assignees, labels, priorities, due dates
- Upload directly to Jira Cloud via REST API v3 with real-time progress tracking
Every uploaded batch is automatically tagged with a unique run label (JiraMaster-XYZ-000001) so you can find and audit issues created in any session.
No database. No migrations. Works out of the box with ./scripts/start.sh.
| Layer | Components |
|---|---|
| Client | Browser — interacts with the Flask web UI |
| Application | Flask 3.x app (app.py) with 8 route blueprints: / (home), /prompt, /import, /edit, /upload, /settings, /tools, /cache |
| Core Modules | parser.py, prompt_builder.py, jira_client.py, config.py, models.py, work_store.py, logging_config.py |
| File Storage | .work/{uuid}.json per session; cache/ directory for assignees, labels, projects, initiatives; config.json — no database |
| External | Jira Cloud (REST API v3), Atlassian Teams API, OS Keyring (macOS Keychain / Windows Credential Manager), and your LLM of choice |
| Launch Scripts | scripts/start.sh (macOS/Linux) and scripts/start.ps1 + scripts/start.bat (Windows) — manage venv, deps, TLS cert merging |
- No database — each session is a UUID-keyed JSON file in
.work/. Zero migrations, trivially portable. - Include flags —
Epic.includeandStory.includebooleans let you exclude any item before upload. - ADF dual-mode — acceptance criteria are written in Atlassian Document Format (ADF); falls back to plain text if the field doesn't support ADF.
- TLS proxy support —
scripts/start.sh/scripts/start.ps1merge your system CA certificates into the certifi bundle automatically, so corporate TLS-inspection proxies work without admin rights. - Centralised logging — all modules use
logging.getLogger(__name__); a singlesetup_logging()call inapp.pyroutes everything to a rotating file log and console. - Secure credential storage — API tokens can be stored in the OS keyring (macOS Keychain, Windows Credential Manager) instead of plaintext
config.json. - Security hardening — all responses carry CSP, X-Frame-Options, and X-Content-Type-Options headers; session cookies are HttpOnly, SameSite=Lax, 8-hour lifetime; work-file access is validated against a session fingerprint.
- Run label tracking — every upload run auto-generates a
JiraMaster-BBB-XXXXXXlabel (where BBB = user initials, XXXXXX = incrementing counter) applied to all created issues, making it easy to find and audit batches.
Meeting Notes (optional)
│
▼
┌───────────────────┐
│ Step 1: Prompt │ ← Choose Copilot Mode, tune aggressiveness & detail
│ /prompt │ → Download .txt or copy to clipboard
└────────┬──────────┘
│ (manual: paste into Copilot / ChatGPT)
▼
LLM produces YAML
│
▼
┌───────────────────┐
│ Step 2: Import │ ← Paste YAML/JSON or upload file
│ /import │ → Select epics, set initiative IDs & project keys
└────────┬──────────┘
│ saved to .work/{uuid}.json
▼
┌───────────────────┐
│ Step 3: Edit │ ← Edit all fields: titles, AC, assignees, labels
│ /edit │ → Updated in .work/{uuid}.json
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Step 4: Upload │ ← Preview counts, then push with live SSE progress
│ /upload │ → Jira Cloud REST API v3
└───────────────────┘
│
▼
Jira Epics & Stories created
(auto-tagged with run label JiraMaster-BBB-XXXXXX)
- Python 3.9+
- A Jira Cloud instance with API access
- A Jira API token (see Configuration)
git clone https://github.com/your-username/JiraMaster.git
cd JiraMaster
./scripts/start.shscripts/start.sh will:
- Create a Python virtual environment (
venv/) - Install all dependencies from
requirements.txt(includingkeyring) - Merge system CA certificates into the certifi bundle (for TLS-inspection proxies)
- Start the app at http://127.0.0.1:5000
git clone https://github.com/your-username/JiraMaster.git
cd JiraMaster
scripts\start.batscripts/start.bat launches scripts/start.ps1 with -ExecutionPolicy Bypass, so it works on corporate machines without admin rights or policy changes. CA certs are merged from Cert:\CurrentUser\Root, Cert:\LocalMachine\Root, and Cert:\LocalMachine\CA.
Advanced: If you have already configured your own PowerShell execution policy, you can run
.\scripts\start.ps1directly instead.
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python app.py| Package | Purpose |
|---|---|
Flask >= 3.0 |
Web framework |
Flask-WTF >= 1.2 |
CSRF protection |
PyYAML >= 6.0.1 |
Parse LLM YAML output |
requests >= 2.31 |
Jira REST API calls |
certifi >= 2024 |
CA certificate bundle |
keyring >= 25.0 |
OS keyring for secure credential storage |
On first run, open http://127.0.0.1:5000/settings to enter your Jira credentials.
| Field | Description |
|---|---|
| Jira Base URL | Your Atlassian Cloud URL, e.g. https://yourcompany.atlassian.net |
| Email / Username | The email address of your Atlassian account |
| API Token | Generate at id.atlassian.com → Security → API tokens |
| Project Key | The short key for your Jira project (e.g. PROJ, DEV) |
| Acceptance Criteria Field ID | Custom field ID for AC (e.g. customfield_11401). Use Detect Fields to find it automatically. |
| HTTP Proxy URL | Optional. For corporate proxies: http://proxy.company.com:8080 |
Credentials are saved to config.json in the project directory (gitignored). If the OS keyring is available, the API token is stored there instead of in plaintext.
The Settings page shows a Security Status panel with:
- Credential storage mode —
keyring(secure) orconfig file(plaintext) - SSL configuration — whether a custom CA bundle or proxy is active
- API token state — whether a token is stored and where
- Go to https://id.atlassian.com → Security → API tokens
- Click Create API token
- Give it a name (e.g.
JiraMaster) and copy the token - Paste it into the API Token field in Settings
If your Jira project uses a custom AC field, click Detect Fields in Settings. JiraMaster will inspect your project's issue create metadata and find fields whose name contains "acceptance" or "criteria".
Navigate to http://127.0.0.1:5000 to reach the landing page. It provides quick access to all modules:
- Meeting to Jira — start the 4-step wizard
- Jira Tools — refresh cached project data
- Cache Manager — inspect and clear local caches
- Settings — configure your Jira connection
The navbar's home icon (JiraMaster) returns to this page from anywhere in the app.
Navigate to Meeting to Jira in the top nav (or http://127.0.0.1:5000/prompt).
Controls:
| Control | Options | Effect |
|---|---|---|
| Copilot Mode | Post-Recap / In-Meeting | Post-Recap: paste the full meeting recap after it ends. In-Meeting: use Copilot during the meeting for a live prompt. |
| Aggressiveness | Conservative / Standard / Aggressive | Filters how strictly Jira-worthy items must be. Conservative = only committed, multi-story work; Standard = clear action items; Aggressive = all discussion points and tentative tasks. |
| Stories per Epic | Min / Max (numbers) | Tells the LLM how many stories to generate per epic. |
| Story Detail Level | Brief / Standard / Detailed | Brief = title + description only; Standard = all fields; Detailed = rich acceptance criteria with examples. |
| Include Sub-tasks | Checkbox | Adds 2–4 sub-task title suggestions per story. |
Steps:
- Paste your meeting notes into the Meeting Notes textarea (or leave blank)
- Select your Copilot Mode and adjust the controls
- Click Generate Prompt — the right panel updates with the tailored prompt
- Click Copy to copy to clipboard, or Download .txt to save
- Paste the prompt into GitHub Copilot, ChatGPT, or any LLM
Paste the YAML/JSON your LLM produced, or upload a .yaml/.json/.txt file.
Expected YAML structure:
epics:
- title: "User Authentication System"
description: "Implement secure login with SSO support"
acceptance_criteria: "Users can log in. Sessions expire after 8 hours."
due_date: "2026-06-30"
priority: "High" # Low | Medium | High | Critical
assignee: "Alice Johnson"
comment: "From sprint planning"
stories:
- title: "Login page UI"
description: "Build the login form with validation"
acceptance_criteria: "Form validates email format."
due_date: "2026-05-15"
priority: "High"
assignee: "Bob Smith"Click Parse → to import.
After parsing, you see a list of epics with checkboxes. Here you can:
- Check/uncheck epics and stories to include or exclude them from upload
- Set an Initiative ID (the Jira key of a parent epic, e.g.
PROJ-42) to link epics — uses a searchable dropdown populated from the cached initiatives list - Override the Project Key per epic if you're targeting multiple projects — uses a searchable dropdown populated from the cached projects list
Click Confirm & Edit → to proceed.
Fine-tune every field before upload. Each epic is an expandable card showing:
| Field | Notes |
|---|---|
| Title | Free text |
| Description | Free text |
| Acceptance Criteria | Posted as a Jira comment in ADF format |
| Assignee | Autocomplete from cached Jira users (refresh in Jira Tools) |
| Status | Target status after creation (e.g. "In Progress") |
| Priority | Low / Medium / High / Critical |
| Due Date | YYYY-MM-DD |
| Labels | Multi-select from cached Jira labels |
| Comment | Optional additional comment posted to the issue |
Stories are nested under their parent epic. Assignee inherits from the epic if left blank on the story.
Click Save & Continue → when done.
Review the summary before pushing: epic count, story count, total issues, and target project. Each epic is listed with its included stories, priorities, due dates, and initiative links.
Click Upload to Jira when ready. A full-screen overlay with a live API log and progress bar appears while the upload runs — you can abort at any time.
After upload, each issue is shown with:
- ✓ Success — with a clickable Jira issue key link
- ✗ Failure — with the error message from the API
For each epic/story, JiraMaster:
- Creates the Jira issue (Epic or Story with parent link)
- Applies the auto-generated run label (
JiraMaster-BBB-XXXXXX) alongside any manually set labels - Transitions it to the requested status (if set)
- Posts acceptance criteria as a comment (ADF format, with plain-text fallback)
- Posts the optional extra comment
Navigate to Jira Tools in the top nav.
Fetches assignable users from Jira and caches them to cache/assignees.json for use in the Edit step.
Fetch Scope — use Load Projects to pick a project other than your configured default.
Filter Options — narrow the user list before caching:
| Filter | How to use |
|---|---|
| Project Role | Click Load Roles to populate the dropdown, then select a role (e.g. Developer, Viewer) |
| Group / Team | Click Load Groups to populate the dropdown, then select your team's Jira group |
| Name / Email Filter | Type a partial name or email to search within assignable users |
| Max Results | Limit the cache size (10–200, default 50) |
Filters are combined with AND logic. If all filters together return 0 users, the cache is not updated.
Fetches labels from Jira and caches them to cache/labels.json. You can scope the fetch to a specific project and filter by prefix, or add labels manually.
Fetches parent epics (initiatives) from Jira and caches them to cache/initiatives.json for use as searchable dropdowns on the Import page.
Filter Options:
- Projects — multi-select; fetch initiatives from specific projects only
- Statuses — multi-select; e.g. fetch only
In Progressinitiatives
Run Refresh Assignees, Refresh Labels, and Refresh Initiatives once after setup, and again whenever your team or project data changes.
Navigate to Cache in the top nav (or http://127.0.0.1:5000/cache).
The Cache Manager shows the current state of all locally cached Jira data:
| Cache | Contents | Source |
|---|---|---|
| Assignees | Jira assignable users (display name, email, account ID) | Jira Tools → Refresh Assignees |
| Labels | Cached project labels with usage counts | Jira Tools → Refresh Labels |
| Projects | Accessible Jira project list | Jira Tools → Load Projects |
| Initiatives | Parent epics for linking | Jira Tools → Refresh Initiatives |
For each cache you can:
- View item count and last-fetched timestamp
- Delete individual entries
- Clear all entries in a cache
Caches are stored in the cache/ directory as JSON files.
JiraMaster can update itself with one click. In Settings, scroll to the Update & Restart card and click the Update & Restart button.
What happens:
- JiraMaster shuts down the Flask server
- Pulls the latest code from GitHub (
git reset --hard origin/main) - Re-installs any new dependencies
- Restarts the server
Your config.json, cache/, .work/, and logs/ are preserved — the update only affects application code.
The page shows a full-screen overlay while the update runs and reloads automatically when the server is back up (polls every 2 seconds for up to 90 seconds).
You can also update from the command line:
macOS / Linux:
./scripts/update.shWindows:
scripts\update.batBoth scripts wait for port 5000 to be free, pull the latest code, and re-launch the app.
FLASK_DEBUG=1 ./scripts/start.shEnables:
- Flask auto-reload on code changes
DEBUG-level console logging (normally onlyINFO)
Windows:
$env:FLASK_DEBUG="1"; .\scripts\start.ps1If your organisation uses TLS inspection, the start scripts automatically merge your system's CA certificates into the certifi bundle so requests trusts your proxy's certificate.
You can also set the proxy URL in Settings → HTTP Proxy URL.
If you need to set it manually:
export REQUESTS_CA_BUNDLE=/path/to/your/ca-bundle.crtJiraMaster uses the OS keyring to store your Jira API token when available:
- macOS — stored in Keychain
- Windows — stored in Credential Manager
- Linux — stored via
keyring(requires a keyring backend such asSecretService)
If no keyring backend is available, the token falls back to plaintext in config.json. The Settings page shows which mode is active in the Security Status panel.
Every upload run automatically applies a label to all created issues in the format:
JiraMaster-BBB-XXXXXX
Where:
- BBB = initials derived from your Jira username (first 2 letters of first name + first letter of last name, e.g.
ALJfor Alice Johnson) - XXXXXX = zero-padded incrementing counter per run (e.g.
000001,000002)
This makes it trivial to search Jira for all issues created in a specific JiraMaster session: label = "JiraMaster-ALJ-000003".
The counter is persisted in cache/run_counter.json and survives restarts.
JiraMaster auto-detects your AC custom field. If auto-detection fails:
- Go to Settings → Detect Fields to try again
- Or find the field ID manually: in Jira, open an issue, right-click the AC field → Inspect → look for
customfield_NNNNN - Enter it directly in Settings → Acceptance Criteria Field ID
Each browser session gets its own UUID, so multiple users can work independently at the same time. Work files accumulate in .work/ — clean them up periodically with:
rm .work/*.jsonLogs are written to logs/jiramaster.log (rotating, 5 MB max, 3 backups) and logs/startup.log. Both are gitignored.
JiraMaster/
├── app.py # Flask app factory, CSRF, blueprint registration
├── config.py # Load/save config.json; prompt template management; keyring integration
├── models.py # Dataclasses: Epic, Story, JiraConfig, UploadResult, Priority
├── jira_client.py # All Jira REST API v3 calls (create, transition, comment, groups, roles)
├── parser.py # Parse YAML/JSON LLM output → Epic/Story objects
├── prompt_builder.py # Build tunable prompts (aggressiveness, detail, story count, copilot mode)
├── logging_config.py # Centralised logging (rotating file + console)
├── work_store.py # Centralised session work-file access with fingerprint validation
├── run_counter.py # Persistent run counter for JiraMaster-BBB-XXXXXX label generation
├── assignees.py # Assignee cache helpers (reads/writes cache/assignees.json)
├── labels.py # Label cache helpers (reads/writes cache/labels.json)
├── operation_events.py # SSE queue/stream infrastructure for long-running operations
│
├── routes/
│ ├── prompt.py # /prompt — generate & download prompt
│ ├── import_view.py # /import — paste/upload and parse LLM output
│ ├── edit.py # /edit — edit all epic/story fields
│ ├── upload.py # /upload — preview and SSE-powered push to Jira
│ ├── settings.py # /settings — Jira credentials, field config, security status
│ ├── tools.py # /tools — refresh assignees/labels/initiatives, update & restart
│ └── cache_manager.py # /cache — view and manage local caches
│
├── templates/ # Jinja2 templates (one subdirectory per blueprint)
├── static/
│ ├── style.css # Bootstrap 5.3 overrides
│ ├── app.js # Clipboard copy, story toggles, cascade checkbox logic
│ └── overlay.js # SSE overlay: progress bar, API log, abort button
│
├── scripts/
│ ├── start.sh # Launch script: macOS/Linux
│ ├── start.ps1 # Launch script: Windows
│ ├── start.bat # Windows batch wrapper
│ ├── update.sh # Update script: macOS/Linux (git pull + restart)
│ ├── update.ps1 # Update script: Windows
│ └── update.bat # Windows batch wrapper
│
├── data/
│ └── prompt_template.txt # Default LLM prompt template
│
├── docs/
│ ├── images/ # Architecture diagrams and app screenshots
│ ├── generate_diagrams.py # Regenerate architecture/dataflow PNGs
│ └── take_screenshots.py # Headless Playwright screenshot tool
│
└── tests/ # pytest test suite (166 tests)
Runtime files (gitignored):
| Path | Purpose |
|---|---|
config.json |
Jira credentials and settings |
.secret_key |
Flask session secret (auto-generated) |
cache/assignees.json |
Cached Jira assignable users |
cache/labels.json |
Cached Jira labels |
cache/projects.json |
Cached Jira project list |
cache/initiatives.json |
Cached Jira initiatives |
cache/run_counter.json |
Persistent upload run counter |
.work/{uuid}.json |
In-progress session work files |
logs/jiramaster.log |
Rotating application log |
logs/startup.log |
Startup diagnostics |
Go to Settings and enter your Jira URL, email, API token, and project key. Click Test Connection to verify.
Your network may use TLS inspection. The start scripts handle this automatically, but if you see SSL: CERTIFICATE_VERIFY_FAILED:
- Re-run
./scripts/start.sh— it merges system CA certs on every start - Or set
REQUESTS_CA_BUNDLE=/path/to/bundle.crtmanually before running
The acceptance criteria field ID may be wrong or missing. Go to Settings → Detect Fields to auto-detect it. If detection fails, find the field ID in Jira's issue create screen source and enter it manually.
Go to Jira Tools → Refresh Assignees. If the list is empty after refresh:
- Ensure your API token has permission to browse users in the target project
- Try using the Group / Team filter to fetch a specific group instead of all assignable users
- Try the Project Role filter to narrow the pool
Your Jira account may not have permission to list groups or roles. Check with your Jira administrator. The Name / Email filter can be used as an alternative to fetch users by partial name.
- Ensure your LLM output starts with
epics:(YAML) or{"epics":(JSON) - Strip any preamble text before pasting — the LLM sometimes adds "Here is the YAML:" before the content
- Try Detail Level: Standard in Step 1 if the LLM keeps adding extra fields
Go to Jira Tools and run the relevant Refresh operations first. The Cache Manager only shows data that has been fetched at least once.
On Linux, keyring requires a backend (e.g. gnome-keyring, kwallet, or pass). If none is installed, credentials fall back to config.json automatically — no action needed. The Settings page shows current credential storage mode.
If the page doesn't reload after ~90 seconds, the app may have failed to start. Check logs/startup.log for errors, then run ./scripts/start.sh manually.
This project is licensed under the GNU General Public License v3.0. See LICENSE for details.










