# analysis-jira - an analysis document, read as work

`/multi-agent:analysis-jira` turns a rendered analysis document into a Jira tree.
Two files do it, and the split is the design:

| File | Does | Network |
|---|---|---|
| `scripts/analysis-story-tree.mjs` | decides WHAT to create, checks coverage, assigns identity | none |
| `lib/analysis-jira-write.sh` | creates it | yes |

Planning is deterministic and testable offline; creating issues is neither. Kept
apart, the file that can write to a tracker is small enough to read in one
sitting.

`jira-publish.sh` could not be extended to do this: it writes a comment or a
description on an issue that already exists. The only creation path in the repo
was a curl hand-written inside a markdown instruction, which is what this
replaces.

## The marker gate runs first, before any network call

Zero `EKLENECEK`, zero `TBD`, no open Section 20 row. A document with an open
placeholder is not a plan, and turning it into a tree publishes the gap as work
somebody is now assigned. Refusal is exit 4 and it names
`/multi-agent:analysis-resolve` as the step.

This is the same closure contract `status: final` enforces in
`validate-analysis-doc.mjs`, applied at the point the document leaves the
analysis world.

## Coverage is two-way, and the second direction is the useful one

| Direction | Catches |
|---|---|
| every defined id appears in some story | a dropped requirement |
| every cited id is defined in the document | an **invented story** - a node with no requirement behind it |

No forward check can see the second one, and it is the failure mode of building
a tree from a model's reading rather than from the document's own ids.

The atom is `BR-<slug>-NN` in the global profile and `FG-NN` in the corporate
one; the group is the `BR-<slug>` prefix, or `UC-NN`. Locked 31 guarantees both
exist, which is why the tree can be derived rather than invented.

`coverageOf()` is exported and tested directly. The planner cannot emit an
invented id - it derives every `sourceIds` from the defined set - so a test that
fabricates one and re-checks it with its own logic proves nothing about the
shipped code. The backward direction guards the boundary where a plan arrives
from somewhere else: hand-edited, resumed from an older format, or produced by
something that read the prose instead of the ids.

## An unverifiable run is allowed; looking verified is not

A lite document may carry no ids at all. Coverage then cannot run, and the
verdict is `unverifiable`, never `ok`. It appears:

- on its own line in the preview, where a reader looks for `ok`
- as a separate fourth approval option, not folded into `Approve`
- in the writer's own output, and beside every key in the ledger

Such a document still plans work, from its user-story sub-sections. That is not
a convenience: without it the no-atom document produced no stories, exited
"nothing to plan", and the `unverifiable` branch was unreachable - a case the
code claimed to handle and never could.

## Identity is a label, not a title

Each node carries `<labelPrefix>-<10 hex>`, hashed from the document id
(`evidence_digest`) plus the node's own source ids. A second run finds its tree
back with one JQL search on those labels.

Titles were the obvious key and are the wrong one: they get edited, and matching
on them breaks exactly when someone has improved the wording. The label lives
server-side, so it survives a new machine, a deleted `~/.claude`, and a **second
analyst** - who is precisely the person positioned to open a duplicate tree.

## The write is ledgered

`~/.claude/logs/multi-agent/_analysis-jira/<docId>.jsonl`. An `intent` line
before each POST, the key after the response. A crash between them leaves an
intent with no key; the next run sees that and searches by label before sending
anything. Without the ledger the failure mode is not "the run stopped" but "the
run stopped and the retry made a second tree", which is the expensive one.

Every line also carries the coverage verdict, so "was this tree checked?" stays
answerable from the tree's own trail.

## An existing node is skipped, never updated

Jira has no backup path for fields other than description. Rewriting a body an
engineer has since edited would repeat, at tree scale, the defect that made
`jira-publish.sh` take backups in the first place.

## Every site-specific name is a VALUE, never a schema key

`prefs.global.issueTree` holds the vocabulary. A key is a published literal and
this schema ships to everyone, so a site's component, team and issue-type names
are values the site fills in:

- `channelComponents` / `channelTeams` are free-form maps: the key is the
  channel, the value is the site's own name for it
- `subtaskRoles` ships **empty**. An empty list is an instruction to look at how
  this board actually splits work; a ready-made list would be the guess most
  worth avoiding
- `subtaskIssueType: null` means discover it - `createmeta` returns whichever
  type carries `subtask: true`, under whatever name the site gave it. Same rule
  as `features/jira-context.md`: the type travels as it comes from Jira and is
  never a matching criterion in code

The preview prints every field beside the pref key it came from, so a wrong
setting is visible before the writes rather than in Jira afterwards.

## Auth

`lib/_jira-auth.sh` holds one host-and-token resolution and one curl idiom: the
token reaches curl through a `-K` config on process substitution and never
touches argv, a log, or `ps`. That idiom is the part most easily retyped badly,
which is why the third writer got a shared copy instead of a third hand-written
one.

**Only this writer consumes it today.** `jira-publish.sh` and `issue-fetcher.sh`
still carry their own resolution, and retrofitting them is real work rather than
a rename - `issue-fetcher.sh` resolves per-account token keys that this helper
does not model yet. So the file is the shared copy going forward, not a
consolidation that has already happened, and saying otherwise would describe a
cleanup nobody did. The leak property itself is asserted on all three callers
independently in `smoke-analysis-jira.sh`, which is the part that must hold
whether or not they ever share code.
