jira-syntax
Use when writing or formatting Jira descriptions, comments, or any text destined for Jira. Converts Markdown to Jira wiki markup, provides templates (bug reports, feature requests), and validates syntax before submission. Trigger on any Jira content authoring task.
Install
npx skills add https://github.com/netresearch/jira-skill/tree/main/skills/jira-syntax
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install netresearch-jira-skill@llmmart
git clone https://github.com/netresearch/jira-skill.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole netresearch/jira-skill collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Jira Syntax
Jira wiki markup syntax, templates, and validation. For API operations, use the jira-communication skill.
Quick Syntax Reference
| Jira Syntax | Purpose | NOT this (Markdown) |
|---|---|---|
h2. Title |
Heading | ## Title |
*bold* |
Bold | **bold** |
_italic_ |
Italic | *italic* |
{{code}} |
Inline code | `code` |
{code:java}...{code} |
Code block | java ``` |
[text\|url] |
Link | [text](url) |
[PROJ-123] |
Issue link | - |
[~username] |
User mention | @username |
* item |
Bullet list | - item |
# item |
Numbered list | 1. item |
\|\|Header\|\| |
Table header | \|Header\| |
See references/jira-syntax-quick-reference.md for complete syntax documentation.
Available Templates
Bug Report
Path: templates/bug-report-template.md
Sections: Environment, Steps to Reproduce, Expected/Actual Behavior, Error Messages, Technical Notes
Feature Request
Path: templates/feature-request-template.md
Sections: Overview, User Stories, Acceptance Criteria, Technical Approach, Success Metrics
Syntax Validation
Run before submitting to Jira:
${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh path/to/content.txt
It gates the post — run it as its own step, never chained with the posting command.
Validation Checklist
- Headings:
h2. Title(space after period) - Bold:
*text*(single asterisk) - Code blocks:
{code:language}...{code} - Lists:
*for bullets,#for numbers - Links:
[label|url]or[PROJ-123] - Tables:
||Header||and|Cell| - Colors:
{color:red}text{color} - Panels:
{panel:title=X}...{panel}
Common Mistakes
| ❌ Wrong | ✅ Correct |
|---|---|
## Heading |
h2. Heading |
**bold** |
*bold* |
`code` |
{{code}} |
[text](url) |
[text\|url] |
- bullet |
* bullet |
h2.Title |
h2. Title |
MR !42 (bare GitLab ref) |
[MR 42\|url] or full group/project!42 — a bare !…! is image markup |
(/) on an open/proposed item |
(x) — (/) renders as a green check (done); use (x) for open items |
( ) as a checkbox |
(x) — ( ) is not a macro and renders literally |
(-) or (x) on a pending step in a review |
(i) pending: … — (-) renders as a red no-entry sign, and in a review (x) means a failure |
Integration with jira-communication Skill
Workflow:
- Get template from jira-syntax
- Fill content using Jira wiki markup
- Validate with
${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh - Submit via jira-communication skill
References
references/jira-syntax-quick-reference.md- Complete syntax documentation Includes Ask the renderer instead of reasoning about it:POST /rest/api/1.0/renderreturns the HTML Jira will produce for any markup, before anything is posted. Use it to settle a markup question rather than deriving the answer.references/cross-project-refs.md- GitLab cross-project ref convention (group/project!N,group/project#N,group/project@tag) when linking to GitLab from Jiratemplates/bug-report-template.md- Bug report templatetemplates/feature-request-template.md- Feature request template${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh- Automated syntax checker- Official Jira Wiki Markup
Files (jira-skill)
-
evals
-
evals.json 2.5 KB
[ { "name": "convert_markdown_to_jira", "prompt": "Convert this Markdown to Jira wiki format: **bold** text with [link](https://example.com) and `code`", "assertions": [ { "type": "content", "pattern": "\\*bold\\*" }, { "type": "content", "pattern": "\\[link\\|https://example\\.com\\]" }, { "type": "content", "pattern": "\\{\\{code\\}\\}" }, { "type": "not_content", "pattern": "\\*\\*bold\\*\\*" } ] }, { "name": "bug_report_with_table", "prompt": "Write a Jira bug report description with a table of reproduction steps (Step, Action, Expected, Actual) for a file upload bug", "assertions": [ { "type": "content", "pattern": "\\|\\|.*Step.*\\|\\|" }, { "type": "content", "pattern": "h[1-3]\\." }, { "type": "not_content", "pattern": "^#{1,3} " }, { "type": "not_content", "pattern": "\\*\\*" } ] }, { "name": "format_code_block_in_jira", "prompt": "Show me how to include a Java stack trace in a Jira comment with proper formatting", "assertions": [ { "type": "content", "pattern": "\\{code(:java)?\\}" }, { "type": "not_content", "pattern": "```" } ] }, { "name": "escape_literal_block_tag_mention", "prompt": "Write a short Jira QA comment in wiki markup with one finding line stating that all commands are documented in {code} blocks with command and output together, plus one real code block containing: ls -l", "assertions": [ { "type": "content", "pattern": "\\\\\\{code\\\\\\}" }, { "type": "not_content", "pattern": "\\{code\\}[^\\n]*[A-Za-z]" } ] }, { "name": "pending_step_marker_in_review", "prompt": "Write a short Jira QA review comment in wiki markup with two status lines. Use a status icon on each line: the GitLab service update is done, and the runner manager update is still pending until the service is verified.", "assertions": [ { "type": "content", "pattern": "\\(/\\)[^\\n]*[Ss]ervice" }, { "type": "content", "pattern": "\\(i\\)[^\\n]*[Pp]ending" }, { "type": "not_content", "pattern": "\\(-\\)" }, { "type": "not_content", "pattern": "\\(x\\)[^\\n]*[Pp]ending" } ] } ]
-
-
references
-
cross-project-refs.md 2.4 KB
# Cross-Project References to GitLab Resources When referencing GitLab merge requests, issues, tags, or commits **from a Jira description, comment, or worklog**, prefer the GitLab cross-project autolink syntax — even though Jira itself doesn't autolink GitLab paths, the cross-project form removes ambiguity for human readers and matches the convention used in GitLab itself. ## Convention | GitLab resource | Short form (single-project context) | Cross-project form (use this in Jira) | |---|---|---| | Merge request | `!42` | `group/project!42` | | Issue | `#123` | `group/project#123` | | Tag / commit / branch | `vX.Y.Z` | `group/project@vX.Y.Z` | The same prefix characters (`!`, `#`, `@`) are what GitLab itself recognises for cross-project autolinks in markdown, commit messages, and MR descriptions — so a reader who pastes the reference into a GitLab UI will get a working link automatically. ## In Jira wiki markup Wrap the cross-project text inside the standard Jira link syntax `[text|url]`: ``` [jira/jira!25|https://git.netresearch.de/jira/jira/-/merge_requests/25] [jira/jira@v9.12.3-2|https://git.netresearch.de/jira/jira/-/tags/v9.12.3-2] [jira/jira#42|https://git.netresearch.de/jira/jira/-/issues/42] ``` ## Why not just `!25`? A bare `!25` in a Jira issue forces the reader to guess which GitLab project it lives in — and to *click* to find out. A bare `v9.12.3-2` is even worse: tag names are reused across many repos. When an issue references multiple repos in the same group (e.g. `jira/jira` for the image build and `jira/app` for the deploy stack), the cross-project form is essential — `!3` could be either repo's MR. ## Inside `{{...}}` monospace `!`, `#`, and `@` all work inside `{{...}}`, but **unescaped `{`, `}` and `*` inside a `{{...}}` block break Jira's parser**: a raw `{` makes the whole block render as raw text, and a `*` pair turns bold mid-token. Escape them with a backslash (verified against the Jira Server 9.12 wiki renderer). Broken — the block falls apart on the raw `{`, and `backup-` renders bold: ``` {{compose.example.{yml,override.pga.yml}}} {{jira-*backup-*}} ``` Correct — renders `compose.example.{yml,override.pga.yml}` and `jira-*backup-*` in monospace: ``` {{compose.example.\{yml,override.pga.yml\}}} {{jira-\*backup-\*}} ``` Splitting into separate `{{...}}` references also works when escaping would hurt readability. The `validate-jira-syntax.sh` script catches the brace collision. -
jira-syntax-quick-reference.md 16.8 KB
# Jira Wiki Markup Syntax - Quick Reference Complete reference for Jira's wiki markup syntax to ensure proper formatting in tickets, comments, and descriptions. ## Table of Contents - [Text Formatting](#text-formatting) - [Headings](#headings) - [Lists](#lists) - [Links](#links) - [Code Blocks](#code-blocks) - [Tables](#tables) - [Panels and Quotes](#panels-and-quotes) - [Colors](#colors) - [Special Blocks](#special-blocks) - [Line Breaks and Horizontal Rules](#line-breaks-and-horizontal-rules) - [Special Characters](#special-characters) - [Checklist Markers](#checklist-markers) - [Validation Checklist](../SKILL.md#validation-checklist) - [Common Mistakes](../SKILL.md#common-mistakes) ## Text Formatting | Syntax | Output | Use Case | |--------|--------|----------| | `*text*` | **text** | Bold/strong emphasis | | `_text_` | *text* | Italic/emphasis | | `{{text}}` | `text` | Monospace for code/paths | | `-text-` | ~~text~~ | Strikethrough | | `+text+` | <u>text</u> | Underline/inserted text | | `^text^` | text^superscript^ | Superscript | | `~text~` | text~subscript~ | Subscript | | `??text??` | text (citation) | Citation format | ## Headings ``` h1. Heading Level 1 (largest) h2. Heading Level 2 h3. Heading Level 3 h4. Heading Level 4 h5. Heading Level 5 h6. Heading Level 6 (smallest) ``` Space required after the period (`h2. Title`, not `h2.Title`). ## Lists ### Bulleted Lists ``` * Level 1 item ** Level 2 nested item *** Level 3 nested item * Another level 1 item ``` ### Numbered Lists ``` # First item ## Nested item ## Another nested item # Second item ``` ### Mixed Lists ``` # Numbered item #* Nested bullet #* Another bullet # Another numbered item ``` **Rules:** - Space after `*` or `#` - Nesting uses additional symbols (`**`, `##`), not indentation - Can mix list types with combined syntax (`#*`) ## Links | Type | Syntax | Example | |------|--------|---------| | Issue Link | `[KEY-123]` | `[PROJ-456]` | | User Mention | `[~username]` | `[~john.doe]` | | External URL | `[http://url]` | `[http://example.com]` | | Labeled Link | `[Label\|url]` | `[Google\|http://google.com]` | | Attachment | `[^filename]` | `[^screenshot.png]` | | Email | `[mailto:email]` | `[mailto:team@example.com]` | | Anchor | `{anchor:name}` + `[#name]` | `{anchor:intro}` → `[#intro]` | ## Code Blocks ### Inline Code Use `{{text}}` for inline monospace text. ### Code Blocks with Syntax Highlighting ``` {code:java} System.out.println("Hello, World!"); {code} ``` **Supported Languages (Jira Server / Data Center):** The Jira Server source-code formatter accepts ONLY this fixed list. Using any other identifier (e.g. `typoscript`, `rust`, `typescript`, `yml`, `shell`) produces: > Unable to find source-code formatter for language: `<name>`. Available languages are: ... | Group | Identifiers | |-------|-------------| | General-purpose | `actionscript`, `ada`, `applescript`, `c`, `c#`, `c++`, `cpp`, `erlang`, `go`, `groovy`, `haskell`, `java`, `javascript`, `js`, `lua`, `objc`, `perl`, `php`, `python`, `r`, `ruby`, `scala`, `swift`, `visualbasic` | | Shell / scripting | `bash`, `sh` | | Data / markup | `css`, `html`, `json`, `sql`, `xml`, `yaml` | | Special | `none` (no highlighting), `nyan`, `rainbow` | **Notes:** - Use `c#` / `c++` literally, not `csharp` / `cplusplus` (though `cpp` is also accepted). - There is no `typescript`, `rust`, `kotlin`, `dart`, `powershell`, `shell`, `yml`, `dockerfile`, `terraform`, or `typoscript` formatter. - For unsupported languages, fall back to `{code:none}` (or `{noformat}`) to preserve the block without highlighting. ``` {code:none} [request && request.getNormalizedParams().getHttpHost() == "backend.example.de"] page.meta.robots = noindex,noarchive [END] {code} ``` ### Preformatted Text (No Highlighting) ``` {noformat} Plain text without syntax highlighting Preserves whitespace and formatting {noformat} ``` ### Block Tags Are Never Inline `{code}`, `{noformat}`, `{quote}` and `{panel}` are **block-level** macros: the tag must stand alone on its own line. An unescaped tag inside a prose sentence opens a real block mid-line and swallows the rest of the line — the classic failure is writing *about* code blocks. Broken — renders everything after "in" as an opened code block: ``` All commands are documented in {code} blocks with output. ``` Correct — renders the literal text `{code}`: ``` All commands are documented in \{code\} blocks with output. ``` Escape literal mentions of any block tag with backslashes: `\{code\}`, `\{noformat\}`, `\{quote\}`, `\{panel\}`. For inline monospace use `{{...}}`, never an inline `{code}` pair — even `{code}one-liner{code}` renders as a block, not inline. `scripts/validate-jira-syntax.sh` flags inline block tags and unbalanced tag counts; run it on composed text before submitting to Jira. ## Tables ### Basic Table ``` ||Header 1||Header 2||Header 3|| |Cell A1|Cell A2|Cell A3| |Cell B1|Cell B2|Cell B3| ``` **Rules:** - `||` for header cells (double pipe) - `|` for regular cells (single pipe) - Rows must have same number of cells - No trailing pipe at end of row ## Panels and Quotes ### Panel with Title and Background ``` {panel:title=Important Information|bgColor=#FFFFCE} Content inside the panel {panel} ``` **Panel Parameters:** - `title=text` - Panel heading - `bgColor=#HEXCODE` - Background color - `borderStyle=solid|dashed` - Border style - `borderColor=#HEXCODE` - Border color - `titleBGColor=#HEXCODE` - Title background ### Quote Block ``` {quote} Multi-line quoted text Can span multiple paragraphs {quote} ``` ### Single Line Quote ``` bq. This is a block quote on one line ``` ## Colors ``` {color:red}Red text{color} {color:blue}Blue text{color} {color:green}Green text{color} {color:#FF0000}Hex color text{color} ``` Named colors (`red`, `blue`, `green`, `yellow`, `orange`, `purple`, `black`, `white`, `gray`/`grey`) or any hex code work as the macro parameter. ## Special Blocks ### Expand/Collapse Section ``` {expand:title=Click to expand} Hidden content that can be toggled {expand} ``` ## Line Breaks and Horizontal Rules ``` Line 1\\ Line 2 (line break with \\) First paragraph Second paragraph (blank line creates new paragraph) ---- Horizontal rule (4 dashes) ``` ## Special Characters ``` --- (em-dash: —) -- (en-dash: –) \\ (line break) \{escaped brace\} ``` To escape special characters, use backslash: `\*`, `\{`, `\[`. - **Only escape characters Jira actually parses as markup** — `*`, `_`, `-`, `+`, `^`, `~`, `{`, `[`, `|`, `\`. Do **not** escape plain punctuation such as `.`, `,`, or `:` — `\.` renders the backslash literally and produces the wrong output. - **Never escape inline monospace** — `{{text}}` is not a macro, so `\{\{text\}\}` is wrong. Only escape the opening brace of a *macro name* shown as prose (e.g. `\{code\}`). - **Preserve existing backslash escapes** — a source `\*`, `\_`, or `\{` already suppresses Markdown markup; keep it as-is, because Jira uses the same `\` escape mechanism for the same characters. ### Common gotcha: macro names in prose Writing a macro name literally in prose (e.g. *"commands wrapped in \{code\} blocks"*) without escaping breaks rendering — Jira parses the literal as the *start* of a code-block macro and either consumes the rest of the comment or pairs with the next unrelated occurrence it finds. The same trap applies to any macro that opens and closes with the same token: `{noformat}…{noformat}`, `{quote}…{quote}`, `{color}…{color}`, `{panel}…{panel}`, `{anchor}…{anchor}`, and so on. Three ways to write the literal token safely, in order of preference: | Approach | Example | When to use | |----------|---------|-------------| | Rephrase to avoid the token | `commands shown as code blocks` | First choice — readers don't need the macro name to understand the prose | | Backslash-escape | `\{code\}` | When you genuinely need to show the macro name | | Wrap in a code span | `` `{{code}}` `` | Last resort — some style guides ban inline `{{monospace}}` in favour of bold `*term*` for technical terms | The backslash escape is the official Jira mechanism; the rephrase is editorial; the `{{monospace}}` wrap renders fine but is disliked by teams that reserve monospace for actual code spans rather than inline references. ### Common gotcha: prose struck through by `-text-` `-text-` is strikethrough. The grammar below is measured against a live Jira Server 9.12 wiki renderer, and the recorded cases are kept in the source repo (netresearch/jira-skill), not in the standalone skill package; it is not a rule of thumb, and an earlier version of this section stated it wrongly in both directions. > **opener** — an unescaped `-` at line start or after a **non-word character**, followed by neither whitespace nor another dash > **closer** — the next *valid* closer: an unescaped `-` that is not preceded by whitespace and is followed by a non-word character or line end; a dash failing either condition is skipped over, not fatal > **body** — anything in between; a dash that fails the closer conditions is skipped over, not fatal Two consequences are worth stating on their own, because both are the opposite of what the shape suggests. **A pair of CLI flags is not a span — on its own.** `journalctl -b -p crit` renders literally, and so do `--strict ... -v` and `offset by -5 seconds`, because a dash that *leads* a word can never close a span, so flags cannot pair with each other. They are not immune, though: put a trailing-dash word anywhere later on the same line and the flag becomes the opener — `journalctl -b -p crit zeigt die Fehler; das Modul ist zu- und abschaltbar.` is struck from `-b` to `zu-`. **The real trap is a dash after an inline element, closed by a trailing-dash word.** Any inline element's closing punctuation — `}}`, `*`, `_`, `]`, `!`, `{color}` — is a non-word character, so `{{nr-pforum}}-Extensions` opens a span; a German elliptical compound (`zu- und abschaltbar`) or any other word ending in a dash then closes it, and everything between renders struck through. German prose produces this shape routinely. A single flag also becomes dangerous once such a closer appears later on the same line: `with -v and a trailing word- here`. Exempt, and not to be escaped: a dash inside a word (`Round-1`, `2026-09-04`, `Größe-x`), a leading dash with no closer anywhere on the line, em/en-dash typography (`---`, `--`), list bullets (`- item`), Unicode dashes (`–`, `—`), and anything inside `{code}`/`{noformat}`. `{quote}` and `{panel}` are **not** exempt — Jira parses text effects inside them. The fix is to backslash-escape the whole dash run that opens the span: `{{nr-pforum}}\-Extensions`. A `\-` reaches the rendered HTML as `-` and prints as a plain hyphen, so the reader sees no difference. Escaping only part of a run does not work: in `{{--strict}}` the opener is the *second* dash, and neutralising just that one promotes the first — write `{{\-\-strict}}`. Putting the command in a `{code}` block avoids the question entirely. You normally do not have to do any of this by hand. Every `jira-communication` option that writes wiki markup — `jira-comment.py add`/`edit`, `jira-transition.py do --comment`, `jira-transition.py path --comment`, `jira-worklog.py add --comment`, and the `--description` of `jira-create.py issue` and `jira-issue.py update` — escapes these spans automatically before posting and report on stderr which lines they changed (`--no-auto-escape` keeps the markup verbatim; posting a deliberate span needs `--no-auto-escape --force`, because the lint and the render check each still refuse it). For a draft that does not go through those scripts, run `skills/jira-syntax/scripts/validate-jira-syntax.sh <file>` on it (from the repo root). The script verifies that the six paired macros (`code`, `panel`, `color`, `noformat`, `quote`, `anchor`) are balanced — every opener matches a closer, even with a language tag like `{code:bash}` — and catches Markdown leakage (` ``` ` fences, `[text](url)` links, `` `code` `` spans), language declarations Jira Server does not recognise, malformed table headers, and dash pairs outside code blocks that would render struck through. ## Checklist Markers `(/)` and `(x)` are the conventional checklist markers: `(/)` for a completed item, `(x)` for an open one. Use them only with that meaning. - Do not put `(/)` on items that are merely proposed or not yet implemented. It renders as a green check and reads as "done". - `( )` (empty parentheses) is **not** a macro. It renders literally as two parentheses, so it conveys nothing. For an open item use `(x)`, or a plain bullet when no status is intended. ``` * (/) Migration script written and tested * (x) Rollback procedure documented ``` What each marker draws, from the render endpoint of Jira Server 9.12: | Markup | Icon | Reads as | | --- | --- | --- | | `(/)` | green check (`check.png`) | done | | `(x)` | red cross (`error.png`) | failed, or open in a plain checklist | | `(!)` | warning triangle (`warning.png`) | needs attention | | `(i)` | blue info (`information.png`) | note | | `(?)` | question mark (`help_16.png`) | unclear | | `(off)` | grey light bulb (`lightbulb.png`) | not applicable, inactive | | `(-)` | red no-entry sign (`forbidden.png`) | forbidden | In a review or QA comment, `(x)` already means a blocking failure, so it cannot also mean "not done yet". Mark a step that is still pending with `(i)` and the word "pending", or with a plain bullet. Never use `(-)` for it: the red no-entry sign reads as "must not be done". Three of seven tickets in one maintenance window marked pending steps that way. ``` * (/) Service updated to 19.4.1 * (i) pending: runner manager, after the service is verified ``` ## Ask the renderer instead of reasoning about it Jira renders wiki markup server-side, and it will tell you what it is going to do — for any markup, before anything is posted: ```bash curl -s -H "Authorization: Bearer $JIRA_PERSONAL_TOKEN" -H 'Content-Type: application/json' \ -X POST "$JIRA_URL/rest/api/1.0/render" \ -d '{"rendererType":"atlassian-wiki-renderer","unrenderedMarkup":"Die {{a}}-Extensions, jede zu- und abschaltbar","issueKey":null}' ``` This is the endpoint behind Jira's own preview button. It is Server/DC only (Cloud uses ADF and has no equivalent), it needs no issue, and it writes nothing. It rate-limits with HTTP 429 above roughly three to eight parallel requests, so a batch run needs backoff. **It is the same renderer that stores a comment.** Verified by rendering the full 1185-character source of an existing comment and diffing against that comment's stored `renderedBody` (`GET /rest/api/2/issue/<KEY>/comment/<id>?expand=renderedBody`) — byte-identical. So the preview is proof, not an approximation. Use it whenever a claim about Jira markup is about to be written down — in a lint, a ticket, a reference page like this one. It costs one call and it settles the question. Two successive hand-derived versions of the strikethrough rule in this repo were wrong in opposite directions, and the second passed 168 hand-picked cases while still being wrong; a generated corpus rendered through this endpoint found the defect in minutes. Two runners built on it — one to re-record the curated cases, one to generate and record a corpus — live in the source repo (netresearch/jira-skill), not in the standalone skill package; the curl above is the whole technique and needs neither. Two things it cannot settle, because they are not in the markup: - **Instance state.** Jira substitutes autolinked issue keys before text effects run, so `OPS-899-x ... zu-` renders struck through where OPS-899 exists and literally where it does not. Render against the instance you will post to. - **What the markup was meant to say.** The renderer answers "what will this look like", never "is this what you wanted". ## Validation is a gate, not a formality `scripts/validate-jira-syntax.sh` only helps if its **result** is read before the content is posted. Run it as its own step: ```bash validate-jira-syntax.sh comment.txt # read the result # only then: jira-comment.py add PROJ-123 - # body piped from the same file ``` Never chain it with the posting command. With `;` the comment posts even though validation failed; with `&&` the report scrolls past unread. Either way the broken comment is already on the ticket and needs a follow-up edit — visible to everyone watching it. ### Frequent catch: braces inside a monospace span `${VAR}` (or any `{`/`}`) inside `{{...}}` breaks the span — the Jira parser renders it as raw text: ``` {{traefik.http.middlewares.office-allow-${ENVIRONMENT}.ipallowlist.sourcerange}} ``` The validator reports: ``` ERROR: Found unescaped { or } inside {{...}} monospace block — Jira parser will render it as raw text. Escape as \{ \} or split the reference. ``` Escape the braces as `\{ \}`, split the reference, or move the whole line into a `{code}` block — a `{code}` block is usually the most readable for anything command-shaped.
-
-
scripts
-
validate-jira-syntax.sh 26.1 KB
#!/bin/bash # Jira Wiki Markup Syntax Validator # Checks text for common Jira syntax errors and suggests corrections set -e # Colors for output RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' NC='\033[0m' # No Color # Counters ERRORS=0 WARNINGS=0 # Function to print error # Use pre-increment so the (( )) expression is the new (non-zero) value; # `((ERRORS++))` returns the OLD value (0 on first call) and trips `set -e`. error() { echo -e "${RED}❌ ERROR:${NC} $1" ((++ERRORS)) } # Function to print warning warning() { echo -e "${YELLOW}⚠️ WARNING:${NC} $1" ((++WARNINGS)) } # Function to print success success() { echo -e "${GREEN}✅ $1${NC}" } # Function to check file validate_file() { local file="$1" echo "" echo "==========================================" echo "Validating: $file" echo "==========================================" if [ ! -f "$file" ]; then error "File not found: $file" return fi # Hybrid template files (templates/*.md) are a Markdown wrapper carrying # the Jira markup inside ``` fences. For those, validate the fenced # payload: untagged ``` fences hold Jira markup to be copy-pasted; # language-tagged fences (```bash) are foreign code and are skipped, as is # the wrapper prose. Files without fences — drafts about to be posted — # keep the full strict treatment. local content if grep -qE '^[[:space:]]*```' "$file"; then echo " (hybrid file: validating untagged \`\`\` fenced payload as Jira markup)" content=$(awk ' /^[[:space:]]*```/ { if (infence) { infence = 0 } else { infence = 1; tagged = ($0 ~ /^[[:space:]]*```./) } next } infence && !tagged { print } ' "$file") else content=$(cat "$file") fi # Check for Markdown-style headings (## instead of h2.) if echo "$content" | grep -qE "^##+ "; then error "Found Markdown-style headings (##). Use Jira format: h2. Heading" echo " Lines with issue:" echo "$content" | grep -nE "^##+ " | head -5 fi # Check for Markdown-style bold (**text** instead of *text*) if echo "$content" | grep -qE "\*\*[^*]+\*\*"; then warning "Found Markdown-style bold (**text**). Use Jira format: *text*" echo " Examples found:" echo "$content" | grep -oE "\*\*[^*]+\*\*" | head -3 fi # Check for Markdown-style italic (_text_ is ok, but *text* for bold might be confused) if echo "$content" | grep -qE "\*[^*]+\*\*[^*]+\*"; then warning "Found potential Markdown-style italic mixed with bold" fi # Check for Markdown-style code blocks (``` instead of {code}) if echo "$content" | grep -qE "^\`\`\`"; then error "Found Markdown code blocks (\`\`\`). Use Jira format: {code:language}" echo " Lines with issue:" echo "$content" | grep -nE "^\`\`\`" | head -5 fi # Check for Markdown-style inline code (` instead of {{) if grep -qE "\`[^\`]+\`" <<< "$content"; then warning "Found Markdown inline code (\`code\`). Consider Jira format: {{code}}" fi # Check for unescaped { or } inside {{...}} monospace blocks. The Jira parser # is greedy and breaks on raw inner braces, rendering the block as raw text # (e.g. {{compose.example.{yml,override.pga.yml}}} renders verbatim). # Backslash-escaped braces (\{ \}) render literally and are fine. # Two failure modes: # 1. {{ followed by another raw { before any } — e.g. {{path/{a,b}.txt}} # or {{a{b}c}}. # 2. A {{ block with an extra raw } before the closing }} — e.g. {{a}b}}. # `([^...\\]|\\.)*` skips escaped characters so \{ and \} don't false-positive. local brace_re='\{\{([^{}\\]|\\.)*\{|\{\{([^{}\\]|\\.)*\}([^{}\\]|\\.)*\}\}' if grep -qE "$brace_re" <<< "$content"; then error "Found unescaped { or } inside {{...}} monospace block — Jira parser will render it as raw text. Escape as \\{ \\} or split the reference." echo " Lines with issue:" grep -nE "$brace_re" <<< "$content" | head -3 fi # Check for unescaped * inside {{...}} monospace blocks. Jira still parses # inline markup inside {{...}}: a * pair turns bold mid-token # (e.g. {{jira-*backup-*}} renders "backup-" bold). Escape as \*. local star_re='\{\{([^{}*\\]|\\.)*\*' if grep -qE "$star_re" <<< "$content"; then warning "Found unescaped * inside {{...}} monospace block — renders as bold mid-token. Escape as \\* (e.g. {{jira-\\*backup-\\*}})." echo " Lines with issue:" grep -nE "$star_re" <<< "$content" | head -3 fi # Check for dash pairs Jira renders as a strikethrough span (`-text-`). # # This is the same grammar as find_strikethrough_spans() in # skills/jira-communication/scripts/lib/markup.py, and both are measured # against a live Jira Server 9.12 renderer rather than reasoned about: # tests/test_validator_parity.py runs BOTH implementations over the # single-line cases in tests/fixtures/strikethrough_oracle.json and asserts # they agree. It does NOT cover multi-line input, tables or headings, so # block handling is pinned separately in tests/test_strikethrough.py, which # also holds the Python side against the recorded corpus. # # opener an unescaped `-` at line start or after a non-word character # (which includes the `}}`, `*`, `_`, `]`, `!` that end an inline # element — that is why {{mono}}-Extensions opens a span), and # followed by neither whitespace nor another dash # closer the first LATER unescaped `-` that is not preceded by # whitespace and is followed by a non-word character or end of # line; dashes that fail those conditions are skipped over. A # dash that leads a word can never close, which is why # `journalctl -b -p crit` alone is not a span — but the same # flags followed by `zu-` later on the line ARE one. # # Dashes inside {code}/{noformat} render literally, so those lines are # skipped via open/close toggling ({quote}/{panel} are NOT skipped — Jira # parses text effects inside them). Markdown ``` fences are skipped the # same way: their own check above already errors on them. # # Links, bare URLs and !images! are resolved into a single element before # text effects run, so a dash inside them is inert — the `/-/` in a GitLab # merge-request URL must not be escaped, or the link breaks. They are # masked out before the scan. {{monospace}} is NOT masked: text effects do # apply inside it. # # Word class: NOT space and NOT punct, which is locale-independent. Two # dead ends, both measured with gawk 5.2.1: [[:alnum:]] is locale-dependent # (ASCII-only under LC_ALL=C), and an explicit high-byte range # [\200-\377] is rejected outright in a multibyte locale ("Invalid # collation character"), killing the rule silently. Negating space and # punct agrees in both for ASCII and for non-ASCII LETTERS: a high byte is # neither space nor punct under C, and is alnum under UTF-8. It also # matches Jira, where `_` is NOT a word character. # # The one place the two locales genuinely disagree is non-ASCII # PUNCTUATION: under LC_ALL=C a typographic quote is a run of high bytes # and therefore reads as a word character, so `Der Wert "-x- ist falsch` # (German quotes) would be missed. tests/test_validator_parity.py caught # exactly that on five corpus cases. They are folded down to an ASCII # quote first, which behaves identically in both locales - gsub on a # literal UTF-8 string matches bytes under C and characters under UTF-8. # Run the scan under a UTF-8 locale when the machine has one. The word class # below is locale-independent for ASCII and for non-ASCII LETTERS, but not # for arbitrary non-ASCII PUNCTUATION: under LC_ALL=C a `§`, `€`, `°` or `±` # is a run of high bytes and reads as a word character, so the opener # boundary is lost and a real span is missed. fold() handles the handful of # codepoints that show up in prose; the locale handles the rest. On a # C-only machine the fold is what is left, and the gap is the unfolded # symbols - tests/test_validator_parity.py runs both locales to keep that # visible rather than silent. # JIRA_SYNTAX_SCAN_LOCALE overrides the choice, so the C-only fallback can # be exercised on a machine that does have UTF-8 (the parity test does that). local scan_locale="${JIRA_SYNTAX_SCAN_LOCALE:-}" if [ -z "$scan_locale" ]; then scan_locale=$(locale -a 2>/dev/null | grep -iE '\.(utf-?8)$' | head -1) fi : "${scan_locale:=C}" local dash_hits # AWK-DASH-SCAN-BEGIN (marker: tests/test_validator_parity.py extracts the # program between these two markers and runs it over the whole corpus in # one pass. Keep them.) dash_hits=$(LC_ALL="$scan_locale" awk ' function fold(line) { # NBSP becomes \002, a sentinel that is neither space nor word. # It has to be BOTH at once, because Jira treats U+00A0 as two # different things: an ordinary character for the dash delimiters # (`a -x<NBSP>- y` IS struck through, measured) and a terminator # for the URL autolinker (`https://h/a<NBSP>/-/b` links only # `https://h/a`). Python gets this from `\s` matching U+00A0 while # _is_delimiter_space does not; here \002 is excluded from the # region classes below and from isword(). Folding it to a space # instead would break the delimiters, and folding it to punctuation # - which this did - let a region swallow the dashes after it. gsub(/\xc2\xa0/, "\002", line) gsub(/\xe2\x80\x9e|\xe2\x80\x9c|\xe2\x80\x9d|\xe2\x80\x98|\xe2\x80\x99|\xe2\x80\x9a/, "\"", line) gsub(/\xc2\xab|\xc2\xbb|\xe2\x80\xb9|\xe2\x80\xba/, "\"", line) gsub(/\xe2\x80\x93|\xe2\x80\x94|\xe2\x80\x90|\xe2\x80\xa6|\xe2\x80\xa2/, "\"", line) # Common non-ASCII symbols. Not exhaustive and cannot be - this is # the C-locale fallback only; where a UTF-8 locale exists the # scan runs under it and gawk classifies these itself. gsub(/\xc2\xa7|\xe2\x82\xac|\xc2\xb0|\xc2\xb1|\xc2\xb5|\xc3\x97|\xc3\xb7/, "\"", line) gsub(/\xc2\xa9|\xc2\xae|\xe2\x84\xa2|\xe2\x80\xa0|\xe2\x80\xa1|\xe2\x80\xb0/, "\"", line) return line } # The \001 test comes first: a masked region must read as a boundary, and a # control character is neither space nor punct, so it would otherwise # count as a word character and swallow the opener after a link. function isword(c) { return (c != "" && c != "\001" && c != "\002" && c !~ /[[:space:][:punct:]]/) } # Replace protected regions with \001, preserving length. Mirrors # _mask_protected() in lib/markup.py: a bare URL, mailto or image needs # an ASCII non-alphanumeric before it (one glued to a word is not # autolinked, so it keeps its dashes live) while a square-bracketed # link resolves anywhere, and a region starting exactly where the # previous one ended does not resolve either. function mask(line, out, pos, rest, start, len_, before, kind, i, prev_end) { out = "" pos = 1 prev_end = 0 while (pos <= length(line)) { rest = substr(line, pos) if (!match(rest, /(\[[^]\n]*\])|((https?|ftp):\/\/[^][:space:]{}|\002]+)|(mailto:[^][:space:]{}|\002]+)|(![^[:space:]!\002]+!)/)) { out = out rest break } start = pos + RSTART - 1 len_ = RLENGTH before = (start > 1) ? substr(line, start - 1, 1) : "" kind = substr(line, start, 1) # A backslash-escaped bracket or bang is not a macro, so its # content is ordinary prose and its dashes stay live. Same for a # region that does not sit at a boundary: a bare URL, mailto or # image needs a non-alphanumeric before it (and a URL must not # follow a pipe), while a square-bracketed link resolves # anywhere. In both cases the region is not a region, so the # scan resumes one character in - which is what the Python # regex does by simply not matching there. if (before == "\\" || (kind != "[" && (before ~ /[A-Za-z0-9]/ || (kind != "!" && (before == "|" || before == "!"))))) { out = out substr(line, pos, RSTART) pos = start + 1 continue } # Two regions written back to back do not both resolve: Jira # renders the first and leaves the second literal. The SECOND # one is skipped whole, not one character at a time - resuming # inside it would let a shorter region nested within it be # masked, which loses the dashes around it. That was a real # miss: `!i.png!https://h/a/-/b[t|...]` comes back struck and # the scan reported nothing. prev_end deliberately does not # advance here, so a third region is masked again, matching # _mask_protected() in lib/markup.py. if (start == prev_end) { out = out substr(line, pos, RSTART - 1 + len_) pos = start + len_ continue } out = out substr(line, pos, RSTART - 1) for (i = 0; i < len_; i++) out = out "\001" pos = start + len_ prev_end = pos } return out } function strikes(raw, line, n, i, j, prev, nxt, after) { line = mask(raw) n = length(line) for (i = 1; i <= n; i++) { if (substr(line, i, 1) != "-") continue if (i > 1 && substr(line, i - 1, 1) == "\\") continue prev = (i > 1) ? substr(line, i - 1, 1) : "" if (isword(prev)) continue nxt = (i < n) ? substr(line, i + 1, 1) : "" if (nxt == "" || nxt ~ /[[:space:]]/ || nxt == "-") continue for (j = i + 2; j <= n; j++) { if (substr(line, j, 1) != "-") continue if (substr(line, j - 1, 1) == "\\") continue if (substr(line, j - 1, 1) ~ /[[:space:]]/) continue after = (j < n) ? substr(line, j + 1, 1) : "" if (isword(after)) continue return 1 } } return 0 } # Only the SAME tag closes a block, exactly as the Jira renderer does # and as _split_verbatim() in lib/markup.py does. Toggling on either tag # loses track after `{noformat}` / `{code}` / `{noformat}`, and then # misses every span in the rest of the file. /^[[:space:]]*\{(code|noformat)(:[^}]*)?\}[[:space:]]*$/ { tag = $0 sub(/^[[:space:]]*\{/, "", tag) sub(/[:}].*$/, "", tag) if (opentag == "") opentag = tag else if (opentag == tag) opentag = "" next } /^[[:space:]]*```/ { infence = !infence; next } opentag != "" || infence { next } strikes(fold($0)) { printf "%d:%s\n", NR, $0 }' <<< "$content") && dash_rc=0 || dash_rc=$? # The `&& … || …` is load-bearing under `set -e` (line 6): a bare assignment # takes the command substitution's status, so a failing awk aborted the # whole script before this branch could run. It exited non-zero, so nothing # was silently passed - but the ERROR below never printed and the remaining # files in a multi-file run were skipped. A compound command is exempt from # set -e, which is what lets the branch execute. # awk's status must not be swallowed by the pipe: a scan that never ran # (no awk, a syntax error, a killed process) produces no hits, and no hits # is what a clean draft looks like. That is the same "a failure reads as # clean" shape RenderVerdict.available exists to prevent on the Python # side, and it is an ERROR here rather than silence. if [ "$dash_rc" -ne 0 ]; then error "The dash-strikethrough scan did not run (awk exited $dash_rc) - this draft was NOT checked for spans Jira would render struck through." dash_hits="" else dash_hits=$(printf '%s\n' "$dash_hits" | grep -v '^$' | head -3) fi # AWK-DASH-SCAN-END if [ -n "$dash_hits" ]; then warning "Found a dash pair that renders struck through outside a code block — Jira reads a dash after a non-word character (including the {{}}, *, _, ] or ! that ends an inline element) as a strikethrough opener, and a dash before one as the closer. Escape the opener as \\-foo; a backslash-escaped dash still prints as a plain hyphen." echo " Lines with issue:" echo "$dash_hits" fi # Check for Markdown-style links ([text](url) instead of [text|url]) if echo "$content" | grep -qE "\[([^\]]+)\]\(([^)]+)\)"; then error "Found Markdown-style links ([text](url)). Use Jira format: [text|url]" echo " Examples found:" echo "$content" | grep -oE "\[([^\]]+)\]\(([^)]+)\)" | head -3 fi # Check for headings without space after period (h2.Title instead of h2. Title) if echo "$content" | grep -qE "^h[1-6]\.[^ ]"; then error "Found headings without space after period. Use: h2. Title (not h2.Title)" echo " Lines with issue:" echo "$content" | grep -nE "^h[1-6]\.[^ ]" | head -5 fi # Check for code blocks without language specification # (skip escaped \{code\} literals and inline-monospace {{code}}) if grep -qE '(^|[^\\{])\{code\}[^{]' <<< "$content"; then warning "Found {code} blocks without language. Consider: {code:java} for syntax highlighting" fi # Check for {code:LANG} using a language Jira Server's formatter does not recognize. # Authoritative list from the server error message ("Available languages are: ..."). # Anything outside this set causes "Unable to find source-code formatter for language: X". # Use Bash built-in pattern matching with literal-quoted needle so identifiers # containing shell-significant characters (c#, c++) are compared verbatim. local valid_langs="actionscript ada applescript bash c c# c++ cpp css erlang go groovy haskell html java javascript js json lua none nyan objc perl php python r rainbow ruby scala sh sql swift visualbasic xml yaml" local search_langs=" $valid_langs " while IFS= read -r lang; do [ -z "$lang" ] && continue # Templates ship `{code:language}` as a fill-in placeholder; warn rather than # error so templates stay validatable until users substitute a real lang. if [ "$lang" = "language" ]; then warning "Found {code:language} placeholder — replace with an actual language before submitting to Jira" continue fi if [[ "$search_langs" != *" $lang "* ]]; then # Suggest the closest-fit valid language for common stumbles before # falling back to the generic "use {code:none} or ..." message. # Lower-case the identifier so `Dockerfile`, `Makefile` etc. match # without per-variant case entries. local hint="" case "${lang,,}" in hcl|tf|terraform|tofu) hint="{code:none} for HCL / Terraform / OpenTofu" ;; dockerfile|containerfile) hint="{code:bash} (Dockerfile RUN lines lex acceptably as bash) or {code:none}" ;; rust|rs) hint="{code:none} for Rust" ;; kotlin|kt) hint="{code:java} (Kotlin lexes acceptably as Java) or {code:none}" ;; typescript|ts|tsx) hint="{code:javascript} or {code:none}" ;; shell|zsh|fish|console) hint="{code:bash} or {code:none}" ;; powershell|ps1|pwsh) hint="{code:none} for PowerShell" ;; make|makefile) hint="{code:none} for Makefile" ;; ini|toml|conf|properties) hint="{code:none} for INI / TOML / config" ;; diff|patch) hint="{code:none}" ;; go-template|gotmpl|jinja|jinja2) hint="{code:none}" ;; esac if [ -n "$hint" ]; then error "Unsupported {code:$lang} language. Jira Server rejects this; use $hint" else error "Unsupported {code:$lang} language. Jira Server rejects this; use {code:none} or one of: $valid_langs" fi fi done < <(grep -oE '(^|[^\\{])\{code:[^}|\\]+' <<< "$content" | sed 's/.*{code://' | sort -u) # Check for tables with incorrect header syntax (|Header| instead of ||Header||) if echo "$content" | grep -qE "^\|[^|]+\|$" && ! echo "$content" | grep -qE "^\|\|"; then warning "Potential table header without double pipes. Headers should use: ||Header||" fi # Check for unclosed {code} blocks # Jira wiki markup uses {code} as both the opening and closing tag, so a # correctly paired block always produces an even occurrence count. # Use `grep -o ... | wc -l` to count each occurrence (not just matching # lines), matching the {color} check below for consistency and to catch # multiple tags on the same line. # `(^|[^\\{])` skips escaped literals (\{code\}) and inline-monospace # lookalikes ({{code}}) — both are prose, not block markup. local code_count code_count=$(grep -oE '(^|[^\\{])\{code[}:]' <<< "$content" | wc -l) if [ $((code_count % 2)) -ne 0 ]; then error "Mismatched {code} tags: odd number ($code_count) of occurrences (expected pairs)" fi # Check for unclosed {panel} blocks # Same rule applies: {panel} opens and closes the block. local panel_count panel_count=$(grep -oE '(^|[^\\{])\{panel[}:]' <<< "$content" | wc -l) if [ $((panel_count % 2)) -ne 0 ]; then error "Mismatched {panel} tags: odd number ($panel_count) of occurrences (expected pairs)" fi # Check for unclosed {color} blocks local color_count color_count=$(echo "$content" | grep -o "{color" | wc -l) if [ $((color_count % 2)) -ne 0 ]; then warning "Potential unclosed {color} tag (odd number of occurrences)" fi # Check for unclosed {noformat}, {quote}, {anchor} blocks # Same single-token open/close rule as {code}, {panel}, {color}: an odd # occurrence count signals an unescaped literal in prose or a missing close. for macro in noformat quote anchor; do local mcount mcount=$(grep -oE "(^|[^\\\\{])\{${macro}[}:]" <<< "$content" | wc -l) if [ $((mcount % 2)) -ne 0 ]; then warning "Potential unclosed {${macro}} tag (odd number of occurrences)" fi done # Check for block-markup tags used inline. {code}, {noformat}, {quote} and # {panel} are block-level macros: the tag must stand alone on its own line. # An unescaped tag with other text on the same line opens the block # mid-prose and swallows the rest of the line (classic case: writing # *about* {code} in a sentence). Escape literal mentions as \{code\}. # Escaped tags (\{code\}) and {{monospace}} lookalikes ({{code}}) are # stripped per line BEFORE testing — a line-level exclusion would hide a # genuine unescaped tag sharing a line with an escaped/monospace mention. local inline_hits inline_hits=$(awk ' { line = $0 gsub(/\\\{(code|noformat|quote|panel)[^}]*\\\}/, "", line) gsub(/\{\{(code|noformat|quote|panel)(:[^}]*)?\}\}/, "", line) if (line ~ /\{(code|noformat|quote|panel)(:[^}]*)?\}/ && line !~ /^[[:space:]]*\{(code|noformat|quote|panel)(:[^}]*)?\}[[:space:]]*$/) printf "%d:%s\n", NR, $0 }' <<< "$content" | head -3) if [ -n "$inline_hits" ]; then error "Block tag used inline — {code}/{noformat}/{quote}/{panel} must stand alone on their own line; escape literal mentions as \\{code\\}" echo " Lines with issue:" echo "$inline_hits" fi # Check for Markdown-style lists (- item instead of * item) if echo "$content" | grep -qE "^- [^-]"; then warning "Found Markdown-style bullets (- item). Jira prefers: * item" fi # Positive checks if echo "$content" | grep -qE "^h[1-6]\. "; then success "Found correctly formatted Jira headings" fi if echo "$content" | grep -qE "{code:[a-z]+}"; then success "Found code blocks with language specification" fi if echo "$content" | grep -qE "\[~[a-z.]+\]"; then success "Found user mentions ([~username])" fi if echo "$content" | grep -qE "\[[A-Z]+-[0-9]+\]"; then success "Found issue links ([PROJ-123])" fi } # Main script echo "Jira Wiki Markup Syntax Validator" echo "==================================" if [ $# -eq 0 ]; then echo "Usage: $0 <file1> [file2] [file3] ..." echo "" echo "Validates Jira wiki markup syntax in text files" echo "" echo "Example:" echo " $0 issue-description.txt" echo " $0 templates/*.md" exit 1 fi # Validate each file for file in "$@"; do validate_file "$file" done # Summary echo "" echo "==========================================" echo "Validation Summary" echo "==========================================" echo "Files checked: $#" echo -e "${RED}Errors: $ERRORS${NC}" echo -e "${YELLOW}Warnings: $WARNINGS${NC}" if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then echo -e "${GREEN}✅ All checks passed!${NC}" exit 0 elif [ $ERRORS -eq 0 ]; then echo -e "${YELLOW}⚠️ No errors, but $WARNINGS warnings found${NC}" exit 0 else echo -e "${RED}❌ $ERRORS errors found - please fix before submitting to Jira${NC}" exit 1 fi
-
-
templates
-
bug-report-template.md 4 KB
# Jira Bug Report Template Use this template when creating bug reports in Jira with proper wiki markup syntax. ## Template ``` h2. Bug Description [Provide a clear, concise description of the bug] h3. Environment * *Browser:* Chrome 120.0 * *OS:* Windows 11 * *Version:* 2.1.0 * *Environment:* Production h3. Steps to Reproduce # Navigate to [specific page/feature] # Perform [specific action] # Observe [unexpected behavior] h3. Expected Behavior [Describe what should happen] h3. Actual Behavior [Describe what actually happens] {panel:title=Error Message|bgColor=#FFEBE9} {code:java} [Paste error message or stack trace here] {code} {panel} h3. Additional Context * Frequency: [Always/Sometimes/Rare] * User Impact: [Critical/High/Medium/Low] * Workaround Available: [Yes/No] h3. Screenshots [^screenshot1.png] [^screenshot2.png] h3. Related Issues * Blocks [PROJ-XXX] * Related to [PROJ-YYY] h3. Technical Notes {code:javascript} // Code snippet showing the issue function problematicCode() { // Details here } {code} --- *Reported by:* [~username] *Date:* YYYY-MM-DD ``` ## Example - Filled Template ``` h2. Bug Description Login button becomes unresponsive after failed authentication attempt, requiring page refresh to retry. h3. Environment * *Browser:* Chrome 120.0.6099.109 * *OS:* Windows 11 * *Version:* 2.3.1 * *Environment:* Production h3. Steps to Reproduce # Navigate to {{/login}} page # Enter invalid credentials # Click *Login* button # Observe error message # Try to enter correct credentials # Click *Login* button again # Button remains disabled h3. Expected Behavior After a failed login attempt, the login button should become active again, allowing users to retry with different credentials. h3. Actual Behavior The login button remains in a disabled state after the first failed attempt. Users must refresh the page to attempt login again. {panel:title=Error in Browser Console|bgColor=#FFEBE9} {code:javascript} TypeError: Cannot read property 'reset' of null at LoginForm.handleSubmit (login.js:45) at onClick (login.js:23) {code} {panel} h3. Additional Context * Frequency: Always (100% reproduction rate) * User Impact: High (blocks login functionality) * Workaround Available: Yes (page refresh) * Affects ~1000 daily users based on error logs h3. Screenshots [^login-disabled-state.png] - Login button stuck in disabled state [^console-error.png] - Browser console showing error h3. Related Issues * Blocks [PROJ-234] - User authentication improvements * Related to [PROJ-189] - Form validation refactoring h3. Technical Notes {code:javascript} // Problem in LoginForm component handleSubmit(event) { event.preventDefault(); this.setState({ isSubmitting: true }); // Error occurs here if form ref is null this.formRef.reset(); // BUG: formRef can be null // Rest of submission logic } {code} Suggested fix: Add null check before calling {{reset()}} method. --- *Reported by:* [~john.smith] *Date:* 2025-11-06 ``` ## Usage with jira-communication Skill ```bash # Create the bug report using the script uv run scripts/workflow/jira-create.py issue PROJ \ "Login button unresponsive after failed authentication" \ --type Bug \ --priority High \ --labels frontend,authentication,ux \ --description-file bug-description.txt # Or with inline description (short version) uv run scripts/workflow/jira-create.py issue PROJ \ "Login button unresponsive after failed authentication" \ --type Bug \ --priority High ``` ## Checklist Before Submitting - [ ] h2. heading for main Description section - [ ] h3. headings for subsections - [ ] Numbered list (#) for Steps to Reproduce - [ ] Bulleted list (*) for Environment details - [ ] \{panel\} for error messages with appropriate bgColor - [ ] \{code:language\} for code snippets with correct language - [ ] [^filename] format for attachment references - [ ] [PROJ-XXX] format for issue links - [ ] [~username] format for user mentions - [ ] *bold* for emphasis on key terms - [ ] {{monospace}} for UI elements and paths -
feature-request-template.md 7.9 KB
# Jira Feature Request Template Use this template when creating feature requests in Jira with proper wiki markup syntax. ## Template ``` h2. Feature Overview [Provide a brief summary of the feature] h3. Business Value * *User Impact:* [Describe who benefits and how] * *Business Goal:* [Align with strategic objectives] * *Priority Justification:* [Why this should be prioritized] h3. User Stories h4. As a [user type] * I want to [action] * So that [benefit] h4. As a [another user type] * I want to [action] * So that [benefit] h3. Acceptance Criteria # [Specific, testable criterion] # [Specific, testable criterion] # [Specific, testable criterion] h3. Functional Requirements h4. Must Have * Requirement 1 * Requirement 2 h4. Should Have * Requirement 3 * Requirement 4 h4. Could Have * Requirement 5 * Requirement 6 h3. Non-Functional Requirements * *Performance:* [Response time requirements] * *Security:* [Security considerations] * *Scalability:* [Scale requirements] * *Accessibility:* [WCAG compliance level] h3. Technical Considerations {code:language} // Pseudocode or technical notes {code} h3. UI/UX Mockups [^wireframe-01.png] - Main interface mockup [^user-flow.png] - User journey diagram h3. Dependencies * Requires [PROJ-XXX] to be completed first * Impacts [PROJ-YYY] - needs coordination h3. Open Questions ? Question 1 - needs clarification ? Question 2 - requires decision h3. Success Metrics ||Metric||Target||Measurement Method|| |User Adoption|80% of active users|Analytics tracking| |Performance|< 200ms response|Performance monitoring| |Satisfaction|4.5/5 rating|User surveys| --- *Requested by:* [~username] *Stakeholders:* [~pm], [~designer], [~engineer] ``` ## Example - Filled Template ``` h2. Feature Overview Implement bulk export functionality allowing users to export multiple projects to various formats (CSV, JSON, Excel) with customizable field selection. h3. Business Value * *User Impact:* 500+ power users currently export data manually one project at a time (30+ clicks per export) * *Business Goal:* Reduce data export time by 90%, improving productivity and user satisfaction * *Priority Justification:* #1 requested feature in Q4 2024 user survey (78% of respondents), competitive gap vs competitors h3. User Stories h4. As a Project Manager * I want to export multiple projects at once with selected fields * So that I can create consolidated reports without manual data entry h4. As a Data Analyst * I want to export historical project data in machine-readable formats * So that I can perform advanced analytics in external tools h4. As a Team Lead * I want to schedule automated exports to run daily * So that stakeholders receive up-to-date reports without manual intervention h3. Acceptance Criteria # User can select 1-100 projects for bulk export from project list # Export supports CSV, JSON, and Excel formats # User can customize which fields to include in export # Export progress is shown with cancel option # Completed exports are downloadable from exports history page # Export file size limit is 50MB with pagination for larger datasets # Exports are available for 30 days before auto-deletion h3. Functional Requirements h4. Must Have * Multi-select checkbox interface for project selection * Format selector (CSV, JSON, Excel) * Field customization with drag-and-drop ordering * Progress indicator during export generation * Download link with expiration notice * Export history page showing last 10 exports h4. Should Have * Search and filter for project selection * Save field configurations as templates * Email notification when export completes * Preview sample data before full export h4. Could Have * Schedule recurring exports * Share export links with team members * Export directly to cloud storage (Google Drive, Dropbox) * Advanced filtering within export data h3. Non-Functional Requirements * *Performance:* Export generation completes within 30 seconds for 50 projects * *Security:* Exports encrypted at rest, only accessible to authorized users with audit trail * *Scalability:* Support 1000 concurrent export requests * *Accessibility:* WCAG 2.1 AA compliance for export interface * *Browser Support:* Chrome 90+, Firefox 88+, Safari 14+, Edge 90+ h3. Technical Considerations {code:python} # Proposed export architecture class BulkExporter: def export_projects(self, project_ids, format, fields): # Use async task queue for processing task = ExportTask.create( projects=project_ids, format=format, fields=fields, user=current_user ) # Process in background celery.send_task('exports.process_bulk', args=[task.id]) return task.id def get_export_status(self, task_id): # Return progress percentage and download link when complete return ExportTask.get(task_id).status {code} *Database Impact:* New {{exports}} table for tracking, ~1GB storage for 30-day retention *API Endpoints:* * {{POST /api/exports}} - Initiate export * {{GET /api/exports/:id}} - Check status * {{GET /api/exports/:id/download}} - Download file h3. UI/UX Mockups [^bulk-export-interface.png] - Main export dialog with project selection [^field-customization.png] - Field selector with drag-and-drop ordering [^export-progress.png] - Progress indicator during generation [^export-history.png] - Export history page design h3. Dependencies * Requires [PROJ-456] - Background task queue infrastructure * Impacts [PROJ-789] - Storage quota system (needs capacity planning) * Coordinates with [PROJ-321] - API rate limiting (exports count as API calls) h3. Open Questions ? Should exports include archived projects or only active ones? ? What permission level is required to export data? (View vs Export permission) ? Should we support incremental exports (only new/changed data)? ? How to handle very large exports exceeding 50MB limit? h3. Success Metrics ||Metric||Target||Measurement Method|| |User Adoption|60% of power users within 3 months|Analytics event tracking| |Time Savings|90% reduction in export time|User session timing comparison| |User Satisfaction|4.5/5 feature rating|In-app feedback survey| |Export Volume|5000+ exports per month|Export usage dashboard| |Error Rate|< 1% failed exports|Error monitoring logs| {panel:title=Launch Plan|bgColor=#DEEBFF} h4. Phase 1 - Beta (Week 1-2) * Release to 50 beta users * Gather feedback and fix critical bugs h4. Phase 2 - Limited Release (Week 3-4) * Release to 20% of user base * Monitor performance and error rates h4. Phase 3 - Full Release (Week 5+) * Release to all users * Announce via email and in-app notifications {panel} --- *Requested by:* [~sarah.johnson] *Stakeholders:* [~product.manager], [~ux.designer], [~backend.lead], [~frontend.lead] *Estimated Effort:* 3 sprints (6 weeks) *Target Release:* Q1 2025 ``` ## Usage with jira-communication Skill ```bash # Create the feature request using the script uv run scripts/workflow/jira-create.py issue PROJ \ "Bulk export functionality for multiple projects" \ --type Story \ --priority High \ --labels feature-request,export,productivity \ --description-file feature-description.txt # Or with inline description (short version) uv run scripts/workflow/jira-create.py issue PROJ \ "Bulk export functionality for multiple projects" \ --type Story \ --priority High ``` ## Checklist Before Submitting - [ ] h2. heading for Feature Overview - [ ] h3. headings for all major sections - [ ] h4. headings for User Stories and subsections - [ ] Bulleted lists (*) for requirements and criteria - [ ] Numbered lists (#) for Acceptance Criteria - [ ] Tables (||header|| syntax) for Success Metrics - [ ] \{code:language\} blocks for technical details - [ ] \{panel\} for important launch/timeline information - [ ] [^filename] for mockup/wireframe references - [ ] [PROJ-XXX] format for dependency links - [ ] [~username] format for stakeholder mentions - [ ] *bold* for emphasis on metrics and targets - [ ] {{monospace}} for technical terms and paths - [ ] ? prefix for Open Questions
-
-
AGENTS.md 1.8 KB
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-12-12 --> # AGENTS.md — jira-syntax Development guide for maintaining templates, references, and validation scripts. ## Overview Static content skill: templates, reference docs, and a validation script. No runtime dependencies. ## Setup & environment No special setup. Files are plain text/markdown/shell. ## Build & tests ```bash # Test validation script scripts/validate-jira-syntax.sh templates/bug-report-template.md ``` ## Code style & conventions **Templates (`templates/`):** - Use actual Jira wiki markup, never Markdown - Include section comments explaining purpose - Test in real Jira before committing **References (`references/`):** - Keep `jira-syntax-quick-reference.md` as single source of truth - Examples must be copy-pasteable into Jira **Validation script:** - Bash with grep patterns - Should catch common Markdown mistakes ## Security & safety - No executable code in templates - No sensitive data in examples ## PR/commit checklist - [ ] All examples use valid Jira wiki markup - [ ] Tested in actual Jira instance - [ ] Updated quick-reference if adding new syntax - [ ] Validation script catches the patterns ## Good vs. bad examples **Template changes:** ``` # ✓ Test paste into Jira before commit # ✓ Update quick-reference if new syntax # ✗ Assume Markdown works in Jira # ✗ Add syntax without testing ``` ## When stuck - Paste content into real Jira to verify rendering - Check official docs: https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa ## House rules - SKILL.md has the user-facing syntax reference - This file is for maintaining the skill itself --- **Maintaining this file:** See root `AGENTS.md` for convention reference. -
SKILL.md 3.9 KB
--- name: jira-syntax description: "Use when writing or formatting Jira descriptions, comments, or any text destined for Jira. Converts Markdown to Jira wiki markup, provides templates (bug reports, feature requests), and validates syntax before submission. Trigger on any Jira content authoring task." license: "(MIT AND CC-BY-SA-4.0). See LICENSE-MIT and LICENSE-CC-BY-SA-4.0" metadata: author: Netresearch DTT GmbH version: "3.32.2" repository: https://github.com/netresearch/jira-skill --- # Jira Syntax Jira wiki markup syntax, templates, and validation. For API operations, use the **jira-communication** skill. ## Quick Syntax Reference | Jira Syntax | Purpose | NOT this (Markdown) | |-------------|---------|---------------------| | `h2. Title` | Heading | `## Title` | | `*bold*` | Bold | `**bold**` | | `_italic_` | Italic | `*italic*` | | `{{code}}` | Inline code | `` `code` `` | | `{code:java}...{code}` | Code block | ``` ```java ``` | | `[text\|url]` | Link | `[text](url)` | | `[PROJ-123]` | Issue link | - | | `[~username]` | User mention | `@username` | | `* item` | Bullet list | `- item` | | `# item` | Numbered list | `1. item` | | `\|\|Header\|\|` | Table header | `\|Header\|` | See `references/jira-syntax-quick-reference.md` for complete syntax documentation. ## Available Templates ### Bug Report **Path**: `templates/bug-report-template.md` Sections: Environment, Steps to Reproduce, Expected/Actual Behavior, Error Messages, Technical Notes ### Feature Request **Path**: `templates/feature-request-template.md` Sections: Overview, User Stories, Acceptance Criteria, Technical Approach, Success Metrics ## Syntax Validation Run before submitting to Jira: ```bash ${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh path/to/content.txt ``` **It gates the post** — run it as its own step, never chained with the posting command. ### Validation Checklist - [ ] Headings: `h2. Title` (space after period) - [ ] Bold: `*text*` (single asterisk) - [ ] Code blocks: `{code:language}...{code}` - [ ] Lists: `*` for bullets, `#` for numbers - [ ] Links: `[label|url]` or `[PROJ-123]` - [ ] Tables: `||Header||` and `|Cell|` - [ ] Colors: `{color:red}text{color}` - [ ] Panels: `{panel:title=X}...{panel}` ### Common Mistakes | ❌ Wrong | ✅ Correct | |---------|-----------| | `## Heading` | `h2. Heading` | | `**bold**` | `*bold*` | | `` `code` `` | `{{code}}` | | `[text](url)` | `[text\|url]` | | `- bullet` | `* bullet` | | `h2.Title` | `h2. Title` | | `MR !42` (bare GitLab ref) | `[MR 42\|url]` or full `group/project!42` — a bare `!…!` is image markup | | `(/)` on an open/proposed item | `(x)` — `(/)` renders as a green check (done); use `(x)` for open items | | `( )` as a checkbox | `(x)` — `( )` is not a macro and renders literally | | `(-)` or `(x)` on a pending step in a review | `(i) pending: …` — `(-)` renders as a red no-entry sign, and in a review `(x)` means a failure | ## Integration with jira-communication Skill **Workflow:** 1. Get template from jira-syntax 2. Fill content using Jira wiki markup 3. Validate with `${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh` 4. Submit via jira-communication skill ## References - `references/jira-syntax-quick-reference.md` - Complete syntax documentation Includes **Ask the renderer instead of reasoning about it**: `POST /rest/api/1.0/render` returns the HTML Jira will produce for any markup, before anything is posted. Use it to settle a markup question rather than deriving the answer. - `references/cross-project-refs.md` - GitLab cross-project ref convention (`group/project!N`, `group/project#N`, `group/project@tag`) when linking to GitLab from Jira - `templates/bug-report-template.md` - Bug report template - `templates/feature-request-template.md` - Feature request template - `${CLAUDE_SKILL_DIR}/scripts/validate-jira-syntax.sh` - Automated syntax checker - [Official Jira Wiki Markup](https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.