Skip to content

Commit 13ee4ab

Browse files
committed
fix: address PR redhat-developer#31 review feedback from Paul
- to-issue.md: story_points → customfield_10028 (field cannot be set via display name on writes) - SKILL.md Gotcha redhat-developer#6: point agents at jira-wiki-to-adf.py instead of hand-rolling ADF - to-epic/feature/issue.md: use $(mktemp) variable for ADF output files for portability - to-epic.md: soften parent link wording — both customfield_10018 and parent.key work; warn only against issuelinks - jira-wiki-to-adf.py: switch to argparse with --help, add encoding="utf-8" on file I/O, remove unused sys import - tests/unit/test_jira_wiki_to_adf.py: 10 unit tests against epic-example.txt (heading, bulletList, taskList, inline marks) - acli-commands.md, templates.md: fix old plain-text create flow to use ADF conversion step Assisted-by: claude-sonnet-4-6
1 parent 5777b5e commit 13ee4ab

9 files changed

Lines changed: 291 additions & 26 deletions

File tree

skills/init-agents-md/SKILL.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
name: init-agents-md
3+
description: >-
4+
Use when asked to bootstrap, create, initialize, or generate an AGENTS.md or
5+
context file for a repository. Also use when asked to "set up AI agent
6+
context", "create repo context file", "add AGENTS.md", "scaffold context
7+
file", "init agents md", "create CLAUDE.md for a repo", "make my repo
8+
AI-friendly", or "help agents understand this codebase".
9+
---
10+
11+
## Goal
12+
13+
Write a minimal `AGENTS.md` and `CLAUDE.md` in the current working directory.
14+
The output is a starting point — the human must review and edit before committing.
15+
16+
**Research finding (ETH Zurich, Feb 2026):** Auto-generated context files shipped
17+
as-is reduce agent success rates by ~3% and increase costs by 20-23%. This skill
18+
generates a draft. The human edits it aggressively before committing.
19+
20+
## Step 1: Check for existing files
21+
22+
If `AGENTS.md` already exists in the CWD, ask:
23+
24+
> "AGENTS.md already exists. Overwrite it? (yes/no)"
25+
26+
If no: stop. Tell the user to delete it first and re-run the skill.
27+
If yes: proceed.
28+
29+
## Step 2: Detect commands
30+
31+
Scan these files in the CWD:
32+
33+
| File | What to extract |
34+
|------|-----------------|
35+
| `package.json` | `scripts` entries matching build, test, lint, typecheck, dev/start |
36+
| `Makefile` / `GNUmakefile` | Targets: build, test, lint, check, run |
37+
| `pyproject.toml` | `[tool.pytest]`, `[tool.ruff]`, `[tool.mypy]`, `[project.scripts]` |
38+
| `setup.cfg`, `tox.ini` | test commands |
39+
| `Taskfile.yml` | task definitions |
40+
| `go.mod` | signals Go project — look for `go test`, `go build`, `go vet` in Makefile or CI |
41+
| `Cargo.toml` | signals Rust — look for `cargo test`, `cargo build`, `cargo clippy` |
42+
| `.github/workflows/*.yml` | `run:` steps containing test/lint/build/typecheck keywords |
43+
| `.gitlab-ci.yml` | `script:` entries |
44+
| `Jenkinsfile` | `sh` steps |
45+
46+
**Cross-reference rule:** If a `package.json` script and a CI `run:` step agree, use
47+
that form. If they differ, prefer the CI form — it's what actually runs in the
48+
pipeline. If a command cannot be found or confidently inferred, omit it. Do not guess.
49+
50+
Look for these command categories:
51+
52+
- **Build** — compiles, bundles, or packages the project
53+
- **Test all** — runs the full test suite without external dependencies
54+
- **Test single file/package** — runs tests for one module
55+
- **Lint** — runs the linter across the project
56+
- **Lint single file** — lints one file in isolation
57+
- **Type check** — static type checking
58+
- **Run/dev** — starts the dev server or app locally
59+
60+
## Step 3: Interactive prompting
61+
62+
Ask these questions **one at a time**. Wait for each answer before asking the
63+
next. Accept "skip" or a blank answer to omit that section entirely.
64+
65+
**Question 1 — Key Conventions:**
66+
> "What are 2-3 project conventions an AI agent couldn't discover by reading the
67+
> code? For example: a required wrapper type for API responses, a naming rule for
68+
> files, a directory that must never be imported directly, or where generated
69+
> files live. Skip if none come to mind."
70+
71+
**Question 2 — Architecture:**
72+
> "Are there any non-obvious places where things live — for example, a feature
73+
> configured in one place but evaluated elsewhere, or a shared internal API that
74+
> shouldn't be called directly? Skip if the directory names make it obvious."
75+
76+
**Question 3 — PR Conventions:**
77+
> "Any commit message format, required CI checks, or PR conventions agents should
78+
> know? For example: Conventional Commits format, a required sign-off, or a label
79+
> that blocks merge. Skip if standard."
80+
81+
## Step 4: Write the files
82+
83+
Infer the project name from `package.json` (`name` field), `go.mod` (module path,
84+
last segment), `pyproject.toml` (`[project] name`), `Cargo.toml` (`[package] name`),
85+
or the CWD directory name as a fallback.
86+
87+
Write `AGENTS.md` using only the content that was found or provided. Omit any
88+
section — including its header — where you have nothing to say:
89+
90+
```markdown
91+
# [Project name]
92+
93+
## Build & Test Commands
94+
- Build: `[command]`
95+
- Test all: `[command]`
96+
- Test single file: `[command]`
97+
- Lint: `[command]`
98+
- Lint single file: `[command]`
99+
- Type check: `[command]`
100+
- Run: `[command]`
101+
102+
## Key Conventions
103+
- [convention from Q1 answer]
104+
105+
## Architecture
106+
- [note from Q2 answer]
107+
108+
## PR Conventions
109+
- Agent-assisted commits should include an `Assisted-by: <model>` footer
110+
- [convention from Q3 answer]
111+
```
112+
113+
Write `CLAUDE.md` with exactly this content:
114+
115+
```markdown
116+
@AGENTS.md
117+
```
118+
119+
## Step 5: Review gate
120+
121+
After writing the files, say this exactly:
122+
123+
> **Review before committing.**
124+
>
125+
> `AGENTS.md` and `CLAUDE.md` have been written to this directory. Before committing:
126+
>
127+
> 1. Open `AGENTS.md` in your editor
128+
> 2. Run each listed command to confirm it actually works
129+
> 3. Delete anything an agent could figure out by reading the code
130+
> 4. Apply this test to every line: *"Would removing this cause an agent to make a
131+
> mistake it wouldn't otherwise make?"* If not, delete it.
132+
>
133+
> Target: under 150 lines. Every unnecessary line makes agents slightly worse at
134+
> following the lines that matter.
135+
136+
Do not offer to commit the files. Do not suggest the files are ready to use.
137+
138+
## Gotchas
139+
140+
- If no commands are found at all, still write the file and tell the user which
141+
files you looked in and found nothing useful
142+
- Do not invent commands not present in config or CI — a hallucinated command is
143+
worse than an omitted one
144+
- If a script name is ambiguous (e.g., `npm run check` could be lint or typecheck),
145+
add a brief inline note: `- Lint: \`npm run check\` (appears to run ESLint)`
146+
- Omit the `## Architecture` and `## Key Conventions` sections entirely when the
147+
user skips those questions — don't leave placeholder comment lines
148+
- Always include the `Assisted-by` footer line in `## PR Conventions` even if the
149+
user skips Q3 — it applies to any repo where agents contribute
150+
- If `CLAUDE.md` already exists and contains more than `@AGENTS.md`, ask before
151+
overwriting it

skills/rhdh-jira/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Load only what the current task requires.
151151
3. **`--yes` is mandatory for mutations.** All `edit`, `transition`, `assign`, and `link create` commands prompt interactively without it. Always pass `--yes`.
152152
4. **`--fields` is restrictive on search.** Only accepts `key`, `summary`, `status`, `assignee`, `issuetype`, `priority`, `description`, `labels`. For components, sprint, fixVersions, and all custom fields — use `--json` or `scripts/parse_issues.py --enrich`.
153153
5. **Team field has two JQL syntaxes.** `customfield_10001` cannot be used in JQL WHERE clauses. However, `"Team[Team]" = {teamId}` (using the team UUID, not display name) works. Use the UUID syntax for JQL filtering; use `customfield_10001.name` in post-processing only when you need the display name from JSON output.
154-
6. **ADF is required for formatted descriptions.** Reading descriptions via `--json` returns Atlassian Document Format (nested JSON). Jira Cloud's editor is ADF-native — plain text and Jira wiki markup (`h1.`, `*bold*`) both render as literal characters in the UI. For formatted descriptions, build ADF JSON directly in Python and pass via `--description-file`. Both `acli create` and `acli edit` accept ADF JSON via `--description-file`. Do not use Jira wiki markup in description files expecting it to render.
154+
6. **ADF is required for formatted descriptions.** Reading descriptions via `--json` returns Atlassian Document Format (nested JSON). Jira Cloud's editor is ADF-native — plain text and Jira wiki markup (`h1.`, `*bold*`) both render as literal characters in the UI. For formatted descriptions, fill a wiki markup template then run `scripts/jira-wiki-to-adf.py <input.txt> <output.json>` to convert to ADF, then pass via `--description-file`. Both `acli create` and `acli edit` accept ADF JSON via `--description-file`. Do not use Jira wiki markup in description files expecting it to render.
155155
7. **Acceptance Criteria field is almost always null.** Scan the description for "Requirements", "Acceptance Criteria", or bullet-style criteria instead of checking `customfield_10718`.
156156
8. **`--enrich` is MANDATORY for custom fields AND labels.** Both `acli search --json` and `acli view KEY --json` (without `--fields "*all"`) return only basic fields (assignee, issuetype, priority, status, summary). Labels, story points, team, sprint, size, and components will all appear as empty/null — looking like the data isn't set when it actually is. Always use `scripts/parse_issues.py --enrich` to get custom field data. Skipping `--enrich` is the #1 cause of false "missing data" reports.
157157
9. **`acli` cannot set arbitrary custom fields.** `acli jira workitem edit` does not have a `--custom` flag. Fields like Team, Size, Story Points, and Release Note Type can only be updated via the Jira REST API. Use `PUT /rest/api/3/issue/{key}` with the field payload (see `references/rest-api-fallback.md` for curl examples and payload formats). Find the token file at `.jira-token` next to the `acli` executable (discover the path with `readlink -f "$(which acli)"` or `where acli`). Never read the token file into context.

skills/rhdh-jira/references/acli-commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ acli jira filter get --id 10001
240240
| Labels | Not available via `--fields` | Array of strings |
241241
| Fix versions | Not available via `--fields` | Array of version objects |
242242

243-
When writing descriptions, use `--description "plain text"`. When reading, be aware `--json` returns ADF — don't try to round-trip it.
243+
When writing formatted descriptions, fill a wiki markup template then convert with `scripts/jira-wiki-to-adf.py` and pass via `--description-file` (see Gotcha #6 in SKILL.md). When reading, be aware `--json` returns ADF — don't try to round-trip it.
244244

245245
## Custom Fields and `--enrich`
246246

skills/rhdh-jira/references/templates.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ Implement SSO integration for admin console.
2626
...
2727
EOF
2828

29-
# Create the issue
29+
# Convert wiki markup to ADF (required — plain wiki text is not rendered by Jira)
30+
ISSUE_ADF=$(mktemp)
31+
python scripts/jira-wiki-to-adf.py issue-desc.txt "$ISSUE_ADF"
32+
33+
# Create the issue (note: --yes does not exist on create, see Gotcha #18)
3034
acli jira workitem create --project RHIDP --type Epic \
3135
--summary "SSO Integration for Admin Console" \
32-
--description-file issue-desc.txt \
33-
--assignee "@me" \
34-
--yes
36+
--description-file "$ISSUE_ADF" \
37+
--assignee "@me"
3538
```
3639

3740
## Field Requirements at Creation

skills/rhdh-jira/references/to-epic.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,20 @@ Run the pre-creation check from `references/duplicates.md`. Search RHIDP Epics (
8989
Fill the template. Then convert to ADF using the helper script (see Gotcha #6). `acli create` accepts ADF via `--description-file`:
9090

9191
```bash
92-
python scripts/jira-wiki-to-adf.py /tmp/epic-filled.txt > /tmp/epic-desc.adf.json
92+
EPIC_ADF=$(mktemp) # on Windows: use %TEMP% or Python tempfile
93+
python scripts/jira-wiki-to-adf.py /tmp/epic-filled.txt "$EPIC_ADF"
9394
```
9495

9596
Create the issue — note `--priority`, `--component`, and `--yes` do not exist on `create` (see Gotcha #18):
9697

9798
```bash
9899
acli jira workitem create --project RHIDP --type Epic \
99100
--summary "Epic summary" \
100-
--description-file /tmp/epic-desc.adf.json \
101+
--description-file "$EPIC_ADF" \
101102
--assignee "ACCOUNT_ID"
102103
```
103104

104-
Then set priority, components, size, and parent Feature link together in one REST call. Cross-project parent links use `customfield_10018`, not `parent` (see Gotcha #16):
105+
Then set priority, components, size, and parent Feature link together in one REST call. Cross-project parent links accept either `customfield_10018` or `parent.key` — do not use `issuelinks` (see Gotcha #16):
105106

106107
```bash
107108
curl -s -X PUT -u "$AUTH" -H "Content-Type: application/json" \

skills/rhdh-jira/references/to-feature.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,16 @@ If a likely duplicate Feature is found, present it and ask: "This may already ex
9999
Fill the template with grill results. Save to a temp file. Then convert to ADF using the helper script (see Gotcha #6). `acli create` accepts ADF via `--description-file`:
100100

101101
```bash
102-
python scripts/jira-wiki-to-adf.py /tmp/feature-filled.txt > /tmp/feature-desc.adf.json
102+
FEATURE_ADF=$(mktemp) # on Windows: use %TEMP% or Python tempfile
103+
python scripts/jira-wiki-to-adf.py /tmp/feature-filled.txt "$FEATURE_ADF"
103104
```
104105

105106
Create the issue — note `--priority` and `--yes` do not exist on `create` (see Gotcha #18):
106107

107108
```bash
108109
acli jira workitem create --project RHDHPLAN --type Feature \
109110
--summary "Feature summary" \
110-
--description-file /tmp/feature-desc.adf.json \
111+
--description-file "$FEATURE_ADF" \
111112
--assignee "ACCOUNT_ID" \
112113
--label "rhdh-2.1-candidate"
113114
```

skills/rhdh-jira/references/to-issue.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ Run the pre-creation check from `references/duplicates.md`. Scope to the target
122122
Fill the appropriate template (`assets/templates/story.txt`, `task.txt`, or `bug.txt`) with grill results, then convert to ADF using the helper script (see Gotcha #6). `acli create` accepts ADF via `--description-file`:
123123

124124
```bash
125-
python scripts/jira-wiki-to-adf.py /tmp/story-filled.txt > /tmp/story-desc.adf.json
125+
ISSUE_ADF=$(mktemp) # on Windows: use %TEMP% or Python tempfile
126+
python scripts/jira-wiki-to-adf.py /tmp/story-filled.txt "$ISSUE_ADF"
126127
```
127128

128129
Create the issue — note `--priority`, `--component`, and `--yes` do not exist on `create` (see Gotcha #18):
@@ -131,18 +132,18 @@ Create the issue — note `--priority`, `--component`, and `--yes` do not exist
131132
# Story
132133
acli jira workitem create --project RHIDP --type Story \
133134
--summary "Story summary" \
134-
--description-file /tmp/story-desc.adf.json \
135+
--description-file "$ISSUE_ADF" \
135136
--assignee "ACCOUNT_ID"
136137

137138
# Bug (different project)
138139
acli jira workitem create --project RHDHBUGS --type Bug \
139140
--summary "Bug summary" \
140-
--description-file /tmp/bug-desc.adf.json
141+
--description-file "$ISSUE_ADF"
141142

142143
# Spike (Task with prefix)
143144
acli jira workitem create --project RHIDP --type Task \
144145
--summary "SPIKE: Research multi-source catalog merging" \
145-
--description-file /tmp/spike-desc.adf.json \
146+
--description-file "$ISSUE_ADF" \
146147
--assignee "ACCOUNT_ID"
147148
```
148149

@@ -154,7 +155,7 @@ curl -s -X PUT -u "$AUTH" -H "Content-Type: application/json" \
154155
"fields": {
155156
"priority": {"name": "Major"},
156157
"components": [{"name": "Plugins"}],
157-
"story_points": 5
158+
"customfield_10028": 5
158159
}
159160
}' \
160161
"https://redhat.atlassian.net/rest/api/3/issue/RHIDP-YYY"

skills/rhdh-jira/scripts/jira-wiki-to-adf.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import json
1414
import re
15-
import sys
1615

1716

1817
def parse_inline(text):
@@ -184,17 +183,22 @@ def convert(wiki):
184183

185184

186185
if __name__ == "__main__":
187-
if len(sys.argv) < 2:
188-
print("Usage: jira-wiki-to-adf.py <input.txt> [output.json]", file=sys.stderr)
189-
sys.exit(1)
186+
import argparse
190187

191-
with open(sys.argv[1]) as f:
188+
parser = argparse.ArgumentParser(
189+
description="Convert Jira wiki markup to Atlassian Document Format (ADF) JSON."
190+
)
191+
parser.add_argument("input", help="Input file containing Jira wiki markup")
192+
parser.add_argument("output", nargs="?", help="Output JSON file (default: stdout)")
193+
args = parser.parse_args()
194+
195+
with open(args.input, encoding="utf-8") as f:
192196
wiki = f.read()
193197

194-
output = json.dumps(convert(wiki), ensure_ascii=False)
198+
result = json.dumps(convert(wiki), ensure_ascii=False)
195199

196-
if len(sys.argv) >= 3:
197-
with open(sys.argv[2], "w") as f:
198-
f.write(output)
200+
if args.output:
201+
with open(args.output, "w", encoding="utf-8") as f:
202+
f.write(result)
199203
else:
200-
print(output)
204+
print(result)

0 commit comments

Comments
 (0)