jira-claude-toolkit
Turn a Jira issue into a Spec-Kit technical specification, from inside Claude Code or from a plain shell.
Install it once, use it in every repository. The toolkit discovers each repository's own Spec-Kit conventions instead of imposing its own.
Jira → Jira Client → Normalized Issue Context → Claude Code
→ Grill / Clarification → Decision → Spec-Kit → Technical Specification
│
┌─────────────────┼──────────────┬──────────────────┐
▼ ▼ ▼ ▼
CREATE_SPEC UPDATE_SPEC NO_OP NEEDS_CLARIFICATION
└─────────────────┘ / BLOCKED
└──► spec.md (no spec)
Quick start
pipx install jira-claude-toolkit # or: pip install jira-claude-toolkit
cd ~/code/your-project
jira-claude install --config-files # writes skills + starter config
$EDITOR .jira-claude.yaml # base_url, project_key
cp .env.example .env && $EDITOR .env # JIRA_EMAIL, JIRA_API_TOKEN
jira-claude doctor # verify everything
Then in Claude Code:
/jira-speckit PROJ-25
That fetches the ticket, investigates your codebase, grills the requirements, decides whether a specification is actually needed, and — if it is — drives your repository's own Spec-Kit specify command. It produces a specification and nothing else; it never implements the feature.
Commands
jira-claude doctor # every check: config, Jira, Spec-Kit, skills, secrets
jira-claude validate-env # configuration only, no network
jira-claude connect # authenticate and confirm project access
jira-claude ticket PROJ-25 # normalized issue context (Markdown or --json)
jira-claude grill PROJ-25 # clarification questions derived from the ticket
jira-claude decide PROJ-25 # CREATE_SPEC / UPDATE_SPEC / NO_OP / NEEDS_CLARIFICATION / BLOCKED
jira-claude smoke-test PROJ-25 # read-only checks against a REAL Jira site
jira-claude install # write the Claude Code skills into a repository
jira-claude scan-secrets # fail if a credential reached a committable file
stdout is data, stderr is diagnostics, exit codes are stable:
jira-claude ticket PROJ-25 --json | jq '.acceptance_criteria'
| Code | Meaning | Code | Meaning |
|---|---|---|---|
| 0 | ok | 7 | not found |
| 2 | usage | 8 | wrong project |
| 3 | missing dependency | 9 | malformed response |
| 4 | config invalid | 11 | unexpected HTTP status |
| 5 | Jira unreachable | 12 | Spec-Kit missing |
| 6 | auth failed | 14 | secret found |
Claude Code commands
| Command | Purpose |
|---|---|
/jira-speckit <TICKET_ID> |
Jira ticket → codebase investigation → grilling → decision → specification |
/jira-connect |
Diagnostics, when something failed |
Status:
1.0.0. Verified against a mock Jira (415 tests, 95% coverage), a CI matrix spanning Python 3.9–3.13 on Ubuntu and Windows, and a live Atlassian site over both REST v2 and v3. See docs/testing.md and CHANGELOG.md for what live data did and did not exercise.
/jira-speckit does not require /jira-connect to have been run. It
validates the connection itself and points at /jira-connect only if something
breaks. There is no connection-state file: every Jira call authenticates, so a
cached "connected" flag could only ever produce a misleading error message.
A Jira ticket does not imply a specification
The workflow ends in an explicit, reportable decision — not in a document by default:
| Outcome | Meaning |
|---|---|
CREATE_SPEC |
Nothing covers this ticket; write a specification |
UPDATE_SPEC |
One exists but is unfinished; extend it, do not fork it |
NO_OP |
An existing specification already covers the request |
NEEDS_CLARIFICATION |
Blocking questions must be answered first |
BLOCKED |
Something outside the ticket prevents progress |
$ jira-claude decide PROJ-25
Jira Issue: PROJ-25
Repository Investigation: COMPLETE
Existing Specification: FOUND
Grill: RESOLVED
Decision: NO_OP
Reason:
Existing specification completely covers this Jira request.
jira-claude decide computes a mechanical proposal from files on disk and
ticket fields — it searches the specs directory for a specification citing this
Jira key. /jira-speckit then confirms or overrides it after reading the
codebase, which is the one part of the pipeline that needs judgement.
Concluding NO_OP is a success. A pipeline that always emits a document emits
duplicates.
Exit code 0 means a decision was computed, not that the answer was "yes". Branch on the outcome, never on the exit status:
outcome=$(jira-claude decide PROJ-25 --json | jq -r .outcome)
case "$outcome" in
CREATE_SPEC|UPDATE_SPEC) run-spec-kit ;;
NO_OP) echo "already specified — nothing to do" ;;
NEEDS_CLARIFICATION) echo "needs answers first" ;;
BLOCKED) exit 1 ;;
esac
Failures that prevent a decision — invalid key (2), config (4), auth (6), not found (7), wrong project (8) — still use their own exit codes.
Configuration: two files, one rule each
.env credentials ONLY — JIRA_EMAIL, JIRA_API_TOKEN. Git-ignored.
.jira-claude.yaml everything else — base URL, project key, limits. Committed.
Putting a credential in the YAML file is a hard error, not a warning: that file is meant to be committed, so accepting a token there would create the exact leak the split exists to prevent.
Precedence: process environment → .env → .jira-claude.yaml → defaults. The
environment wins so CI needs no files on disk.
How it stays repo-agnostic
The skills contain no Jira logic, no credentials, and no paths. Before doing
anything, /jira-speckit runs:
jira-claude doctor --json --offline
and reads this repository's actual conventions out of the result — the specs
directory, feature numbering (003-name vs 20260319-143022-name), and the
real specify command (/speckit-specify or /speckit.specify). Nothing is
assumed, so the same two files work in Engineering OS, Product Intelligence,
TokenHelm and repositories that do not exist yet.
Override discovery per project in .jira-claude.yaml when a repository does
something unusual.
Architecture
Layering is enforced by import direction, not convention:
| Module | Knows about | Knows nothing about |
|---|---|---|
client |
Jira HTTP | Claude Code, Spec-Kit, YAML, the CLI |
issue |
Jira's data shape | HTTP |
grill |
normalized issues | Jira |
speckit |
the host repository | Jira |
config |
files | HTTP |
installer |
Claude Code | Jira |
decision |
specs on disk | Jira, HTTP |
smoke |
real-site verification | Spec-Kit, the CLI |
cli |
all of the above | — |
So the Jira client is usable on its own:
from jira_claude.client import JiraClient
from jira_claude.issue import normalize
from jira_claude.secrets import Secret
client = JiraClient("https://acme.atlassian.net", "dev@acme.io", Secret(token))
issue = normalize(client.issue("PROJ-25"))
print(issue.acceptance_criteria)
…and the Claude Code integration is usable against any repository.
See docs/architecture.md.
Requirements
Python 3.9+. One runtime dependency: PyYAML. HTTP is stdlib urllib.
Tests
pip install -e ".[dev]"
pytest # 389 tests — mock only, no credentials, no network
pytest -m "not integration" # unit tests only
pytest --cov=jira_claude # coverage
Real-Jira tests are a separate layer and are deselected by default. They
need JIRA_REAL_TEST=1 plus real credentials plus -m realjira; any gate closed
means skipped, never failed. They are read-only.
jira-claude smoke-test PROJ-25 # the same checks, without pytest
See docs/testing.md.
Security
The API token is wrapped in a Secret that cannot be printed by accident —
str(), repr(), f-strings and format() all yield ***, and pickling
raises. It travels in an HTTP header, never in a URL, a process argument, or a
file. Redirects are refused so it is never forwarded to another host.
jira-claude scan-secrets # pre-commit hook material
See docs/security.md.
Documentation
- Installation — pip, pipx, local, skill install
- Configuration — project config and secrets
- Architecture — the seven components and the layering
- Testing — mock testing vs real-Jira smoke testing
- Security — credential containment
- End-to-end example — a real run, verbatim
- Changelog — release history and the path to 1.0.0
Licence
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jira_claude_toolkit-1.0.0.tar.gz.
File metadata
- Download URL: jira_claude_toolkit-1.0.0.tar.gz
- Upload date:
- Size: 84.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
855c3f581401b3013898c25a54ee2c59d455385405dbc6041840ff42e7de4c65
|
|
| MD5 |
ca265294f284e2b00c7efcd14b1b1d95
|
|
| BLAKE2b-256 |
f776e1ab7a5a84bf301628cbfa86e81767d4c5b0c62b5a941285768de8a7c055
|
Provenance
The following attestation bundles were made for jira_claude_toolkit-1.0.0.tar.gz:
Publisher:
publish-pypi.yml on srinitrumatics/jira_claude
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jira_claude_toolkit-1.0.0.tar.gz -
Subject digest:
855c3f581401b3013898c25a54ee2c59d455385405dbc6041840ff42e7de4c65 - Sigstore transparency entry: 2438126770
- Sigstore integration time:
-
Permalink:
srinitrumatics/jira_claude@62523885b0c38be4d977704e7aec065ed6aa1775 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/srinitrumatics
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@62523885b0c38be4d977704e7aec065ed6aa1775 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file jira_claude_toolkit-1.0.0-py3-none-any.whl.
File metadata
- Download URL: jira_claude_toolkit-1.0.0-py3-none-any.whl
- Upload date:
- Size: 61.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17b5bb7e441dcc276af71de2825dbc18b55a7f4ce25427a267221e5b05ae4ccd
|
|
| MD5 |
7d0f6a1505920b720f8746a7acc3ead9
|
|
| BLAKE2b-256 |
3154aaf091acd23d09264e463b2d4dbd21687128b44a2e2f2fdf1867ab171a3a
|
Provenance
The following attestation bundles were made for jira_claude_toolkit-1.0.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on srinitrumatics/jira_claude
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jira_claude_toolkit-1.0.0-py3-none-any.whl -
Subject digest:
17b5bb7e441dcc276af71de2825dbc18b55a7f4ce25427a267221e5b05ae4ccd - Sigstore transparency entry: 2438126791
- Sigstore integration time:
-
Permalink:
srinitrumatics/jira_claude@62523885b0c38be4d977704e7aec065ed6aa1775 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/srinitrumatics
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@62523885b0c38be4d977704e7aec065ed6aa1775 -
Trigger Event:
workflow_dispatch
-
Statement type: