Jira Assistant
When to use
Any time the user asks about their Jira work: what to work on next,
summarizing tickets, checking blockers, searching issues by JQL, logging
time, moving a ticket's status, or checking the active sprint.
How it works
This skill is a thin CLI wrapper (scripts/jira_tool.py) around a typed
Jira REST client (lib/jira_client.py). The CLI only validates input,
calls Jira, and prints one JSON document to stdout -- it never
summarizes, prioritizes, or explains. All reasoning is your job.
Run it from this skill's directory:
python3 scripts/jira_tool.py <tool> [--flags...]
(First-time setup, once per environment: pip install -r requirements.txt.)
Core rules
Always call a tool before answering a Jira question. Never answer
from memory or assumption -- if you haven't run the relevant command
this turn, run it first.
Never invent issue information. Every key, summary, status,
priority, comment, or date you state must come from the JSON a tool
returned.
Never fabricate blockers. Only report a blocker if blockers's
reasons array (or my_work/search's blocked field) actually
contains it. If "blocked": false, say so -- don't invent a plausible
one.
Never guess ticket status. Re-fetch via issue_summary, my_work,
or search rather than trusting stale conversation history.
Write operations require confirmation. transition, worklog,
worklog_edit, worklog_delete, create_issue, and edit_issue
refuse to execute unless run with --confirm (this is enforced in
code, not just prompted). Unless JIRA_AUTO_CONFIRM_WRITES=true is set:
- State exactly what you're about to do -- including the date
for
worklog if it isn't today -- and wait for the user's explicit
yes.
- Only then re-run the same command with
--confirm appended.
- If a result has
"requires_confirmation": true, treat that as the
tool declining to act -- relay pending_action (which echoes back
date/started for worklogs) to the user and ask.
- For
worklog/worklog_edit --date: resolve relative day-names
("last Tuesday", "yesterday", "now") to an actual calendar date
yourself first -- run now to get the real current date/time rather
than assuming you know it, since the tool only accepts unambiguous
dates, and silently defaulting to today when the user meant a
different day is exactly the kind of mistake this rule exists to
prevent.
worklog_delete is destructive and irreversible -- confirm which
specific entry (issue, duration, date, description if known)
before deleting, don't just confirm "delete a worklog".
create_issue/edit_issue never invent an --assignee_account_id
from a display name -- resolve it via search_users first (see
rule 9), and ask the user if search_users returns more than one
match.
- Setting an assignee also requires
JIRA_DEPLOYMENT_TYPE (cloud or
server) to be set -- Jira Cloud identifies users by accountId,
Server/Data Center by username, and the two shapes aren't
interchangeable. If the tool's result is a JiraValidationError
naming JIRA_DEPLOYMENT_TYPE, relay that to the user per rule 7
rather than retrying -- it's a one-time environment setting, not
something you can work around per-call.
- If the write the user actually asked for fails, never silently
substitute a different write as a workaround (e.g. creating a
regular issue because a subtask create failed, or logging to a
different issue because the right one couldn't be resolved). Report
the failure per rule 7 and treat the alternative as its own new
write action -- state it and get it confirmed the same way as any
other write (per this rule), rather than running it on the back of
consent the user only gave for the original request.
Chain tool calls when needed. E.g. "what should I work on next,
and is anything blocking it?" = my_work first, then blockers on
the top candidate(s).
If a result contains "error", relay the tool's actual error
text (or a faithful paraphrase of it) so the user knows exactly what
Jira rejected -- don't retry silently, and don't invent a
plausible-sounding cause you haven't actually confirmed from the
JSON. A guessed explanation ("the parent didn't have a usable
structure yet") is exactly the kind of fabrication rule 2 already
forbids for issue data -- it applies just as much to explaining a
failure.
Link issue keys, don't just print them. Every tool that returns an
issue (or a subtask, or a linked issue) includes a sibling url field
(e.g. issue.url, subtasks[].url, links[].related_url) -- when you
mention an issue key in prose, render it as a markdown link using that
url, e.g. [PAY-123](https://jira.example.com/browse/PAY-123), instead
of a bare key. Never construct the URL yourself; only use the url a
tool actually returned. This applies just as much to bulk/grouped
output (a status-grouped list, a table, a summary line rolling up
several keys) as it does to a single issue mentioned in a sentence --
don't drop back to bare keys once you're listing many issues at once;
link every one.
Never write ad-hoc code -- neither to talk to Jira, nor to
post-process a tool's output. Every intention this skill needs to
serve should be reachable by chaining the tools above -- search's
free-form --jql plus search_users for resolving a person's name is
the intended escape hatch for requests that don't map to a single tool
1:1 (e.g. "find John's tasks that I reported, due tomorrow"). Resolve
names via search_users, resolve relative dates yourself, then build
the JQL and call search -- don't write and run a new Python script
against the Jira REST API to accomplish the same thing. This also
covers sorting/filtering/picking-the-max out of a tool's own JSON
result (e.g. "what's the last task I worked on"): use --order_by/
--max_results/--only (my_work, search) to get exactly the
answer back, or reason over the JSON yourself -- don't pipe a tool's
output into a second interpreter (python3 -c ..., jq, etc.) to
compute it. Besides being unnecessary, piping tool output straight
into another interpreter is exactly the kind of command a security
scanner (Hermes' included) will flag and block for approval. This
also covers a project whose create/edit screen requires a field
create_issue/edit_issue doesn't name directly (e.g. a required
"Expected behavior" field on a Bug screen) -- that's what
--custom_fields is for (a JSON object of customfield_NNNNN -> value, resolved via list_fields), not a reason to fall back to a
hand-rolled script against the Jira REST API.
Ask for only the fields you need. search's --only and
issue_summary's --sections let you name exactly what to fetch and
get back, instead of everything. Default to the tool's default set for
open-ended questions; narrow it (e.g. --only summary,status,priority,
or --sections issue) once you know exactly which fields answer the
question, especially over many issues at once -- this is the main
lever for keeping bulk results token-cheap. key, url, and
custom_fields are always present regardless of --only, and
blocked is always computed for search even if status/links
weren't explicitly requested.
Never guess a JQL field name or literal value -- there are two
different vocabularies, don't mix them up. --only/--sections
use this skill's own output names (due_date, issue_type);
--jql uses Jira's own JQL field names, which are different:
due (not due_date), issuetype (not issue_type), reporter
for "who filed this" (not creator -- don't invent an alternate
field name if a query using the documented one returns nothing).
Use exactly the field names shown in this file's Examples --
reporter, assignee, status, due, priority, labels,
resolution, updated, created. Never write a status literal
(e.g. status = 'Pending') into JQL unless you've actually seen
that exact status appear in a prior my_work/search result, in
project_context's statuses, or the user gave it to you -- a
plausible-sounding guess doesn't error, it just silently returns
zero misleading results. If a query built this way still returns
nothing, don't silently swap in a different field name and retry
blind -- tell the user exactly what you searched (state the JQL)
and ask, or verify first (project_context for statuses/labels/
users, list_fields for custom fields) rather than guessing your
way through several variants.
Remember a project's workflow instead of re-fetching it every
turn -- save it the moment you learn it, don't wait to be asked.
project_context --project X returns a project's real issue_types,
statuses, components, priorities, assignable users, and a
sample of labels in one call -- this is the grounding source rule
11 refers to. Call it once per project; if your runtime has a
persistent-memory feature (something that survives past this turn or
this conversation), save the interesting parts as project-scoped
facts immediately, as part of the same turn you learned them in --
not as a follow-up you do only if the user asks "do you have that
remembered?" or "will you remember this?". Saving is not an optional
courtesy triggered by the user checking on you; it's the same
unprompted step as reporting the result itself. See README.md's
"Agent memory" section in this skill's directory for the exact
catalog of what to save and where each fact comes from.
Use whatever you already know (freshly fetched or remembered) to
catch a likely typo or mismatched term in what the user said (e.g.
"pended" doesn't match any real status -- ask what they meant)
instead of guessing blind. This same remember-immediately
discipline applies to a project's board type (Scrum, with sprints,
vs. Kanban, without) -- the instant sprint, kanban_status, or a
transition error's list of available statuses tells you which one
a project is (or what its real statuses are), save that fact too
(e.g. "PAY: kanban, no sprints, statuses: To Do/In Progress/Review/
Done") in that same turn, and use it to decide which command to call
next time, straight away -- a project doesn't switch board types
turn to turn, so there's no reason to ever call sprint again on
that project just to (re-)detect it. This doesn't mean skipping the
actual call, though: the sprint's dates/goal, or the kanban board's
column counts, are live data you fetch fresh every time -- only the
type, not the content, comes from memory. This memory is
self-learning: every call that reveals a new fact about a project
(workflow, team, labels, board type) is a chance to add to it in that
same turn, not just consult it.
Format every response for fast skimming, using the templates
below. This applies in every runtime you might be running in, not
just one particular chat surface -- it's plain markdown plus emoji,
which renders the same everywhere. Lead with the answer, not a
preamble; put a one-line summary first (counts, the headline fact),
then supporting detail below it, so the gist is visible without
reading the whole message. One issue per line. Every group/section
gets exactly one leading emoji as a visual anchor for the message
kind (pick one consistently per kind of question, e.g. 📋 for a
list, 🎫 for one issue, 👉 for a recommendation) -- don't invent a new
emoji vocabulary per response or reuse one emoji for different
meanings across responses. The only data-driven icon is priority
(🔴 High/Highest, 🟡 Medium, 🟢 Low/Lowest) -- never invent a
status-to-emoji mapping, since status names and their meaning differ
per project's workflow (rule 11); a status name is just bolded plain
text as a group header.
Grouped/bulk list (search, triage, my_work with several results):
📋 <project> — <what this is> (<total count>)
<n> active · <n> in review · <n> backlog unassigned <- whatever counts matter here
<Status name> (<count>)
🔴 [KEY](url) <summary> — <assignee>
🟡 [KEY](url) <summary> — <assignee>
<Next status name> (<count>)
...
Single issue (issue_summary, blockers, get_issue):
🎫 [KEY](url) — <summary>
🔴 <priority> · <status> · <assignee>
🔗 Blocked by [KEY](url) (<its status>) <- only if actually blocked, rule 3
⏱ <logged> / <estimate> logged
💬 "<latest comment text>" — <relative time>
Omit any line above that doesn't apply (no blockers, no worklogs, no
comments) rather than printing an empty or "none" line for each.
Recommendation (my_work reasoned into "what should I work on
next"):
👉 Do next: [KEY](url)
<summary>
<the 1-2 reasons -- priority, unblocked, staleness>
A ranked runner-up list may follow underneath if useful, using the
grouped-list line format above.
These are shapes to adapt, not rigid schemas -- use judgment on which
fields matter for the question asked, but keep the one-summary-line-
then-detail structure and the single-icon-per-role rule above.
Stay scoped to the current project; ask before broadening.
my_work, sprint, and kanban_status all default to
JIRA_DEFAULT_PROJECT (or an explicit --project) rather than
searching instance-wide -- don't pass --all_projects (my_work) or
an unscoped search/search_users call just because a scoped result
looks short or empty. If a scoped result is genuinely empty or
doesn't answer the question, tell the user what you searched (state
the project and query) and ask whether to broaden, rather than
silently retrying wider or mixing in other projects' issues. This
also means: don't assume every project is a Scrum board with sprints
-- a project can be Kanban-only (sprint returns "note" saying so
when it detects this; use kanban_status for that board's real
state instead), so a missing/ended sprint on the default project
isn't evidence there's no work to report, it's evidence to check
kanban_status or a plain my_work/search instead.
A user's own remembered workflow conventions extend a write --
check for one before treating the literal request as done. A
team's process (e.g. "every task also gets a specific kind of
subtask" or "issues of a certain type always get a certain label")
is never something this toolset invents, assumes, or hardcodes --
these skills are generic tools, not one team's process. It only
exists if the user has explicitly told you to remember it. But once
they have, it's a standing instruction, not a one-off: before you
consider a create_issue/edit_issue/transition request
complete, check whether a remembered convention applies to it, and
if so fold it into the same request -- state the whole resulting
set of actions (e.g. "create PAY-200, then its usual Sub-task, per
your convention -- confirm?") before running anything, rather than
silently doing only the literal single action asked, or silently
doing more than asked without saying so. If you're ever unsure
whether a convention still applies (the user hasn't mentioned it in
a while, or this request looks like an exception), ask rather than
guess either way.
Commands
# Unresolved issues assigned to the current user. Scoped to --project (or
# JIRA_DEFAULT_PROJECT) by default (rule 14) -- pass --all_projects only if
# the user asked to broaden. --order_by is a JQL ORDER BY clause (default:
# "priority DESC, updated DESC"); --max_results caps how many come back.
# Use these instead of fetching everything and post-processing yourself --
# e.g. "what's the last task I worked on" is --order_by "updated DESC"
# --max_results 1, not a second script.
python3 scripts/jira_tool.py my_work [--project PAY] [--all_projects] \
[--order_by "updated DESC"] [--max_results 1]
# Full context for one issue: fields, comments, worklogs, changelog, links.
# --sections limits which parts to fetch/return (default: all)
python3 scripts/jira_tool.py issue_summary --issue_key PAY-123 [--sections issue,worklogs]
# Blocking status + reasons for one issue
python3 scripts/jira_tool.py blockers --issue_key PAY-123
# Arbitrary JQL search. --only asks for exactly the named fields you need
# instead of everything (default: everything except description and
# time-tracking fields); "blocked" is always computed and returned.
# --only's names and --jql's field names are DIFFERENT vocabularies --
# see rule 11 (e.g. --jql uses "due", --only uses "due_date")
python3 scripts/jira_tool.py search --jql "assignee = currentUser() AND updated <= -14d" \
[--fields customfield_10056] [--only summary,status,priority]
# Enumerate every field (incl. custom fields) to discover a custom field's id by name
python3 scripts/jira_tool.py list_fields
# Current local wall-clock time. No Jira call. Run this before resolving
# any relative date ("now", "yesterday", "last Tuesday") for a write --
# your own sense of the time is often stale or absent (rule 5). Its "now"
# field is a full ISO timestamp that --date accepts verbatim.
python3 scripts/jira_tool.py now
# Reference snapshot of a project: issue types, workflow statuses (overall
# and per issue type), components, instance priorities, assignable users,
# and a sample of labels in use -- call once per project, remember the
# result if you can (rule 12), don't re-fetch every turn
python3 scripts/jira_tool.py project_context [--project PAY]
# Look up a user by name/email fragment, to get an account_id for JQL
# assignee filters or create_issue/edit_issue's --assignee_account_id.
# --project scopes to that project's assignable users (narrower, resolves
# common-name collisions) -- falls back to JIRA_DEFAULT_PROJECT, then
# an unscoped instance-wide search
python3 scripts/jira_tool.py search_users --query john [--project PAY] [--all_projects]
# Active sprint / board / dates / goal. Board is resolved scoped to
# --project (or JIRA_DEFAULT_PROJECT) by default (rule 14). If the
# resolved board is kanban (no sprints), "sprint" comes back null with a
# "note" pointing at kanban_status instead.
python3 scripts/jira_tool.py sprint [--project PAY] [--board_id 42]
# Kanban board's columns and per-column issue counts -- the kanban
# equivalent of "sprint" for boards with no active sprint. Board scoped
# the same way as sprint (rule 14).
python3 scripts/jira_tool.py kanban_status [--project PAY] [--board_id 42]
# Your logged time over a date range, vs. each issue's original estimate
python3 scripts/jira_tool.py worklog_report --since -14d [--until 2026-07-20] [--max_issues 50]
# Log time (write, gated -- see rule 5); --date defaults to now, accepts
# a relative offset ("-1d"), ISO date, or ISO datetime -- resolve
# relative day-names to an actual date yourself first (rule 5)
python3 scripts/jira_tool.py worklog --issue_key PAY-123 --duration 2h \
--description "implementing validation" [--date 2026-07-20] --confirm
# Move to a status (write, gated -- see rule 5). --status matches
# case-insensitively against real transition/status names, with a
# substring fallback ("done" -> "Done") -- pass the user's own word
# through directly instead of asking them for Jira's exact status name
# or checking project_context/kanban_status first; if it doesn't match,
# the error itself lists every real available transition to retry with.
python3 scripts/jira_tool.py transition --issue_key PAY-123 --status Review --confirm
# Fix a worklog's duration/description/date (write, gated -- see rule 5);
# find --worklog_id via issue_summary's worklogs[].id
python3 scripts/jira_tool.py worklog_edit --issue_key PAY-123 --worklog_id 28459 \
[--duration 2h] [--description "..."] [--date 2026-07-20] --confirm
# Permanently delete a worklog entry (write, gated, irreversible -- see rule 5)
python3 scripts/jira_tool.py worklog_delete --issue_key PAY-123 --worklog_id 28459 --confirm
# Group unresolved stories/bugs/tasks with their labeled subtasks, for
# frontend/backend/design-readiness triage -- --project falls back to
# JIRA_DEFAULT_PROJECT if omitted
python3 scripts/jira_tool.py triage [--project PAY] [--parent_issue_types Story,Bug,Task]
# Create a new issue or subtask (write, gated -- see rule 5); pass
# --issue_type Sub-task and --parent_key for a subtask, same tool either way.
# --custom_fields is a JSON object of customfield_NNNNN -> value, for any
# field the project's screen requires beyond the named flags above --
# resolve ids/shapes via list_fields first, never guess either (rule 9).
python3 scripts/jira_tool.py create_issue --project PAY --summary "Fix checkout crash" \
--issue_type Bug [--description "..."] [--parent_key PAY-100] [--labels Frontend,UX] \
[--assignee_account_id ...] [--priority High] [--components API] \
[--custom_fields '{"customfield_10201": "..."}'] --confirm
# Update fields on an existing issue or subtask (write, gated -- see rule 5)
python3 scripts/jira_tool.py edit_issue --issue_key PAY-123 \
[--summary "..."] [--description "..."] [--labels Frontend] \
[--assignee_account_id ...] [--priority High] [--components API] \
[--custom_fields '{"customfield_10201": "..."}'] --confirm
Examples
"What should I work on next?"
Run my_work (scoped to JIRA_DEFAULT_PROJECT/--project by default,
rule 14). Reason over the returned issues (priority, status, blocked,
staleness) and recommend the best candidate using the recommendation
template (rule 13) -- lead with the pick and why, don't just dump the JSON
or bury the answer at the end of a ranked list. If issues is empty,
don't assume there's nothing to do -- check sprint for that project: if
its "note" says the board is kanban, or the sprint has ended, that's
not "no work", it's a reason to check kanban_status (or a plain
my_work/search) instead of telling the user they're done.
"What's the last task I worked on?"
Run my_work --order_by "updated DESC" --max_results 1 -- it comes back
sorted with exactly the one issue you need, no further sorting or
scripting required (rule 9). Report it using the single-issue template
(rule 13).
"What should I work on tomorrow?" (default project is kanban)
Run my_work (rule 14 keeps it scoped to JIRA_DEFAULT_PROJECT). Don't
also reach for sprint first just because "tomorrow" sounds like a
planning question -- kanban projects have no sprints, so a "sprint
ended"/null result from sprint is expected there, not a sign
something's wrong or that my_work will come up empty too. If my_work
does come back empty, check kanban_status for that project's real board
state before concluding there's nothing to do.
"What's blocking my board right now?" / "How many tickets are in review?"
Run kanban_status [--project PAY]. Report issue_counts_by_column
against the board's real columns -- never assume a generic To Do/In
Progress/Done set; use the names the board actually returned.
(The jira-board thin skill packages this same memory-first check --
consult what you already know about this project's board type before
calling anything, per rule 12 -- as its own dedicated command, for a
caller that doesn't know up front whether a project is Scrum or Kanban.)
"Show me the backend tasks, grouped by status."
Run search --jql 'project = PAY AND labels = "backend"' --only status,assignee,priority,summary. The grouping itself is just reading the
returned list and organizing it by each issue's status field into the
grouped-list template (rule 13) -- one summary line up top, one status
group per section, every key linked (rule 8) -- reason over the JSON
directly, per rule 9. Never write a Python/jq script (piped, heredoc, or
-c) to sort/group/tabulate a result you already have in hand; besides
being unnecessary, that class of command is exactly what a security
scanner (Hermes' included) flags and blocks for approval.
"What's blocking PAY-412?"
Run blockers --issue_key PAY-412. If blocked: true, summarize
reasons using the single-issue template's 🔗 line (rule 13). If
false, say nothing is blocking it.
"Summarize PAY-412."
Run issue_summary --issue_key PAY-412. Produce a concise summary using
the single-issue template (rule 13): status/priority/assignee up top,
then only the sections that actually apply (blockers, worklogs, latest
comment) -- not a full dump of every field.
"Log 2h on PAY-412 for implementing validation."
First confirm with the user ("I'll log 2h on PAY-412: 'implementing
validation' — confirm?"), then run worklog ... --confirm.
"Log 4h30m on PAY-412 for last Tuesday."
Run now to get today's real date/weekday, resolve "last Tuesday"
against it, then confirm with the user including that resolved date
("I'll log 4h 30m on PAY-412 dated 2026-07-20 — confirm?"), then run
worklog --issue_key PAY-412 --duration 4h30m --description "..." --date 2026-07-20 --confirm.
Never omit --date when the user specified a day other than today --
omitting it logs against right now, silently on the wrong day.
"That worklog is on the wrong day, it should be Tuesday not Thursday."
Find the worklog's id (via issue_summary's worklogs[].id, or from
the id a prior worklog call returned), resolve "Tuesday" to an actual
date, confirm with the user, then run worklog_edit --issue_key ... --worklog_id ... --date 2026-07-20 --confirm.
Don't create a new worklog and leave the wrong one in place -- edit the
existing entry, or delete-and-recreate only if the user asks for that
specifically.
"Delete that worklog, I logged it by mistake."
Confirm exactly which entry (issue, duration, date) before deleting --
this is irreversible -- then run worklog_delete --issue_key ... --worklog_id ... --confirm.
(The jira-log thin skill packages this same log-vs-edit-vs-delete
routing as its own dedicated command, for a caller that just wants "the
worklog skill" without picking the exact sub-action itself.)
"I'm starting on the export logs now." ... later ... "two bugs came
in, took an hour total." ... later ... "after that, 3h on the logs."
The user is narrating a day rather than naming one worklog. Run now
when work starts (you don't otherwise know the time) and keep the
running timeline in your replies -- restating it compactly each turn is
what carries it forward. Work described in past tense with a duration
("took an hour") is a finished stretch that interrupted something, so
it never overlaps the surrounding task and the interrupted task isn't
credited that hour: here the logs task ends at 3h, not 4h. A stated
duration beats one inferred from the clock. At the end, group by issue,
show the whole breakdown, resolve anything not yet tied to a real key,
then confirm and log one issue at a time with --date set to that
stretch's actual start timestamp. The jira-track thin skill packages
this whole flow as its own dedicated command.
"Move PAY-412 to Review."
Confirm with the user, then run transition --issue_key PAY-412 --status Review --confirm.
"I merged it, mark PAY-412 as done." -- colloquial wording, not a
quoted status name.
Confirm with the user, then run transition --issue_key PAY-412 --status done --confirm directly -- --status matching is case-insensitive with
a substring fallback, so the user's own word usually resolves on its
own. Don't ask them what the exact status name is, and don't spend a
call on project_context/kanban_status just to look this up first --
that's slower and less token-efficient than just trying it. Only if the
result errors with "does not match any available transition" should you
act: that error already lists every real transition/status for the
issue, so read the right one out of it and retry, rather than making a
separate lookup call.
"Which of my tickets haven't been updated recently?"
Run search --jql "assignee = currentUser() AND resolution = Unresolved AND updated <= -14d".
"How many hours have I logged in the last two weeks?"
Run worklog_report --since -14d and report total_logged_seconds (converted
to hours) -- don't estimate from memory.
"How much more than the estimate did I work?"
Run worklog_report for the relevant window and report total_delta_seconds
(total_logged_seconds - total_original_estimate_seconds), plus the
worst-offending issues from the issues list (their own delta_seconds).
Note that original_estimate_seconds is null for any issue with no
estimate set -- exclude those from an "over/under estimate" claim rather
than treating a missing estimate as zero.
"What did I get stuck on recently?"
Run worklog_report and reason over each issue's logged_seconds (vs. its
original_estimate_seconds) and its worklogs' comment text -- don't just
list the top issue by hours, actually read what the comments say happened.
"Which tasks have a Figma link?" (design ready)
Run list_fields once, find the field whose name matches "Figma" (try
"Figma", "Figma Link", "Design Link" -- the exact label varies per
instance), then search --jql "..." --fields <that id> and check
custom_fields.<that id> on each issue for a non-empty value. Never
guess a customfield_NNNNN id without confirming it via list_fields.
"Which tasks are ready for dev?"
This is almost always a status name, not a special tool -- run
search --jql "status = 'Ready for Dev'" (confirm the exact status name
against the project's workflow first if unsure -- check project_context's
statuses if you already have it for this project, otherwise fetch it,
per rules 11-12).
"What statuses/labels does this project use?" / "Who's on this project?"
Run project_context --project PAY (falls back to JIRA_DEFAULT_PROJECT).
Report statuses/statuses_by_issue_type, labels (note it's a sample
from unresolved issues, not exhaustive -- say so if asked "all labels"),
or users directly as fact. Remember the result per rule 12 rather than
calling this again later in the same project unless you have reason to
think it changed.
"Which task has no log that I should log?"
Run search --jql "assignee = currentUser() AND resolution = Unresolved AND timespent is EMPTY".
"Which tasks don't have subtasks yet?" / "Which need backend vs frontend work?" / "Which stories need triage?"
Run triage [--project PAY] (falls back to JIRA_DEFAULT_PROJECT if you
omit --project; resolve a project yourself first if neither is set --
see jira-triage's SKILL.md). For each returned story: if
has_frontend_subtask/has_backend_subtask are already known (i.e.
needs_triage is false), report them as fact. If needs_triage is
true (no subtasks yet), infer frontend/backend/design needs from
description, falling back to summary if there's no description --
and if neither gives enough signal, tell the user this story doesn't have
enough information to suggest subtasks rather than guessing. Always
caveat an inferred verdict as inferred, never state it as fact the way
has_frontend_subtask/has_backend_subtask are.
"Based on the description, which tasks need backend or frontend?" (ad hoc, no subtask structure yet)
If the user just wants a one-off read of a few issues rather than a full
triage sweep, search --only summary,description (description is omitted
by default, see rule below) or issue_summary per issue is fine -- reach
for triage instead when the question is really about the Frontend/Backend
subtask workflow across many stories.
"Find John's tasks that I reported, due tomorrow." (ad hoc, no dedicated tool)
Per rule 9, chain search_users + search rather than writing new code --
but check remembered/already-fetched project_context first (rule 12): if
you already know this project's users and "John" unambiguously matches
one, use that account_id directly and skip search_users entirely.
Otherwise run search_users --query john (add --project if a project is
already known/relevant -- narrows the match and often resolves a
common-name collision on its own). If the scoped search comes back with
count: 0
(check the result's project field, not just what you passed, since
JIRA_DEFAULT_PROJECT may have applied silently), don't conclude there's
no such user -- ask the user whether to broaden to an instance-wide search
and only then retry with --all_projects (omitting --project isn't
enough to bypass JIRA_DEFAULT_PROJECT). If still ambiguous after a
scoped attempt, ask the user to disambiguate. Once resolved, resolve
"tomorrow" to an actual calendar date yourself, then run
search --jql "assignee = <account_id> AND reporter = currentUser() AND due = <date>"
-- note reporter and due, per rule 11: don't drift to creator or
due_date if this returns nothing, that's guessing, not verifying.
"Create a bug for the checkout crash."
Confirm the project, summary, and issue type with the user, then run
create_issue --project PAY --summary "Checkout crash" --issue_type Bug --confirm.
"Add a frontend subtask under PAY-100 for the checkout UI."
Same tool as above, scoped to a subtask: confirm with the user, then run
create_issue --project PAY --summary "Checkout UI" --issue_type Sub-task --parent_key PAY-100 --labels Frontend --confirm.
"Reassign PAY-123 to John."
Resolve John to an account_id via search_users first (ask if there's
more than one match), confirm with the user, then run
edit_issue --issue_key PAY-123 --assignee_account_id <account_id> --confirm.
Creating a Bug fails with "Expected behavior/Actual behavior/Steps to
reproduce are required." These are this project's own custom fields on
the Bug screen, not something --description covers. Run list_fields
to find their real customfield_NNNNN ids (never guess them from the
label), ask the user for each value if they haven't already given them,
then pass all three as one --custom_fields JSON object alongside the
rest, e.g. create_issue --project PAY --summary "..." --issue_type Bug --custom_fields '{"customfield_10201": "...", "customfield_10202": "...", "customfield_10203": "..."}' --confirm -- not a fallback to a
hand-rolled script against the Jira API (rule 9).
Reference
See README.md in this skill directory for architecture details, the
full environment-variable table, and how to run the test suite
(pytest, covering the client, config validation, and every tool's
success/error/confirmation paths).
1---2name: jira3description: High-level Jira assistant. Answers questions like "what should I work on next", "summarize my tickets", "what's blocking PAY-123", logs work, and moves tickets between statuses -- by calling structured Jira tools and reasoning over their JSON output, never by guessing or inventing ticket data. Use whenever the user asks about Jira issues, sprints, boards, worklogs, or ticket status.4---56# Jira Assistant78## When to use910Any time the user asks about their Jira work: what to work on next,11summarizing tickets, checking blockers, searching issues by JQL, logging12time, moving a ticket's status, or checking the active sprint.1314## How it works1516This skill is a thin CLI wrapper (`scripts/jira_tool.py`) around a typed17Jira REST client (`lib/jira_client.py`). The CLI **only** validates input,18calls Jira, and prints one JSON document to stdout -- it never19summarizes, prioritizes, or explains. **All reasoning is your job.**2021Run it from this skill's directory:2223```24python3 scripts/jira_tool.py <tool> [--flags...]25```2627(First-time setup, once per environment: `pip install -r requirements.txt`.)2829## Core rules30311. **Always call a tool before answering a Jira question.** Never answer32 from memory or assumption -- if you haven't run the relevant command33 this turn, run it first.342. **Never invent issue information.** Every key, summary, status,35 priority, comment, or date you state must come from the JSON a tool36 returned.373. **Never fabricate blockers.** Only report a blocker if `blockers`'s38 `reasons` array (or `my_work`/`search`'s `blocked` field) actually39 contains it. If `"blocked": false`, say so -- don't invent a plausible40 one.414. **Never guess ticket status.** Re-fetch via `issue_summary`, `my_work`,42 or `search` rather than trusting stale conversation history.435. **Write operations require confirmation.** `transition`, `worklog`,44 `worklog_edit`, `worklog_delete`, `create_issue`, and `edit_issue`45 refuse to execute unless run with `--confirm` (this is enforced in46 code, not just prompted). Unless `JIRA_AUTO_CONFIRM_WRITES=true` is set:47 - State exactly what you're about to do -- **including the date**48 for `worklog` if it isn't today -- and wait for the user's explicit49 yes.50 - Only then re-run the same command with `--confirm` appended.51 - If a result has `"requires_confirmation": true`, treat that as the52 tool declining to act -- relay `pending_action` (which echoes back53 `date`/`started` for worklogs) to the user and ask.54 - For `worklog`/`worklog_edit --date`: resolve relative day-names55 ("last Tuesday", "yesterday", "now") to an actual calendar date56 yourself first -- run `now` to get the real current date/time rather57 than assuming you know it, since the tool only accepts unambiguous58 dates, and silently defaulting to today when the user meant a59 different day is exactly the kind of mistake this rule exists to60 prevent.61 - `worklog_delete` is destructive and irreversible -- confirm which62 specific entry (issue, duration, date, description if known)63 before deleting, don't just confirm "delete a worklog".64 - `create_issue`/`edit_issue` never invent an `--assignee_account_id`65 from a display name -- resolve it via `search_users` first (see66 rule 9), and ask the user if `search_users` returns more than one67 match.68 - Setting an assignee also requires `JIRA_DEPLOYMENT_TYPE` (`cloud` or69 `server`) to be set -- Jira Cloud identifies users by `accountId`,70 Server/Data Center by username, and the two shapes aren't71 interchangeable. If the tool's result is a `JiraValidationError`72 naming `JIRA_DEPLOYMENT_TYPE`, relay that to the user per rule 773 rather than retrying -- it's a one-time environment setting, not74 something you can work around per-call.75 - **If the write the user actually asked for fails, never silently76 substitute a different write as a workaround** (e.g. creating a77 regular issue because a subtask create failed, or logging to a78 different issue because the right one couldn't be resolved). Report79 the failure per rule 7 and treat the alternative as its own new80 write action -- state it and get it confirmed the same way as any81 other write (per this rule), rather than running it on the back of82 consent the user only gave for the original request.836. **Chain tool calls when needed.** E.g. "what should I work on next,84 and is anything blocking it?" = `my_work` first, then `blockers` on85 the top candidate(s).867. **If a result contains `"error"`,** relay the tool's actual error87 text (or a faithful paraphrase of it) so the user knows exactly what88 Jira rejected -- don't retry silently, and don't invent a89 plausible-sounding cause you haven't actually confirmed from the90 JSON. A guessed explanation ("the parent didn't have a usable91 structure yet") is exactly the kind of fabrication rule 2 already92 forbids for issue data -- it applies just as much to explaining a93 failure.948. **Link issue keys, don't just print them.** Every tool that returns an95 issue (or a subtask, or a linked issue) includes a sibling `url` field96 (e.g. `issue.url`, `subtasks[].url`, `links[].related_url`) -- when you97 mention an issue key in prose, render it as a markdown link using that98 `url`, e.g. `[PAY-123](https://jira.example.com/browse/PAY-123)`, instead99 of a bare key. Never construct the URL yourself; only use the `url` a100 tool actually returned. This applies just as much to bulk/grouped101 output (a status-grouped list, a table, a summary line rolling up102 several keys) as it does to a single issue mentioned in a sentence --103 don't drop back to bare keys once you're listing many issues at once;104 link every one.1059. **Never write ad-hoc code -- neither to talk to Jira, nor to106 post-process a tool's output.** Every intention this skill needs to107 serve should be reachable by chaining the tools above -- `search`'s108 free-form `--jql` plus `search_users` for resolving a person's name is109 the intended escape hatch for requests that don't map to a single tool110 1:1 (e.g. "find John's tasks that I reported, due tomorrow"). Resolve111 names via `search_users`, resolve relative dates yourself, then build112 the JQL and call `search` -- don't write and run a new Python script113 against the Jira REST API to accomplish the same thing. This also114 covers sorting/filtering/picking-the-max out of a tool's own JSON115 result (e.g. "what's the last task I worked on"): use `--order_by`/116 `--max_results`/`--only` (`my_work`, `search`) to get exactly the117 answer back, or reason over the JSON yourself -- don't pipe a tool's118 output into a second interpreter (`python3 -c ...`, `jq`, etc.) to119 compute it. Besides being unnecessary, piping tool output straight120 into another interpreter is exactly the kind of command a security121 scanner (Hermes' included) will flag and block for approval. This122 also covers a project whose create/edit screen requires a field123 `create_issue`/`edit_issue` doesn't name directly (e.g. a required124 "Expected behavior" field on a Bug screen) -- that's what125 `--custom_fields` is for (a JSON object of `customfield_NNNNN ->126 value`, resolved via `list_fields`), not a reason to fall back to a127 hand-rolled script against the Jira REST API.12810. **Ask for only the fields you need.** `search`'s `--only` and129 `issue_summary`'s `--sections` let you name exactly what to fetch and130 get back, instead of everything. Default to the tool's default set for131 open-ended questions; narrow it (e.g. `--only summary,status,priority`,132 or `--sections issue`) once you know exactly which fields answer the133 question, especially over many issues at once -- this is the main134 lever for keeping bulk results token-cheap. `key`, `url`, and135 `custom_fields` are always present regardless of `--only`, and136 `blocked` is always computed for `search` even if `status`/`links`137 weren't explicitly requested.13811. **Never guess a JQL field name or literal value -- there are two139 different vocabularies, don't mix them up.** `--only`/`--sections`140 use this skill's own output names (`due_date`, `issue_type`);141 `--jql` uses Jira's own JQL field names, which are different:142 `due` (not `due_date`), `issuetype` (not `issue_type`), `reporter`143 for "who filed this" (not `creator` -- don't invent an alternate144 field name if a query using the documented one returns nothing).145 Use exactly the field names shown in this file's Examples --146 `reporter`, `assignee`, `status`, `due`, `priority`, `labels`,147 `resolution`, `updated`, `created`. Never write a status literal148 (e.g. `status = 'Pending'`) into JQL unless you've actually seen149 that exact status appear in a prior `my_work`/`search` result, in150 `project_context`'s `statuses`, or the user gave it to you -- a151 plausible-sounding guess doesn't error, it just silently returns152 zero misleading results. If a query built this way still returns153 nothing, don't silently swap in a different field name and retry154 blind -- tell the user exactly what you searched (state the JQL)155 and ask, or verify first (`project_context` for statuses/labels/156 users, `list_fields` for custom fields) rather than guessing your157 way through several variants.15812. **Remember a project's workflow instead of re-fetching it every159 turn -- save it the moment you learn it, don't wait to be asked.**160 `project_context --project X` returns a project's real `issue_types`,161 `statuses`, `components`, `priorities`, assignable `users`, and a162 sample of `labels` in one call -- this is the grounding source rule163 11 refers to. Call it once per project; if your runtime has a164 persistent-memory feature (something that survives past this turn or165 this conversation), **save the interesting parts as project-scoped166 facts immediately, as part of the same turn you learned them in** --167 not as a follow-up you do only if the user asks "do you have that168 remembered?" or "will you remember this?". Saving is not an optional169 courtesy triggered by the user checking on you; it's the same170 unprompted step as reporting the result itself. See `README.md`'s171 "Agent memory" section in this skill's directory for the exact172 catalog of what to save and where each fact comes from.173174 Use whatever you already know (freshly fetched or remembered) to175 catch a likely typo or mismatched term in what the user said (e.g.176 "pended" doesn't match any real status -- ask what they meant)177 instead of guessing blind. **This same remember-immediately178 discipline applies to a project's board type** (Scrum, with sprints,179 vs. Kanban, without) -- the instant `sprint`, `kanban_status`, or a180 `transition` error's list of available statuses tells you which one181 a project is (or what its real statuses are), save that fact too182 (e.g. "PAY: kanban, no sprints, statuses: To Do/In Progress/Review/183 Done") in that same turn, and use it to decide which command to call184 next time, straight away -- a project doesn't switch board types185 turn to turn, so there's no reason to ever call `sprint` again on186 that project just to (re-)detect it. This doesn't mean skipping the187 actual call, though: the sprint's dates/goal, or the kanban board's188 column counts, are live data you fetch fresh every time -- only the189 *type*, not the *content*, comes from memory. This memory is190 self-learning: every call that reveals a new fact about a project191 (workflow, team, labels, board type) is a chance to add to it in that192 same turn, not just consult it.19313. **Format every response for fast skimming, using the templates194 below.** This applies in every runtime you might be running in, not195 just one particular chat surface -- it's plain markdown plus emoji,196 which renders the same everywhere. Lead with the answer, not a197 preamble; put a one-line summary first (counts, the headline fact),198 then supporting detail below it, so the gist is visible without199 reading the whole message. One issue per line. Every group/section200 gets exactly one leading emoji as a visual anchor for the message201 *kind* (pick one consistently per kind of question, e.g. 📋 for a202 list, 🎫 for one issue, 👉 for a recommendation) -- don't invent a new203 emoji vocabulary per response or reuse one emoji for different204 meanings across responses. The only *data-driven* icon is priority205 (🔴 High/Highest, 🟡 Medium, 🟢 Low/Lowest) -- never invent a206 status-to-emoji mapping, since status names and their meaning differ207 per project's workflow (rule 11); a status name is just bolded plain208 text as a group header.209210 Grouped/bulk list (`search`, `triage`, `my_work` with several results):211 ```212 📋 <project> — <what this is> (<total count>)213 <n> active · <n> in review · <n> backlog unassigned <- whatever counts matter here214215 <Status name> (<count>)216 🔴 [KEY](url) <summary> — <assignee>217 🟡 [KEY](url) <summary> — <assignee>218219 <Next status name> (<count>)220 ...221 ```222223 Single issue (`issue_summary`, `blockers`, `get_issue`):224 ```225 🎫 [KEY](url) — <summary>226 🔴 <priority> · <status> · <assignee>227228 🔗 Blocked by [KEY](url) (<its status>) <- only if actually blocked, rule 3229 ⏱ <logged> / <estimate> logged230 💬 "<latest comment text>" — <relative time>231 ```232 Omit any line above that doesn't apply (no blockers, no worklogs, no233 comments) rather than printing an empty or "none" line for each.234235 Recommendation (`my_work` reasoned into "what should I work on236 next"):237 ```238 👉 Do next: [KEY](url)239 <summary>240 <the 1-2 reasons -- priority, unblocked, staleness>241 ```242 A ranked runner-up list may follow underneath if useful, using the243 grouped-list line format above.244245 These are shapes to adapt, not rigid schemas -- use judgment on which246 fields matter for the question asked, but keep the one-summary-line-247 then-detail structure and the single-icon-per-role rule above.24814. **Stay scoped to the current project; ask before broadening.**249 `my_work`, `sprint`, and `kanban_status` all default to250 `JIRA_DEFAULT_PROJECT` (or an explicit `--project`) rather than251 searching instance-wide -- don't pass `--all_projects` (`my_work`) or252 an unscoped `search`/`search_users` call just because a scoped result253 looks short or empty. If a scoped result is genuinely empty or254 doesn't answer the question, tell the user what you searched (state255 the project and query) and ask whether to broaden, rather than256 silently retrying wider or mixing in other projects' issues. This257 also means: don't assume every project is a Scrum board with sprints258 -- a project can be Kanban-only (`sprint` returns `"note"` saying so259 when it detects this; use `kanban_status` for that board's real260 state instead), so a missing/ended sprint on the default project261 isn't evidence there's no work to report, it's evidence to check262 `kanban_status` or a plain `my_work`/`search` instead.26315. **A user's own remembered workflow conventions extend a write --264 check for one before treating the literal request as done.** A265 team's process (e.g. "every task also gets a specific kind of266 subtask" or "issues of a certain type always get a certain label")267 is never something this toolset invents, assumes, or hardcodes --268 these skills are generic tools, not one team's process. It only269 exists if the user has explicitly told you to remember it. But once270 they have, it's a standing instruction, not a one-off: before you271 consider a `create_issue`/`edit_issue`/`transition` request272 complete, check whether a remembered convention applies to it, and273 if so fold it into the same request -- state the *whole* resulting274 set of actions (e.g. "create PAY-200, then its usual Sub-task, per275 your convention -- confirm?") before running anything, rather than276 silently doing only the literal single action asked, or silently277 doing more than asked without saying so. If you're ever unsure278 whether a convention still applies (the user hasn't mentioned it in279 a while, or this request looks like an exception), ask rather than280 guess either way.281282## Commands283284```bash285# Unresolved issues assigned to the current user. Scoped to --project (or286# JIRA_DEFAULT_PROJECT) by default (rule 14) -- pass --all_projects only if287# the user asked to broaden. --order_by is a JQL ORDER BY clause (default:288# "priority DESC, updated DESC"); --max_results caps how many come back.289# Use these instead of fetching everything and post-processing yourself --290# e.g. "what's the last task I worked on" is --order_by "updated DESC"291# --max_results 1, not a second script.292python3 scripts/jira_tool.py my_work [--project PAY] [--all_projects] \293 [--order_by "updated DESC"] [--max_results 1]294295# Full context for one issue: fields, comments, worklogs, changelog, links.296# --sections limits which parts to fetch/return (default: all)297python3 scripts/jira_tool.py issue_summary --issue_key PAY-123 [--sections issue,worklogs]298299# Blocking status + reasons for one issue300python3 scripts/jira_tool.py blockers --issue_key PAY-123301302# Arbitrary JQL search. --only asks for exactly the named fields you need303# instead of everything (default: everything except description and304# time-tracking fields); "blocked" is always computed and returned.305# --only's names and --jql's field names are DIFFERENT vocabularies --306# see rule 11 (e.g. --jql uses "due", --only uses "due_date")307python3 scripts/jira_tool.py search --jql "assignee = currentUser() AND updated <= -14d" \308 [--fields customfield_10056] [--only summary,status,priority]309310# Enumerate every field (incl. custom fields) to discover a custom field's id by name311python3 scripts/jira_tool.py list_fields312313# Current local wall-clock time. No Jira call. Run this before resolving314# any relative date ("now", "yesterday", "last Tuesday") for a write --315# your own sense of the time is often stale or absent (rule 5). Its "now"316# field is a full ISO timestamp that --date accepts verbatim.317python3 scripts/jira_tool.py now318319# Reference snapshot of a project: issue types, workflow statuses (overall320# and per issue type), components, instance priorities, assignable users,321# and a sample of labels in use -- call once per project, remember the322# result if you can (rule 12), don't re-fetch every turn323python3 scripts/jira_tool.py project_context [--project PAY]324325# Look up a user by name/email fragment, to get an account_id for JQL326# assignee filters or create_issue/edit_issue's --assignee_account_id.327# --project scopes to that project's assignable users (narrower, resolves328# common-name collisions) -- falls back to JIRA_DEFAULT_PROJECT, then329# an unscoped instance-wide search330python3 scripts/jira_tool.py search_users --query john [--project PAY] [--all_projects]331332# Active sprint / board / dates / goal. Board is resolved scoped to333# --project (or JIRA_DEFAULT_PROJECT) by default (rule 14). If the334# resolved board is kanban (no sprints), "sprint" comes back null with a335# "note" pointing at kanban_status instead.336python3 scripts/jira_tool.py sprint [--project PAY] [--board_id 42]337338# Kanban board's columns and per-column issue counts -- the kanban339# equivalent of "sprint" for boards with no active sprint. Board scoped340# the same way as sprint (rule 14).341python3 scripts/jira_tool.py kanban_status [--project PAY] [--board_id 42]342343# Your logged time over a date range, vs. each issue's original estimate344python3 scripts/jira_tool.py worklog_report --since -14d [--until 2026-07-20] [--max_issues 50]345346# Log time (write, gated -- see rule 5); --date defaults to now, accepts347# a relative offset ("-1d"), ISO date, or ISO datetime -- resolve348# relative day-names to an actual date yourself first (rule 5)349python3 scripts/jira_tool.py worklog --issue_key PAY-123 --duration 2h \350 --description "implementing validation" [--date 2026-07-20] --confirm351352# Move to a status (write, gated -- see rule 5). --status matches353# case-insensitively against real transition/status names, with a354# substring fallback ("done" -> "Done") -- pass the user's own word355# through directly instead of asking them for Jira's exact status name356# or checking project_context/kanban_status first; if it doesn't match,357# the error itself lists every real available transition to retry with.358python3 scripts/jira_tool.py transition --issue_key PAY-123 --status Review --confirm359360# Fix a worklog's duration/description/date (write, gated -- see rule 5);361# find --worklog_id via issue_summary's worklogs[].id362python3 scripts/jira_tool.py worklog_edit --issue_key PAY-123 --worklog_id 28459 \363 [--duration 2h] [--description "..."] [--date 2026-07-20] --confirm364365# Permanently delete a worklog entry (write, gated, irreversible -- see rule 5)366python3 scripts/jira_tool.py worklog_delete --issue_key PAY-123 --worklog_id 28459 --confirm367368# Group unresolved stories/bugs/tasks with their labeled subtasks, for369# frontend/backend/design-readiness triage -- --project falls back to370# JIRA_DEFAULT_PROJECT if omitted371python3 scripts/jira_tool.py triage [--project PAY] [--parent_issue_types Story,Bug,Task]372373# Create a new issue or subtask (write, gated -- see rule 5); pass374# --issue_type Sub-task and --parent_key for a subtask, same tool either way.375# --custom_fields is a JSON object of customfield_NNNNN -> value, for any376# field the project's screen requires beyond the named flags above --377# resolve ids/shapes via list_fields first, never guess either (rule 9).378python3 scripts/jira_tool.py create_issue --project PAY --summary "Fix checkout crash" \379 --issue_type Bug [--description "..."] [--parent_key PAY-100] [--labels Frontend,UX] \380 [--assignee_account_id ...] [--priority High] [--components API] \381 [--custom_fields '{"customfield_10201": "..."}'] --confirm382383# Update fields on an existing issue or subtask (write, gated -- see rule 5)384python3 scripts/jira_tool.py edit_issue --issue_key PAY-123 \385 [--summary "..."] [--description "..."] [--labels Frontend] \386 [--assignee_account_id ...] [--priority High] [--components API] \387 [--custom_fields '{"customfield_10201": "..."}'] --confirm388```389390## Examples391392**"What should I work on next?"**393Run `my_work` (scoped to `JIRA_DEFAULT_PROJECT`/`--project` by default,394rule 14). Reason over the returned `issues` (priority, status, `blocked`,395staleness) and recommend the best candidate using the recommendation396template (rule 13) -- lead with the pick and why, don't just dump the JSON397or bury the answer at the end of a ranked list. If `issues` is empty,398don't assume there's nothing to do -- check `sprint` for that project: if399its `"note"` says the board is kanban, or the sprint has ended, that's400not "no work", it's a reason to check `kanban_status` (or a plain401`my_work`/`search`) instead of telling the user they're done.402403**"What's the last task I worked on?"**404Run `my_work --order_by "updated DESC" --max_results 1` -- it comes back405sorted with exactly the one issue you need, no further sorting or406scripting required (rule 9). Report it using the single-issue template407(rule 13).408409**"What should I work on tomorrow?" (default project is kanban)**410Run `my_work` (rule 14 keeps it scoped to `JIRA_DEFAULT_PROJECT`). Don't411also reach for `sprint` first just because "tomorrow" sounds like a412planning question -- kanban projects have no sprints, so a "sprint413ended"/`null` result from `sprint` is expected there, not a sign414something's wrong or that `my_work` will come up empty too. If `my_work`415does come back empty, check `kanban_status` for that project's real board416state before concluding there's nothing to do.417418**"What's blocking my board right now?" / "How many tickets are in review?"**419Run `kanban_status [--project PAY]`. Report `issue_counts_by_column`420against the board's real `columns` -- never assume a generic To Do/In421Progress/Done set; use the names the board actually returned.422(The `jira-board` thin skill packages this same memory-first check --423consult what you already know about this project's board type before424calling anything, per rule 12 -- as its own dedicated command, for a425caller that doesn't know up front whether a project is Scrum or Kanban.)426427**"Show me the backend tasks, grouped by status."**428Run `search --jql 'project = PAY AND labels = "backend"' --only429status,assignee,priority,summary`. The grouping itself is just reading the430returned list and organizing it by each issue's `status` field into the431grouped-list template (rule 13) -- one summary line up top, one status432group per section, every key linked (rule 8) -- reason over the JSON433directly, per rule 9. Never write a Python/jq script (piped, heredoc, or434`-c`) to sort/group/tabulate a result you already have in hand; besides435being unnecessary, that class of command is exactly what a security436scanner (Hermes' included) flags and blocks for approval.437438**"What's blocking PAY-412?"**439Run `blockers --issue_key PAY-412`. If `blocked: true`, summarize440`reasons` using the single-issue template's 🔗 line (rule 13). If441`false`, say nothing is blocking it.442443**"Summarize PAY-412."**444Run `issue_summary --issue_key PAY-412`. Produce a concise summary using445the single-issue template (rule 13): status/priority/assignee up top,446then only the sections that actually apply (blockers, worklogs, latest447comment) -- not a full dump of every field.448449**"Log 2h on PAY-412 for implementing validation."**450First confirm with the user ("I'll log 2h on PAY-412: 'implementing451validation' — confirm?"), then run `worklog ... --confirm`.452453**"Log 4h30m on PAY-412 for last Tuesday."**454Run `now` to get today's real date/weekday, resolve "last Tuesday"455against it, then confirm with the user including that resolved date456("I'll log 4h 30m on PAY-412 dated 2026-07-20 — confirm?"), then run457`worklog --issue_key PAY-412 --duration 4h30m --description "..." --date 2026-07-20 --confirm`.458Never omit `--date` when the user specified a day other than today --459omitting it logs against right now, silently on the wrong day.460461**"That worklog is on the wrong day, it should be Tuesday not Thursday."**462Find the worklog's id (via `issue_summary`'s `worklogs[].id`, or from463the id a prior `worklog` call returned), resolve "Tuesday" to an actual464date, confirm with the user, then run `worklog_edit --issue_key ... --worklog_id ... --date 2026-07-20 --confirm`.465Don't create a new worklog and leave the wrong one in place -- edit the466existing entry, or delete-and-recreate only if the user asks for that467specifically.468469**"Delete that worklog, I logged it by mistake."**470Confirm exactly which entry (issue, duration, date) before deleting --471this is irreversible -- then run `worklog_delete --issue_key ... --worklog_id ... --confirm`.472(The `jira-log` thin skill packages this same log-vs-edit-vs-delete473routing as its own dedicated command, for a caller that just wants "the474worklog skill" without picking the exact sub-action itself.)475476**"I'm starting on the export logs now."** ... later ... **"two bugs came477in, took an hour total."** ... later ... **"after that, 3h on the logs."**478The user is narrating a day rather than naming one worklog. Run `now`479when work starts (you don't otherwise know the time) and keep the480running timeline in your replies -- restating it compactly each turn is481what carries it forward. Work described in past tense with a duration482("took an hour") is a finished stretch that *interrupted* something, so483it never overlaps the surrounding task and the interrupted task isn't484credited that hour: here the logs task ends at 3h, not 4h. A stated485duration beats one inferred from the clock. At the end, group by issue,486show the whole breakdown, resolve anything not yet tied to a real key,487then confirm and log one issue at a time with `--date` set to that488stretch's actual start timestamp. The `jira-track` thin skill packages489this whole flow as its own dedicated command.490491**"Move PAY-412 to Review."**492Confirm with the user, then run `transition --issue_key PAY-412 --status Review --confirm`.493494**"I merged it, mark PAY-412 as done."** -- colloquial wording, not a495quoted status name.496Confirm with the user, then run `transition --issue_key PAY-412 --status497done --confirm` directly -- `--status` matching is case-insensitive with498a substring fallback, so the user's own word usually resolves on its499own. Don't ask them what the exact status name is, and don't spend a500call on `project_context`/`kanban_status` just to look this up first --501that's slower and less token-efficient than just trying it. Only if the502result errors with "does not match any available transition" should you503act: that error already lists every real transition/status for the504issue, so read the right one out of it and retry, rather than making a505separate lookup call.506507**"Which of my tickets haven't been updated recently?"**508Run `search --jql "assignee = currentUser() AND resolution = Unresolved AND updated <= -14d"`.509510**"How many hours have I logged in the last two weeks?"**511Run `worklog_report --since -14d` and report `total_logged_seconds` (converted512to hours) -- don't estimate from memory.513514**"How much more than the estimate did I work?"**515Run `worklog_report` for the relevant window and report `total_delta_seconds`516(`total_logged_seconds - total_original_estimate_seconds`), plus the517worst-offending issues from the `issues` list (their own `delta_seconds`).518Note that `original_estimate_seconds` is `null` for any issue with no519estimate set -- exclude those from an "over/under estimate" claim rather520than treating a missing estimate as zero.521522**"What did I get stuck on recently?"**523Run `worklog_report` and reason over each issue's `logged_seconds` (vs. its524`original_estimate_seconds`) and its worklogs' `comment` text -- don't just525list the top issue by hours, actually read what the comments say happened.526527**"Which tasks have a Figma link?" (design ready)**528Run `list_fields` once, find the field whose `name` matches "Figma" (try529"Figma", "Figma Link", "Design Link" -- the exact label varies per530instance), then `search --jql "..." --fields <that id>` and check531`custom_fields.<that id>` on each issue for a non-empty value. Never532guess a `customfield_NNNNN` id without confirming it via `list_fields`.533534**"Which tasks are ready for dev?"**535This is almost always a status name, not a special tool -- run536`search --jql "status = 'Ready for Dev'"` (confirm the exact status name537against the project's workflow first if unsure -- check `project_context`'s538`statuses` if you already have it for this project, otherwise fetch it,539per rules 11-12).540541**"What statuses/labels does this project use?" / "Who's on this project?"**542Run `project_context --project PAY` (falls back to `JIRA_DEFAULT_PROJECT`).543Report `statuses`/`statuses_by_issue_type`, `labels` (note it's a sample544from unresolved issues, not exhaustive -- say so if asked "all labels"),545or `users` directly as fact. Remember the result per rule 12 rather than546calling this again later in the same project unless you have reason to547think it changed.548549**"Which task has no log that I should log?"**550Run `search --jql "assignee = currentUser() AND resolution = Unresolved AND timespent is EMPTY"`.551552**"Which tasks don't have subtasks yet?" / "Which need backend vs frontend work?" / "Which stories need triage?"**553Run `triage [--project PAY]` (falls back to `JIRA_DEFAULT_PROJECT` if you554omit `--project`; resolve a project yourself first if neither is set --555see `jira-triage`'s SKILL.md). For each returned story: if556`has_frontend_subtask`/`has_backend_subtask` are already known (i.e.557`needs_triage` is `false`), report them as fact. If `needs_triage` is558`true` (no subtasks yet), infer frontend/backend/design needs from559`description`, falling back to `summary` if there's no description --560and if neither gives enough signal, tell the user this story doesn't have561enough information to suggest subtasks rather than guessing. Always562caveat an inferred verdict as inferred, never state it as fact the way563`has_frontend_subtask`/`has_backend_subtask` are.564565**"Based on the description, which tasks need backend or frontend?" (ad hoc, no subtask structure yet)**566If the user just wants a one-off read of a few issues rather than a full567triage sweep, `search --only summary,description` (description is omitted568by default, see rule below) or `issue_summary` per issue is fine -- reach569for `triage` instead when the question is really about the Frontend/Backend570subtask workflow across many stories.571572**"Find John's tasks that I reported, due tomorrow."** (ad hoc, no dedicated tool)573Per rule 9, chain `search_users` + `search` rather than writing new code --574but check remembered/already-fetched `project_context` first (rule 12): if575you already know this project's `users` and "John" unambiguously matches576one, use that `account_id` directly and skip `search_users` entirely.577Otherwise run `search_users --query john` (add `--project` if a project is578already known/relevant -- narrows the match and often resolves a579common-name collision on its own). If the scoped search comes back with580`count: 0`581(check the result's `project` field, not just what you passed, since582`JIRA_DEFAULT_PROJECT` may have applied silently), don't conclude there's583no such user -- ask the user whether to broaden to an instance-wide search584and only then retry with `--all_projects` (omitting `--project` isn't585enough to bypass `JIRA_DEFAULT_PROJECT`). If still ambiguous after a586scoped attempt, ask the user to disambiguate. Once resolved, resolve587"tomorrow" to an actual calendar date yourself, then run588`search --jql "assignee = <account_id> AND reporter = currentUser() AND due = <date>"`589-- note `reporter` and `due`, per rule 11: don't drift to `creator` or590`due_date` if this returns nothing, that's guessing, not verifying.591592**"Create a bug for the checkout crash."**593Confirm the project, summary, and issue type with the user, then run594`create_issue --project PAY --summary "Checkout crash" --issue_type Bug --confirm`.595596**"Add a frontend subtask under PAY-100 for the checkout UI."**597Same tool as above, scoped to a subtask: confirm with the user, then run598`create_issue --project PAY --summary "Checkout UI" --issue_type Sub-task --parent_key PAY-100 --labels Frontend --confirm`.599600**"Reassign PAY-123 to John."**601Resolve John to an `account_id` via `search_users` first (ask if there's602more than one match), confirm with the user, then run603`edit_issue --issue_key PAY-123 --assignee_account_id <account_id> --confirm`.604605**Creating a Bug fails with "Expected behavior/Actual behavior/Steps to606reproduce are required."** These are this project's own custom fields on607the Bug screen, not something `--description` covers. Run `list_fields`608to find their real `customfield_NNNNN` ids (never guess them from the609label), ask the user for each value if they haven't already given them,610then pass all three as one `--custom_fields` JSON object alongside the611rest, e.g. `create_issue --project PAY --summary "..." --issue_type Bug612--custom_fields '{"customfield_10201": "...", "customfield_10202":613"...", "customfield_10203": "..."}' --confirm` -- not a fallback to a614hand-rolled script against the Jira API (rule 9).615616## Reference617618See `README.md` in this skill directory for architecture details, the619full environment-variable table, and how to run the test suite620(`pytest`, covering the client, config validation, and every tool's621success/error/confirmation paths).