Pull a Jira issue into a local folder
One job: an issue key goes in, a folder of readable files comes out. One issue,
one folder, everything in it — fields, comments, subtasks, worklogs, attachments, and
any Confluence page the issue links to.
<skill-path> below is this skill's directory.
The seven rules
1. Pull only. This skill never creates, edits, transitions, comments on,
assigns, or deletes anything in Jira. Every request reads: every REST call is a GET,
and the one POST is a GraphQL query — the only way to read the deployment list,
which no REST endpoint serves. Nothing here mutates anything. If the user wants a
write, say so plainly and route to atlassian-rest — do not improvise it here.
2. Config first, and the config records the user's ticks. No
.jira.config.json means run workflows/setup.md before
anything else. Setup's five decisions — project, issue types, fields per type,
board, output — are the user's, and each is settled by a box in a file that
setup-choices.mjs generates from the API's own answer. The agent fetches the
options and records what the user did — never narrowing a list, summarising it, or
marking one option as recommended before they have seen all of it.
Only the starting state differs. Project, issue types, board and output arrive
unticked, so a tick is the answer. A per-type fields file arrives fully
ticked, because its options are the fields that issue type's screen shows — Jira's
own answer to what the type holds — so keeping it all is the honest default and the
user's job is to untick what they do not want. Either way their edit is the decision,
and a fresh file is not one.
Setup also records one thing that is not a tick file: which of this site's custom
fields fills each metadata row (project.metaFields). Propose that mapping from the
field list already on screen, then let the user confirm it. An unmapped row is
written —, never guessed.
3. One part per call. fetch-issue.mjs writes one document per run. Never
reach for a single call that fetches everything: the parts are separate so each
can be checked while it is still small enough to check.
4. Complete or failed, never partial. Every comment and every worklog entry is
written. Any list read from the API is paged to the server's own total, and the
count is asserted before the file is written. A file holding one page of a longer
list is a failed pull, not a pull with a caveat. Never read a list out of
fields=*all — that response is capped, silently.
The same rule decides when a pull is done: the folder is the evidence, not the
report. An agent that says it finished, or that reports itself idle, has stated a
hypothesis. Check the counts against Jira before believing it — and when many issues
are in flight, stop the agent before reading its folder, because idle is reported
while a write is still in flight.
5. Every document is versioned, and every JSON file is validated. Markdown
declares schema: in its frontmatter; JSON declares $schema as its first key. In
json or both mode, check-json.mjs must exit clean on every JSON file before
the pull is reported done. A file that fails validation, or carries no $schema,
is a failed pull. A pulled Confluence page is markdown in every mode, so
check-markdown.mjs covers it and check-json.mjs never runs on it.
6. The format pass changes formatting, never words. Layout, headings, table
syntax, list markers, unfilled boilerplate: all fair game. Wording, terminology,
spelling, code, ids, dates, names, URLs: never. A ticket written in mixed
languages, or with a typo in an acceptance criterion, stays exactly as written —
that is what the ticket says. The one exception is image alt text, written from
what the image shows. Detail in
references/refactor-guide.md.
Within that boundary the target is a document a person can read, not a document
the checker accepts. check-markdown.mjs covers what someone thought to automate,
so a clean exit is the floor: the agent still reads the file as the reader will and
fixes what renders wrong or breaks the reading, whether or not a rule names it. A
clean gate on a visibly badly formatted file is a failed part.
7. Write for someone who has never seen the issue or the code. The reader
cannot open Jira, has not read the repository, and does not know the project's
shorthand. An unexplained ticket key, field name, or file path costs them a
lookup they cannot make. Name what a thing is, once, in a short clause.
Pick the workflow
| The request is… |
Workflow |
| "pull PROJ-123", "fetch this story", a pasted issue URL |
workflows/pull.md |
| "sync sprint 62", "pull every bug in the backlog", any JQL or sprint |
workflows/bulk-pull.md |
No .jira.config.json exists yet, or a new issue type needs configuring |
workflows/setup.md |
Setup runs once per project. Pull runs every time after that.
The scripts
All are Node 18+, zero dependencies, run from anywhere inside the workspace.
| Script |
Does |
config.mjs |
Finds, reads and validates .jira.config.json. path, validate, instructions [TYPE], types, type <TYPE>, meta, where [KEY], and show for the whole file — thousands of lines on a real project, so prefer a narrower one |
jira-api.mjs |
Credentials, the one paged GET, and a read-only GraphQL query for what REST will not serve. whoami, get, count, pages |
inspect.mjs |
Setup's read-only probes: projects, issue-types, fields, sample, board, count-type |
setup-choices.mjs |
Setup's decision files. generate/read <kind> [--type T] [--sample KEY], status. Project, issue types, board and output arrive unticked; a per-type fields file arrives fully ticked. --force re-generates over existing ticks. Exit 2 = nothing ticked, 3 = no file, 4 = would erase a tick |
fetch-issue.mjs |
One part per run: <KEY> with --part set to content, comments, tasks, worklogs or development |
fetch-confluence.mjs |
--issue KEY pulls the pages that issue links, into its folder |
check-markdown.mjs |
18 rendering and readability rules. Exit 1 while any remain |
check-json.mjs |
Validates a JSON file against the schema its $schema names |
Run any of them with --help for the full flag list.
Three more files in scripts/ are libraries with no command line, imported by the
scripts above: adf.mjs converts Atlassian Document Format to markdown,
confluence-format.mjs does the same for a wiki page, and dev-status.mjs reads
the development panel.
What the config holds
.jira.config.json sits at the workspace root. It runs to thousands of lines,
almost all of it issueTypes, so read it with config.mjs types,
config.mjs type <TYPE>, config.mjs meta and config.mjs where rather than
config.mjs show.
| Block |
Decides |
jira.domain, auth.envVars |
Which site, and which two environment variables hold the credentials. No token is ever in the file |
project.key, project.boards |
Which project. The boards are a note for a human; nothing reads them |
project.metaFields |
Which custom field fills each row of the metadata table. Unmapped means the row reads — |
project.checklistFields |
Which fields the Checklist app writes into. Unmapped means no Checklist section |
instructions |
What the project knows that the API cannot say. Read before every pull, and passed to the subagent |
output.dir, output.mode, output.assets |
Where the folders go, whether markdown or JSON or both, and which attachments come down |
confluence |
Whether linked pages are pulled, into which subfolder, and how deep |
issueTypes |
Per issue type: its docType, its own instructions, and the ticked fields in the order they are written. This is the whole size of the file |
What a pulled issue looks like
<output.dir>/PROJ-123/
content.md the issue: metadata, description, the ticked fields, checklist,
attachments, subtask list, links, development summary,
Linked Documents
comments.md every comment, oldest first
tasks.md every subtask in full (absent when the issue has none)
worklogs.md every worklog entry, on the issue and on every subtask
development.md pull requests, commits, builds and deployments, grouped by
repository (absent when the issue has no development activity)
assets/ attachments, shared by the files above
confluence/ linked pages, each with its own assets/ (only when linked)
In json mode the same folder holds content.json, comments.json,
tasks.json, worklogs.json, development.json. In both mode it holds both
sets.
Credentials
The token never goes in the config. Two environment variables, best set in a shell
profile so every node call sees them:
export ATLASSIAN_EMAIL="you@example.com"
export ATLASSIAN_API_TOKEN="your-api-token" # id.atlassian.com/manage-profile/security/api-tokens
The site itself is jira.domain in the config. Check all three at once with
node <skill-path>/scripts/jira-api.mjs whoami.
Nothing here is configured for your Jira yet
Every example in these documents uses PROJ-123 and your-site.atlassian.net, and
every field id reads customfield_NNNNN. Those are placeholders, not defaults.
Four things differ on every Jira site, and the skill hard-codes none of them:
- Project key and issue type names. A project commonly has dozens of types, most of
them names the team invented. The config is keyed by the type's own name, so any
of them works.
- Custom field ids. Jira gives the same field a different
customfield_NNNNN id
on every site, so an id copied from someone else's config points at the wrong
field — or at nothing.
- Which field fills which metadata row. Team, Sprint, Story Points, Due Date and
the development panel all live in custom fields.
project.metaFields maps them; a
row left unmapped is written — rather than guessed.
- Which fields the checklist app writes into.
project.checklistFields maps the
three, and content.md rebuilds them as one list of checkboxes. All three are
mapped together or none is. None of them carries the live per-item state — the
YAML field is written once, at issue creation — so the real count comes from the
issue's checklist property and is written as one line above the list, with every
box left unticked.
workflows/setup.md asks Jira for all of it and records what
the user ticks. Run it once before the first pull.
Reference documentation
Load these when the moment calls for them, not upfront. The subagent's set is
pointed at from templates/subagent-instruction.md,
so it reads a file when a step names it rather than being handed all of them.
| Reference |
Who reads it |
When |
references/format-guide.md |
The fetch-and-format subagent |
Running the format pass: every checker rule, its defect, its fix |
references/refactor-guide.md |
The fetch-and-format subagent |
The formatting/wording boundary, worked examples, what to leave alone |
references/jira-fields.md |
Main thread, during setup |
Choosing fields, reading a customfield_NNNNN, why some fields are excluded |
references/confluence-links.md |
The subagent, at the Confluence part |
Where a page link hides on an issue, and how deep to follow it |
references/output-json.md |
The subagent, in json or both mode |
The JSON shape per document, and how to version a layout change |
templates/ |
Whoever changes a layout |
The layout each document follows, with its version |
Errors
| Error |
Cause |
Fix |
No .jira.config.json found |
Never set up here |
Run workflows/setup.md |
| A script prints nothing and exits 0 |
The script loaded as a library instead of running: its main() guard compared raw paths, which disagree when the skill directory is a symlink. The guard now resolves both sides with realpathSync |
Nothing to do. If it recurs, a script has been given a raw process.argv[1] === import.meta.url guard again — use isMainScript instead |
Nothing ticked in … (exit 2) |
A decision file the user has not filled in |
Stop. Tell them which file waits. An empty file is not a vote for the default |
No decision file at … (exit 3) |
read before generate |
Generate it, hand the user the path, wait for the tick |
does not match schemas/config.schema.json |
Config edited by hand into an invalid shape |
Read the reported paths and fix them; the message names each one |
Issue type "X" is not configured |
A type the config has no entry for |
Add it — the message prints the inspect.mjs fields command to run |
401 Unauthorized |
Token wrong or expired |
Regenerate it, re-export it |
403 Forbidden |
The account cannot see this issue or project |
Ask for access; do not work around it |
404 Not Found |
Wrong key, or wrong jira.domain |
Check both. whoami proves the domain |
429 Too Many Requests |
Rate limited |
Wait, then re-run the part that failed |
yielded N of M row(s) |
A list could not be read completely |
Re-run that part. Never accept the short file — see Rule 4 |
only N distinct id(s) |
Pages overlapped |
Re-run that part; the document would have held duplicates |
A part failed and left assets/ with no document |
The run stopped mid-way |
Re-run the same command. Writes overwrite, so re-fetching is always safe |
fetch failed with no status code |
Network, not Jira. whoami fails the same way |
Wait, re-run the part. Distinguish it from 429 before treating it as rate limiting |
A page sits in confluence/ that content.md never links |
The subagent skipped the ## Linked Documents edit. No gate can see this |
Add the link, then re-gate content.md |
| Gate reports findings on a folder that is correct |
The file list swept assets/ — those .md files are attachments, uploaded verbatim |
Use find <folder> -name '*.md' -not -path '*/assets/*' |
| Gate reports nothing at all, on every issue |
A *.md glob matched nothing and zsh aborted the whole command |
Use find, never a glob |
| An agent reports idle with an incomplete folder |
Idle is reported while a write is still in flight, and on failure too |
Stop the agent, then read the folder. Verify counts against Jira — see Rule 4 |
1---2name: jira-to-local3description: Use when the user wants Jira issues pulled onto disk as local files — "pull PROJ-123", "fetch this story", "get that ticket locally", "download the bug", "save this issue and its comments", "grab the NFR", "read this ticket offline", or a pasted Jira browse URL with nothing else asked of it. Also for many at once — "sync all tickets in sprint 62", "pull every bug in the sprint", "fetch the whole backlog locally", "download all of team X's issues". Covers any issue type — story, bug, epic, subtask, or whatever types the project has invented for itself. Also when the issue links a Confluence page that should come down with it. This skill only reads: for creating, editing, transitioning, commenting, searching, or pushing anything back to Jira, use atlassian-rest instead.4---5
6# Pull a Jira issue into a local folder
7
8One job: an issue key goes in, a folder of readable files comes out. One issue,
9one folder, everything in it — fields, comments, subtasks, worklogs, attachments, and
10any Confluence page the issue links to.
11
12`<skill-path>` below is this skill's directory.
13
14## The seven rules
15
16**1. Pull only.** This skill never creates, edits, transitions, comments on,
17assigns, or deletes anything in Jira. Every request reads: every REST call is a GET,
18and the one POST is a GraphQL *query* — the only way to read the deployment list,
19which no REST endpoint serves. Nothing here mutates anything. If the user wants a
20write, say so plainly and route to `atlassian-rest` — do not improvise it here.
21
22**2. Config first, and the config records the user's ticks.** No
23`.jira.config.json` means run [`workflows/setup.md`](workflows/setup.md) before
24anything else. Setup's five decisions — project, issue types, fields per type,
25board, output — are the user's, and each is settled by a box in a file that
26`setup-choices.mjs` generates from the API's own answer. The agent fetches the
27options and records what the user did — never narrowing a list, summarising it, or
28marking one option as recommended before they have seen all of it.
29
30Only the starting state differs. Project, issue types, board and output arrive
31**unticked**, so a tick is the answer. A per-type fields file arrives **fully
32ticked**, because its options are the fields that issue type's screen shows — Jira's
33own answer to what the type holds — so keeping it all is the honest default and the
34user's job is to untick what they do not want. Either way their edit is the decision,
35and a fresh file is not one.
36
37Setup also records one thing that is not a tick file: which of this site's custom
38fields fills each metadata row (`project.metaFields`). Propose that mapping from the
39field list already on screen, then let the user confirm it. An unmapped row is
40written `—`, never guessed.
41
42**3. One part per call.** `fetch-issue.mjs` writes one document per run. Never
43reach for a single call that fetches everything: the parts are separate so each
44can be checked while it is still small enough to check.
45
46**4. Complete or failed, never partial.** Every comment and every worklog entry is
47written. Any list read from the API is paged to the server's own `total`, and the
48count is asserted before the file is written. **A file holding one page of a longer
49list is a failed pull, not a pull with a caveat.** Never read a list out of
50`fields=*all` — that response is capped, silently.
51
52The same rule decides when a pull is done: **the folder is the evidence, not the
53report.** An agent that says it finished, or that reports itself idle, has stated a
54hypothesis. Check the counts against Jira before believing it — and when many issues
55are in flight, stop the agent before reading its folder, because idle is reported
56while a write is still in flight.
57
58**5. Every document is versioned, and every JSON file is validated.** Markdown
59declares `schema:` in its frontmatter; JSON declares `$schema` as its first key. In
60`json` or `both` mode, `check-json.mjs` must exit clean on every JSON file before
61the pull is reported done. A file that fails validation, or carries no `$schema`,
62is a failed pull. A pulled Confluence page is markdown in every mode, so
63`check-markdown.mjs` covers it and `check-json.mjs` never runs on it.
64
65**6. The format pass changes formatting, never words.** Layout, headings, table
66syntax, list markers, unfilled boilerplate: all fair game. Wording, terminology,
67spelling, code, ids, dates, names, URLs: never. A ticket written in mixed
68languages, or with a typo in an acceptance criterion, stays exactly as written —
69that is what the ticket says. The one exception is image alt text, written from
70what the image shows. Detail in
71[`references/refactor-guide.md`](references/refactor-guide.md).
72
73Within that boundary the target is a document a person can read, not a document
74the checker accepts. `check-markdown.mjs` covers what someone thought to automate,
75so a clean exit is the floor: the agent still reads the file as the reader will and
76fixes what renders wrong or breaks the reading, whether or not a rule names it. A
77clean gate on a visibly badly formatted file is a failed part.
78
79**7. Write for someone who has never seen the issue or the code.** The reader
80cannot open Jira, has not read the repository, and does not know the project's
81shorthand. An unexplained ticket key, field name, or file path costs them a
82lookup they cannot make. Name what a thing is, once, in a short clause.
83
84## Pick the workflow
85
86| The request is… | Workflow |
87|---|---|
88| "pull PROJ-123", "fetch this story", a pasted issue URL | [`workflows/pull.md`](workflows/pull.md) |
89| "sync sprint 62", "pull every bug in the backlog", any JQL or sprint | [`workflows/bulk-pull.md`](workflows/bulk-pull.md) |
90| No `.jira.config.json` exists yet, or a new issue type needs configuring | [`workflows/setup.md`](workflows/setup.md) |
91
92Setup runs once per project. Pull runs every time after that.
93
94## The scripts
95
96All are Node 18+, zero dependencies, run from anywhere inside the workspace.
97
98| Script | Does |
99|---|---|
100| `config.mjs` | Finds, reads and validates `.jira.config.json`. `path`, `validate`, `instructions [TYPE]`, `types`, `type <TYPE>`, `meta`, `where [KEY]`, and `show` for the whole file — thousands of lines on a real project, so prefer a narrower one |
101| `jira-api.mjs` | Credentials, the one paged GET, and a read-only GraphQL query for what REST will not serve. `whoami`, `get`, `count`, `pages` |
102| `inspect.mjs` | Setup's read-only probes: `projects`, `issue-types`, `fields`, `sample`, `board`, `count-type` |
103| `setup-choices.mjs` | Setup's decision files. `generate`/`read <kind> [--type T] [--sample KEY]`, `status`. Project, issue types, board and output arrive unticked; a per-type fields file arrives fully ticked. `--force` re-generates over existing ticks. Exit 2 = nothing ticked, 3 = no file, 4 = would erase a tick |
104| `fetch-issue.mjs` | One part per run: `<KEY>` with `--part` set to `content`, `comments`, `tasks`, `worklogs` or `development` |
105| `fetch-confluence.mjs` | `--issue KEY` pulls the pages that issue links, into its folder |
106| `check-markdown.mjs` | 18 rendering and readability rules. Exit 1 while any remain |
107| `check-json.mjs` | Validates a JSON file against the schema its `$schema` names |
108
109Run any of them with `--help` for the full flag list.
110
111Three more files in `scripts/` are libraries with no command line, imported by the
112scripts above: `adf.mjs` converts Atlassian Document Format to markdown,
113`confluence-format.mjs` does the same for a wiki page, and `dev-status.mjs` reads
114the development panel.
115
116## What the config holds
117
118`.jira.config.json` sits at the workspace root. It runs to thousands of lines,
119almost all of it `issueTypes`, so read it with `config.mjs types`,
120`config.mjs type <TYPE>`, `config.mjs meta` and `config.mjs where` rather than
121`config.mjs show`.
122
123| Block | Decides |
124|---|---|
125| `jira.domain`, `auth.envVars` | Which site, and which two environment variables hold the credentials. No token is ever in the file |
126| `project.key`, `project.boards` | Which project. The boards are a note for a human; nothing reads them |
127| `project.metaFields` | Which custom field fills each row of the metadata table. Unmapped means the row reads `—` |
128| `project.checklistFields` | Which fields the Checklist app writes into. Unmapped means no Checklist section |
129| `instructions` | What the project knows that the API cannot say. Read before every pull, and passed to the subagent |
130| `output.dir`, `output.mode`, `output.assets` | Where the folders go, whether markdown or JSON or both, and which attachments come down |
131| `confluence` | Whether linked pages are pulled, into which subfolder, and how deep |
132| `issueTypes` | Per issue type: its `docType`, its own instructions, and the ticked fields in the order they are written. This is the whole size of the file |
133
134## What a pulled issue looks like
135
136```
137<output.dir>/PROJ-123/
138 content.md the issue: metadata, description, the ticked fields, checklist,
139 attachments, subtask list, links, development summary,
140 Linked Documents
141 comments.md every comment, oldest first
142 tasks.md every subtask in full (absent when the issue has none)
143 worklogs.md every worklog entry, on the issue and on every subtask
144 development.md pull requests, commits, builds and deployments, grouped by
145 repository (absent when the issue has no development activity)
146 assets/ attachments, shared by the files above
147 confluence/ linked pages, each with its own assets/ (only when linked)
148```
149
150In `json` mode the same folder holds `content.json`, `comments.json`,
151`tasks.json`, `worklogs.json`, `development.json`. In `both` mode it holds both
152sets.
153
154## Credentials
155
156The token never goes in the config. Two environment variables, best set in a shell
157profile so every `node` call sees them:
158
159```bash
160export ATLASSIAN_EMAIL="you@example.com"
161export ATLASSIAN_API_TOKEN="your-api-token" # id.atlassian.com/manage-profile/security/api-tokens
162```
163
164The site itself is `jira.domain` in the config. Check all three at once with
165`node <skill-path>/scripts/jira-api.mjs whoami`.
166
167## Nothing here is configured for your Jira yet
168
169Every example in these documents uses `PROJ-123` and `your-site.atlassian.net`, and
170every field id reads `customfield_NNNNN`. **Those are placeholders, not defaults.**
171Four things differ on every Jira site, and the skill hard-codes none of them:
172
173- **Project key and issue type names.** A project commonly has dozens of types, most of
174 them names the team invented. The config is keyed by the type's own name, so any
175 of them works.
176- **Custom field ids.** Jira gives the same field a different `customfield_NNNNN` id
177 on every site, so an id copied from someone else's config points at the wrong
178 field — or at nothing.
179- **Which field fills which metadata row.** Team, Sprint, Story Points, Due Date and
180 the development panel all live in custom fields. `project.metaFields` maps them; a
181 row left unmapped is written `—` rather than guessed.
182- **Which fields the checklist app writes into.** `project.checklistFields` maps the
183 three, and `content.md` rebuilds them as one list of checkboxes. All three are
184 mapped together or none is. None of them carries the live per-item state — the
185 YAML field is written once, at issue creation — so the real count comes from the
186 issue's `checklist` property and is written as one line above the list, with every
187 box left unticked.
188
189[`workflows/setup.md`](workflows/setup.md) asks Jira for all of it and records what
190the user ticks. Run it once before the first pull.
191
192## Reference documentation
193
194Load these when the moment calls for them, not upfront. The subagent's set is
195pointed at from [`templates/subagent-instruction.md`](templates/subagent-instruction.md),
196so it reads a file when a step names it rather than being handed all of them.
197
198| Reference | Who reads it | When |
199|---|---|---|
200| [`references/format-guide.md`](references/format-guide.md) | The fetch-and-format subagent | Running the format pass: every checker rule, its defect, its fix |
201| [`references/refactor-guide.md`](references/refactor-guide.md) | The fetch-and-format subagent | The formatting/wording boundary, worked examples, what to leave alone |
202| [`references/jira-fields.md`](references/jira-fields.md) | Main thread, during setup | Choosing fields, reading a `customfield_NNNNN`, why some fields are excluded |
203| [`references/confluence-links.md`](references/confluence-links.md) | The subagent, at the Confluence part | Where a page link hides on an issue, and how deep to follow it |
204| [`references/output-json.md`](references/output-json.md) | The subagent, in `json` or `both` mode | The JSON shape per document, and how to version a layout change |
205| [`templates/`](templates/) | Whoever changes a layout | The layout each document follows, with its version |
206
207## Errors
208
209| Error | Cause | Fix |
210|---|---|---|
211| `No .jira.config.json found` | Never set up here | Run [`workflows/setup.md`](workflows/setup.md) |
212| A script prints nothing and exits 0 | The script loaded as a library instead of running: its `main()` guard compared raw paths, which disagree when the skill directory is a symlink. The guard now resolves both sides with `realpathSync` | Nothing to do. If it recurs, a script has been given a raw `process.argv[1] === import.meta.url` guard again — use `isMainScript` instead |
213| `Nothing ticked in …` (exit 2) | A decision file the user has not filled in | Stop. Tell them which file waits. An empty file is not a vote for the default |
214| `No decision file at …` (exit 3) | `read` before `generate` | Generate it, hand the user the path, wait for the tick |
215| `does not match schemas/config.schema.json` | Config edited by hand into an invalid shape | Read the reported paths and fix them; the message names each one |
216| `Issue type "X" is not configured` | A type the config has no entry for | Add it — the message prints the `inspect.mjs fields` command to run |
217| `401 Unauthorized` | Token wrong or expired | Regenerate it, re-export it |
218| `403 Forbidden` | The account cannot see this issue or project | Ask for access; do not work around it |
219| `404 Not Found` | Wrong key, or wrong `jira.domain` | Check both. `whoami` proves the domain |
220| `429 Too Many Requests` | Rate limited | Wait, then re-run the part that failed |
221| `yielded N of M row(s)` | A list could not be read completely | Re-run that part. Never accept the short file — see Rule 4 |
222| `only N distinct id(s)` | Pages overlapped | Re-run that part; the document would have held duplicates |
223| A part failed and left `assets/` with no document | The run stopped mid-way | Re-run the same command. Writes overwrite, so re-fetching is always safe |
224| `fetch failed` with no status code | Network, not Jira. `whoami` fails the same way | Wait, re-run the part. Distinguish it from `429` before treating it as rate limiting |
225| A page sits in `confluence/` that `content.md` never links | The subagent skipped the `## Linked Documents` edit. No gate can see this | Add the link, then re-gate `content.md` |
226| Gate reports findings on a folder that is correct | The file list swept `assets/` — those `.md` files are attachments, uploaded verbatim | Use `find <folder> -name '*.md' -not -path '*/assets/*'` |
227| Gate reports nothing at all, on every issue | A `*.md` glob matched nothing and zsh aborted the whole command | Use `find`, never a glob |
228| An agent reports idle with an incomplete folder | Idle is reported while a write is still in flight, and on failure too | Stop the agent, then read the folder. Verify counts against Jira — see Rule 4 |