@codyswann/lisa 2.159.0 → 2.159.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/skills/atlassian-access/SKILL.md +54 -18
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/atlassian-access/SKILL.md +54 -18
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/atlassian-access/SKILL.md +54 -18
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/atlassian-access/SKILL.md +54 -18
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/atlassian-access/SKILL.md +54 -18
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"lodash": ">=4.18.1"
|
|
85
85
|
},
|
|
86
86
|
"name": "@codyswann/lisa",
|
|
87
|
-
"version": "2.159.
|
|
87
|
+
"version": "2.159.2",
|
|
88
88
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
89
89
|
"main": "dist/index.js",
|
|
90
90
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atlassian-access
|
|
3
|
-
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation
|
|
3
|
+
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation, binding JIRA writes to the configured cloudId via Atlassian REST whenever token auth is available and using acli only for reads or as a guarded fallback. For non-write acli operations, acli is used when installed and switchable to a profile matching the configured site; mismatched active profiles are skipped only after switch plus re-verification fails."
|
|
4
4
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,21 +35,26 @@ EMAIL=$(jq -r '.atlassian.email // empty' .lisa.config.local.json 2>/dev/null)
|
|
|
35
35
|
[ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set. Run /lisa:setup:atlassian." >&2; exit 1; }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are
|
|
38
|
+
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are switched to the configured profile when one exists, then skipped only if the switch fails or re-verification still mismatches.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
substrate=""
|
|
42
42
|
|
|
43
|
-
# Tier 1: acli
|
|
44
|
-
|
|
43
|
+
# Tier 1: acli for reads and non-write operations only.
|
|
44
|
+
#
|
|
45
|
+
# Do not choose acli for JIRA writes when curl/token auth is available. acli stores
|
|
46
|
+
# one machine-global active account and workitem writes cannot pin a cloudId per
|
|
47
|
+
# invocation, so switch-then-write is a TOCTOU risk in multi-account or concurrent
|
|
48
|
+
# sessions. Write operations prefer the cloudId-scoped REST URL below.
|
|
49
|
+
if [ "$OP_KIND" != "jira-write" ] && command -v acli >/dev/null 2>&1 && acli auth status >/dev/null 2>&1; then
|
|
45
50
|
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
51
|
+
if [ "$current_site" != "$SITE" ]; then
|
|
52
|
+
# acli installed but pointing at a different site. Try switching profiles.
|
|
53
|
+
acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1 || true
|
|
54
|
+
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
55
|
+
fi
|
|
46
56
|
if [ "$current_site" = "$SITE" ]; then
|
|
47
57
|
substrate="acli"
|
|
48
|
-
else
|
|
49
|
-
# acli installed but pointing at a different site. Try switching profiles.
|
|
50
|
-
if acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1; then
|
|
51
|
-
substrate="acli"
|
|
52
|
-
fi
|
|
53
58
|
fi
|
|
54
59
|
fi
|
|
55
60
|
|
|
@@ -114,7 +119,13 @@ public static class LisaCred {
|
|
|
114
119
|
esac
|
|
115
120
|
}
|
|
116
121
|
TOKEN=$(read_atlassian_token "$EMAIL")
|
|
117
|
-
[ -n "$TOKEN" ] && curl_available=true &&
|
|
122
|
+
[ -n "$TOKEN" ] && curl_available=true && {
|
|
123
|
+
if [ "$OP_KIND" = "jira-write" ]; then
|
|
124
|
+
substrate="curl"
|
|
125
|
+
else
|
|
126
|
+
: ${substrate:=curl}
|
|
127
|
+
fi
|
|
128
|
+
}
|
|
118
129
|
|
|
119
130
|
# Fail loudly with actionable remediation if nothing works.
|
|
120
131
|
if [ -z "$substrate" ]; then
|
|
@@ -164,7 +175,7 @@ Operation dispatch then uses `$substrate` for the primary route. If the operatio
|
|
|
164
175
|
|
|
165
176
|
### Step 2 — Connection-match check
|
|
166
177
|
|
|
167
|
-
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already
|
|
178
|
+
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already tries to switch mismatched acli profiles and verifies the result before selection. This step repeats the assertion before any operation runs — defensive in case the substrate state changed since selection.
|
|
168
179
|
|
|
169
180
|
Read configured site:
|
|
170
181
|
|
|
@@ -188,8 +199,22 @@ current_site=$(echo "$current" | jq -r '.site // empty')
|
|
|
188
199
|
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
189
200
|
|
|
190
201
|
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
191
|
-
# Profile mismatch — switch.
|
|
202
|
+
# Profile mismatch — switch, then re-verify. Do not trust the switch exit
|
|
203
|
+
# status alone because acli's active account is machine-global state.
|
|
192
204
|
acli auth switch --site "$site" ${email:+--email "$email"}
|
|
205
|
+
current=$(acli auth status --json 2>/dev/null)
|
|
206
|
+
current_site=$(echo "$current" | jq -r '.site // empty')
|
|
207
|
+
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
211
|
+
echo "Error: acli active site is '$current_site', but .lisa.config.json requires '$site'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
if [ -n "$email" ] && [ -n "$current_email" ] && [ "$current_email" != "$email" ]; then
|
|
216
|
+
echo "Error: acli active account is '$current_email', but .lisa.config.json requires '$email'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
217
|
+
exit 1
|
|
193
218
|
fi
|
|
194
219
|
```
|
|
195
220
|
|
|
@@ -257,18 +282,18 @@ Substrate column meanings:
|
|
|
257
282
|
- Multiple cells filled means tier ordering applies — try acli, then MCP, then curl, taking the first that has an adapter for the op AND is identity-matched.
|
|
258
283
|
- One cell means only that substrate can perform the op.
|
|
259
284
|
|
|
260
|
-
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA
|
|
285
|
+
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA curl writes use the cloudId-bound Atlassian gateway `https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/...`; JIRA curl reads may use either that gateway or `https://<SITE>/rest/api/3/...` after the token account check. Confluence uses `/wiki/rest/api/...` (v1) or `/api/v2/...` (v2).
|
|
261
286
|
|
|
262
287
|
| Operation | acli adapter | MCP adapter | curl adapter |
|
|
263
288
|
|---|---|---|---|
|
|
264
289
|
| **JIRA ops** | | | |
|
|
265
290
|
| `read-ticket key:<K>` | `acli jira workitem view <K> --fields '*all' --json` | `mcp__plugin_atlassian_atlassian__getJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>?fields=*all` |
|
|
266
|
-
| `write-ticket payload:<P>` (create) | `acli jira workitem create --from-json <P>` | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https
|
|
267
|
-
| `write-ticket payload:<P>` (edit) | `acli jira workitem edit <K> --from-json <P>` | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https
|
|
268
|
-
| `transition key:<K> to:<S>` | `acli jira workitem transition --key <K> --status "<S>" --yes` | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST
|
|
291
|
+
| `write-ticket payload:<P>` (create) | guarded fallback only: `acli jira workitem create --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue` body=`<P>` |
|
|
292
|
+
| `write-ticket payload:<P>` (edit) | guarded fallback only: `acli jira workitem edit <K> --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>` body=`<P>` |
|
|
293
|
+
| `transition key:<K> to:<S>` | guarded fallback only: `acli jira workitem transition --key <K> --status "<S>" --yes` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/transitions` |
|
|
269
294
|
| `transitions key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getTransitionsForJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>/transitions` |
|
|
270
|
-
| `comment key:<K> body:<B>` | `acli jira workitem comment add --key <K> --body "<B>"` | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https
|
|
271
|
-
| `link from:<K> to:<K2> type:<T>` | `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https
|
|
295
|
+
| `comment key:<K> body:<B>` | guarded fallback only: `acli jira workitem comment add --key <K> --body "<B>"` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/comment` |
|
|
296
|
+
| `link from:<K> to:<K2> type:<T>` | guarded fallback only: `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` + direction and tenant assertion (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issueLink` |
|
|
272
297
|
| `remote-links key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getJiraIssueRemoteIssueLinks` | `GET https://<SITE>/rest/api/3/issue/<K>/remotelink` |
|
|
273
298
|
| `search-issues jql:<J>` | `acli jira workitem search --jql "<J>" --json` | `mcp__plugin_atlassian_atlassian__searchJiraIssuesUsingJql` | `POST https://<SITE>/rest/api/3/search/jql` |
|
|
274
299
|
| `list-projects` | `acli jira project list --paginate --json` | `mcp__plugin_atlassian_atlassian__getVisibleJiraProjects` | `GET https://<SITE>/rest/api/3/project/search` |
|
|
@@ -291,6 +316,16 @@ Substrate column meanings:
|
|
|
291
316
|
|
|
292
317
|
**acli flag note:** acli's `--output` flag does not exist; the correct flag is `--json`. List commands require `--paginate` or `--limit` (no implicit fetch-all). `acli jira workitem view` defaults to a restricted field set (`key,issuetype,summary,status,assignee,description`), so `read-ticket` MUST pass `--fields '*all'` or an explicit equivalent that includes every downstream dependency: parent, subtasks, issue links, components, labels, priority, status, issue type, summary, description, fix versions, affected versions, attachments, comments, estimates, sprint/story-point fields, and project-required custom fields. Never rely on the default view fields; they hide parent/components/labels and corrupt leaf-only, relationship-search, build-ready, and required-custom-field gates. Several documented adapters are nominal — verify against `acli <subcmd> --help` before relying on them. When acli's adapter is broken or missing for a specific op, fall through to MCP (if identity-matched) then curl per the tier ordering.
|
|
293
318
|
|
|
319
|
+
**JIRA write tenant-safety rule:** create, edit, transition, comment, and link are write operations. They MUST prefer the curl adapter whenever token auth is available because the URL includes `<CLOUDID>` and cannot be redirected by the user-global acli active account. If the flow must fall back to acli for a write, it is a guarded fallback, not the normal path:
|
|
320
|
+
|
|
321
|
+
1. Switch and assert the active `acli auth status` site/email matches config immediately before the write.
|
|
322
|
+
2. Execute the write.
|
|
323
|
+
3. Read the affected issue(s) immediately after the write.
|
|
324
|
+
4. Assert each response belongs to the configured tenant by checking one of: response `self` URL host equals `<SITE>`, response `self` URL path includes `/ex/jira/<CLOUDID>/`, or response metadata reports `<CLOUDID>`.
|
|
325
|
+
5. If the assertion fails, stop, report a cross-tenant write hazard, and best-effort roll back the write when there is a safe reversal: delete a newly created issue, remove a newly created comment/link, or revert a reversible field edit. Never continue as if the write succeeded.
|
|
326
|
+
|
|
327
|
+
Do not treat a successful `acli auth switch` or pre-write `auth status` as sufficient for tenant safety. Another process can mutate the global acli active account between the check and the write.
|
|
328
|
+
|
|
294
329
|
**acli link-create direction is invertible — flags and verification:** acli has no `--inward`/`--outward` flags; the real flags are `--in` and `--out` (confirm with `acli jira workitem link create --help`). For a `Blocks` link, **`--in` is the blocker and `--out` is the blocked** issue, i.e. `--in <X> --out <Y> --type Blocks` resolves to "X blocks Y" (Y `is blocked by` X). The lisa op `link from:<K> to:<K2> type:<T>` means "K ⟨T⟩ K2", so the blocker `from` maps to `--in` and the blocked `to` maps to `--out` (as in the adapter above). The acli success banner only echoes the `--in`/`--out` values you passed — it does NOT confirm the resolved semantic direction, so a reversed link reports success and looks fine. **After every `link` write, re-read the affected issues via `read-ticket` (which already requests `--fields '*all'`) and confirm `issuelinks[].type` + `inwardIssue`/`outwardIssue` resolve to the intended `blocks` / `is blocked by` direction.** Skipping this can silently reverse an entire epic's dependency graph — e.g. cutover tickets recorded as *blocking* the prerequisites that should block them.
|
|
295
330
|
|
|
296
331
|
**JIRA terminal-resolution note:** when a caller marks a transition as terminal per `leaf-only-lifecycle`, the substrate must not treat a Done-named status as sufficient by name alone. After `transition key:<K> to:<S>`, re-read the issue and verify `statusCategory = Done`; if the workflow requires a resolution, verify `resolution` is set. If the transition screen requires a resolution value, pass the configured default resolution when available; otherwise return a setup error so the build-intake skill can report the workflow gap instead of silently leaving an unresolved ticket in a Done-looking status.
|
|
@@ -318,6 +353,7 @@ Do not paraphrase substrate output beyond JSON normalization.
|
|
|
318
353
|
- Substrate is decided once per skill invocation and never switches mid-operation.
|
|
319
354
|
- Connection match is mandatory. Operations that bypass it (because "the user obviously meant the configured site") are forbidden.
|
|
320
355
|
- Profile mutations (`acli auth switch`) are allowed when acli is the active substrate. The curl substrate never mutates the token — if `ATLASSIAN_API_TOKEN` doesn't match the configured account, fail loud rather than silently substituting.
|
|
356
|
+
- JIRA writes are cloudId-bound by default. `acli` write adapters are fallback-only and must perform post-write tenant assertions plus safe rollback on mismatch.
|
|
321
357
|
- `.lisa.config.local.json` overrides `.lisa.config.json` per-key — the same precedence rule as every other consumer of project config.
|
|
322
358
|
|
|
323
359
|
## Headless behavior
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atlassian-access
|
|
3
|
-
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation
|
|
3
|
+
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation, binding JIRA writes to the configured cloudId via Atlassian REST whenever token auth is available and using acli only for reads or as a guarded fallback. For non-write acli operations, acli is used when installed and switchable to a profile matching the configured site; mismatched active profiles are skipped only after switch plus re-verification fails."
|
|
4
4
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,21 +35,26 @@ EMAIL=$(jq -r '.atlassian.email // empty' .lisa.config.local.json 2>/dev/null)
|
|
|
35
35
|
[ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set. Run /lisa:setup:atlassian." >&2; exit 1; }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are
|
|
38
|
+
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are switched to the configured profile when one exists, then skipped only if the switch fails or re-verification still mismatches.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
substrate=""
|
|
42
42
|
|
|
43
|
-
# Tier 1: acli
|
|
44
|
-
|
|
43
|
+
# Tier 1: acli for reads and non-write operations only.
|
|
44
|
+
#
|
|
45
|
+
# Do not choose acli for JIRA writes when curl/token auth is available. acli stores
|
|
46
|
+
# one machine-global active account and workitem writes cannot pin a cloudId per
|
|
47
|
+
# invocation, so switch-then-write is a TOCTOU risk in multi-account or concurrent
|
|
48
|
+
# sessions. Write operations prefer the cloudId-scoped REST URL below.
|
|
49
|
+
if [ "$OP_KIND" != "jira-write" ] && command -v acli >/dev/null 2>&1 && acli auth status >/dev/null 2>&1; then
|
|
45
50
|
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
51
|
+
if [ "$current_site" != "$SITE" ]; then
|
|
52
|
+
# acli installed but pointing at a different site. Try switching profiles.
|
|
53
|
+
acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1 || true
|
|
54
|
+
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
55
|
+
fi
|
|
46
56
|
if [ "$current_site" = "$SITE" ]; then
|
|
47
57
|
substrate="acli"
|
|
48
|
-
else
|
|
49
|
-
# acli installed but pointing at a different site. Try switching profiles.
|
|
50
|
-
if acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1; then
|
|
51
|
-
substrate="acli"
|
|
52
|
-
fi
|
|
53
58
|
fi
|
|
54
59
|
fi
|
|
55
60
|
|
|
@@ -114,7 +119,13 @@ public static class LisaCred {
|
|
|
114
119
|
esac
|
|
115
120
|
}
|
|
116
121
|
TOKEN=$(read_atlassian_token "$EMAIL")
|
|
117
|
-
[ -n "$TOKEN" ] && curl_available=true &&
|
|
122
|
+
[ -n "$TOKEN" ] && curl_available=true && {
|
|
123
|
+
if [ "$OP_KIND" = "jira-write" ]; then
|
|
124
|
+
substrate="curl"
|
|
125
|
+
else
|
|
126
|
+
: ${substrate:=curl}
|
|
127
|
+
fi
|
|
128
|
+
}
|
|
118
129
|
|
|
119
130
|
# Fail loudly with actionable remediation if nothing works.
|
|
120
131
|
if [ -z "$substrate" ]; then
|
|
@@ -164,7 +175,7 @@ Operation dispatch then uses `$substrate` for the primary route. If the operatio
|
|
|
164
175
|
|
|
165
176
|
### Step 2 — Connection-match check
|
|
166
177
|
|
|
167
|
-
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already
|
|
178
|
+
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already tries to switch mismatched acli profiles and verifies the result before selection. This step repeats the assertion before any operation runs — defensive in case the substrate state changed since selection.
|
|
168
179
|
|
|
169
180
|
Read configured site:
|
|
170
181
|
|
|
@@ -188,8 +199,22 @@ current_site=$(echo "$current" | jq -r '.site // empty')
|
|
|
188
199
|
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
189
200
|
|
|
190
201
|
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
191
|
-
# Profile mismatch — switch.
|
|
202
|
+
# Profile mismatch — switch, then re-verify. Do not trust the switch exit
|
|
203
|
+
# status alone because acli's active account is machine-global state.
|
|
192
204
|
acli auth switch --site "$site" ${email:+--email "$email"}
|
|
205
|
+
current=$(acli auth status --json 2>/dev/null)
|
|
206
|
+
current_site=$(echo "$current" | jq -r '.site // empty')
|
|
207
|
+
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
211
|
+
echo "Error: acli active site is '$current_site', but .lisa.config.json requires '$site'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
if [ -n "$email" ] && [ -n "$current_email" ] && [ "$current_email" != "$email" ]; then
|
|
216
|
+
echo "Error: acli active account is '$current_email', but .lisa.config.json requires '$email'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
217
|
+
exit 1
|
|
193
218
|
fi
|
|
194
219
|
```
|
|
195
220
|
|
|
@@ -257,18 +282,18 @@ Substrate column meanings:
|
|
|
257
282
|
- Multiple cells filled means tier ordering applies — try acli, then MCP, then curl, taking the first that has an adapter for the op AND is identity-matched.
|
|
258
283
|
- One cell means only that substrate can perform the op.
|
|
259
284
|
|
|
260
|
-
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA
|
|
285
|
+
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA curl writes use the cloudId-bound Atlassian gateway `https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/...`; JIRA curl reads may use either that gateway or `https://<SITE>/rest/api/3/...` after the token account check. Confluence uses `/wiki/rest/api/...` (v1) or `/api/v2/...` (v2).
|
|
261
286
|
|
|
262
287
|
| Operation | acli adapter | MCP adapter | curl adapter |
|
|
263
288
|
|---|---|---|---|
|
|
264
289
|
| **JIRA ops** | | | |
|
|
265
290
|
| `read-ticket key:<K>` | `acli jira workitem view <K> --fields '*all' --json` | `mcp__plugin_atlassian_atlassian__getJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>?fields=*all` |
|
|
266
|
-
| `write-ticket payload:<P>` (create) | `acli jira workitem create --from-json <P>` | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https
|
|
267
|
-
| `write-ticket payload:<P>` (edit) | `acli jira workitem edit <K> --from-json <P>` | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https
|
|
268
|
-
| `transition key:<K> to:<S>` | `acli jira workitem transition --key <K> --status "<S>" --yes` | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST
|
|
291
|
+
| `write-ticket payload:<P>` (create) | guarded fallback only: `acli jira workitem create --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue` body=`<P>` |
|
|
292
|
+
| `write-ticket payload:<P>` (edit) | guarded fallback only: `acli jira workitem edit <K> --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>` body=`<P>` |
|
|
293
|
+
| `transition key:<K> to:<S>` | guarded fallback only: `acli jira workitem transition --key <K> --status "<S>" --yes` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/transitions` |
|
|
269
294
|
| `transitions key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getTransitionsForJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>/transitions` |
|
|
270
|
-
| `comment key:<K> body:<B>` | `acli jira workitem comment add --key <K> --body "<B>"` | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https
|
|
271
|
-
| `link from:<K> to:<K2> type:<T>` | `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https
|
|
295
|
+
| `comment key:<K> body:<B>` | guarded fallback only: `acli jira workitem comment add --key <K> --body "<B>"` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/comment` |
|
|
296
|
+
| `link from:<K> to:<K2> type:<T>` | guarded fallback only: `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` + direction and tenant assertion (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issueLink` |
|
|
272
297
|
| `remote-links key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getJiraIssueRemoteIssueLinks` | `GET https://<SITE>/rest/api/3/issue/<K>/remotelink` |
|
|
273
298
|
| `search-issues jql:<J>` | `acli jira workitem search --jql "<J>" --json` | `mcp__plugin_atlassian_atlassian__searchJiraIssuesUsingJql` | `POST https://<SITE>/rest/api/3/search/jql` |
|
|
274
299
|
| `list-projects` | `acli jira project list --paginate --json` | `mcp__plugin_atlassian_atlassian__getVisibleJiraProjects` | `GET https://<SITE>/rest/api/3/project/search` |
|
|
@@ -291,6 +316,16 @@ Substrate column meanings:
|
|
|
291
316
|
|
|
292
317
|
**acli flag note:** acli's `--output` flag does not exist; the correct flag is `--json`. List commands require `--paginate` or `--limit` (no implicit fetch-all). `acli jira workitem view` defaults to a restricted field set (`key,issuetype,summary,status,assignee,description`), so `read-ticket` MUST pass `--fields '*all'` or an explicit equivalent that includes every downstream dependency: parent, subtasks, issue links, components, labels, priority, status, issue type, summary, description, fix versions, affected versions, attachments, comments, estimates, sprint/story-point fields, and project-required custom fields. Never rely on the default view fields; they hide parent/components/labels and corrupt leaf-only, relationship-search, build-ready, and required-custom-field gates. Several documented adapters are nominal — verify against `acli <subcmd> --help` before relying on them. When acli's adapter is broken or missing for a specific op, fall through to MCP (if identity-matched) then curl per the tier ordering.
|
|
293
318
|
|
|
319
|
+
**JIRA write tenant-safety rule:** create, edit, transition, comment, and link are write operations. They MUST prefer the curl adapter whenever token auth is available because the URL includes `<CLOUDID>` and cannot be redirected by the user-global acli active account. If the flow must fall back to acli for a write, it is a guarded fallback, not the normal path:
|
|
320
|
+
|
|
321
|
+
1. Switch and assert the active `acli auth status` site/email matches config immediately before the write.
|
|
322
|
+
2. Execute the write.
|
|
323
|
+
3. Read the affected issue(s) immediately after the write.
|
|
324
|
+
4. Assert each response belongs to the configured tenant by checking one of: response `self` URL host equals `<SITE>`, response `self` URL path includes `/ex/jira/<CLOUDID>/`, or response metadata reports `<CLOUDID>`.
|
|
325
|
+
5. If the assertion fails, stop, report a cross-tenant write hazard, and best-effort roll back the write when there is a safe reversal: delete a newly created issue, remove a newly created comment/link, or revert a reversible field edit. Never continue as if the write succeeded.
|
|
326
|
+
|
|
327
|
+
Do not treat a successful `acli auth switch` or pre-write `auth status` as sufficient for tenant safety. Another process can mutate the global acli active account between the check and the write.
|
|
328
|
+
|
|
294
329
|
**acli link-create direction is invertible — flags and verification:** acli has no `--inward`/`--outward` flags; the real flags are `--in` and `--out` (confirm with `acli jira workitem link create --help`). For a `Blocks` link, **`--in` is the blocker and `--out` is the blocked** issue, i.e. `--in <X> --out <Y> --type Blocks` resolves to "X blocks Y" (Y `is blocked by` X). The lisa op `link from:<K> to:<K2> type:<T>` means "K ⟨T⟩ K2", so the blocker `from` maps to `--in` and the blocked `to` maps to `--out` (as in the adapter above). The acli success banner only echoes the `--in`/`--out` values you passed — it does NOT confirm the resolved semantic direction, so a reversed link reports success and looks fine. **After every `link` write, re-read the affected issues via `read-ticket` (which already requests `--fields '*all'`) and confirm `issuelinks[].type` + `inwardIssue`/`outwardIssue` resolve to the intended `blocks` / `is blocked by` direction.** Skipping this can silently reverse an entire epic's dependency graph — e.g. cutover tickets recorded as *blocking* the prerequisites that should block them.
|
|
295
330
|
|
|
296
331
|
**JIRA terminal-resolution note:** when a caller marks a transition as terminal per `leaf-only-lifecycle`, the substrate must not treat a Done-named status as sufficient by name alone. After `transition key:<K> to:<S>`, re-read the issue and verify `statusCategory = Done`; if the workflow requires a resolution, verify `resolution` is set. If the transition screen requires a resolution value, pass the configured default resolution when available; otherwise return a setup error so the build-intake skill can report the workflow gap instead of silently leaving an unresolved ticket in a Done-looking status.
|
|
@@ -318,6 +353,7 @@ Do not paraphrase substrate output beyond JSON normalization.
|
|
|
318
353
|
- Substrate is decided once per skill invocation and never switches mid-operation.
|
|
319
354
|
- Connection match is mandatory. Operations that bypass it (because "the user obviously meant the configured site") are forbidden.
|
|
320
355
|
- Profile mutations (`acli auth switch`) are allowed when acli is the active substrate. The curl substrate never mutates the token — if `ATLASSIAN_API_TOKEN` doesn't match the configured account, fail loud rather than silently substituting.
|
|
356
|
+
- JIRA writes are cloudId-bound by default. `acli` write adapters are fallback-only and must perform post-write tenant assertions plus safe rollback on mismatch.
|
|
321
357
|
- `.lisa.config.local.json` overrides `.lisa.config.json` per-key — the same precedence rule as every other consumer of project config.
|
|
322
358
|
|
|
323
359
|
## Headless behavior
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atlassian-access
|
|
3
|
-
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation
|
|
3
|
+
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Resolves a substrate per operation, binding JIRA writes to the configured cloudId via Atlassian REST whenever token auth is available and using acli only for reads or as a guarded fallback. For non-write acli operations, acli is used when installed and switchable to a profile matching the configured site; mismatched active profiles are skipped only after switch plus re-verification fails."
|
|
4
4
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,21 +35,26 @@ EMAIL=$(jq -r '.atlassian.email // empty' .lisa.config.local.json 2>/dev/null)
|
|
|
35
35
|
[ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set. Run /lisa:setup:atlassian." >&2; exit 1; }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are
|
|
38
|
+
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are switched to the configured profile when one exists, then skipped only if the switch fails or re-verification still mismatches.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
substrate=""
|
|
42
42
|
|
|
43
|
-
# Tier 1: acli
|
|
44
|
-
|
|
43
|
+
# Tier 1: acli for reads and non-write operations only.
|
|
44
|
+
#
|
|
45
|
+
# Do not choose acli for JIRA writes when curl/token auth is available. acli stores
|
|
46
|
+
# one machine-global active account and workitem writes cannot pin a cloudId per
|
|
47
|
+
# invocation, so switch-then-write is a TOCTOU risk in multi-account or concurrent
|
|
48
|
+
# sessions. Write operations prefer the cloudId-scoped REST URL below.
|
|
49
|
+
if [ "$OP_KIND" != "jira-write" ] && command -v acli >/dev/null 2>&1 && acli auth status >/dev/null 2>&1; then
|
|
45
50
|
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
51
|
+
if [ "$current_site" != "$SITE" ]; then
|
|
52
|
+
# acli installed but pointing at a different site. Try switching profiles.
|
|
53
|
+
acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1 || true
|
|
54
|
+
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
55
|
+
fi
|
|
46
56
|
if [ "$current_site" = "$SITE" ]; then
|
|
47
57
|
substrate="acli"
|
|
48
|
-
else
|
|
49
|
-
# acli installed but pointing at a different site. Try switching profiles.
|
|
50
|
-
if acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1; then
|
|
51
|
-
substrate="acli"
|
|
52
|
-
fi
|
|
53
58
|
fi
|
|
54
59
|
fi
|
|
55
60
|
|
|
@@ -114,7 +119,13 @@ public static class LisaCred {
|
|
|
114
119
|
esac
|
|
115
120
|
}
|
|
116
121
|
TOKEN=$(read_atlassian_token "$EMAIL")
|
|
117
|
-
[ -n "$TOKEN" ] && curl_available=true &&
|
|
122
|
+
[ -n "$TOKEN" ] && curl_available=true && {
|
|
123
|
+
if [ "$OP_KIND" = "jira-write" ]; then
|
|
124
|
+
substrate="curl"
|
|
125
|
+
else
|
|
126
|
+
: ${substrate:=curl}
|
|
127
|
+
fi
|
|
128
|
+
}
|
|
118
129
|
|
|
119
130
|
# Fail loudly with actionable remediation if nothing works.
|
|
120
131
|
if [ -z "$substrate" ]; then
|
|
@@ -164,7 +175,7 @@ Operation dispatch then uses `$substrate` for the primary route. If the operatio
|
|
|
164
175
|
|
|
165
176
|
### Step 2 — Connection-match check
|
|
166
177
|
|
|
167
|
-
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already
|
|
178
|
+
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already tries to switch mismatched acli profiles and verifies the result before selection. This step repeats the assertion before any operation runs — defensive in case the substrate state changed since selection.
|
|
168
179
|
|
|
169
180
|
Read configured site:
|
|
170
181
|
|
|
@@ -188,8 +199,22 @@ current_site=$(echo "$current" | jq -r '.site // empty')
|
|
|
188
199
|
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
189
200
|
|
|
190
201
|
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
191
|
-
# Profile mismatch — switch.
|
|
202
|
+
# Profile mismatch — switch, then re-verify. Do not trust the switch exit
|
|
203
|
+
# status alone because acli's active account is machine-global state.
|
|
192
204
|
acli auth switch --site "$site" ${email:+--email "$email"}
|
|
205
|
+
current=$(acli auth status --json 2>/dev/null)
|
|
206
|
+
current_site=$(echo "$current" | jq -r '.site // empty')
|
|
207
|
+
current_email=$(echo "$current" | jq -r '.email // empty')
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
if [ -n "$site" ] && [ "$current_site" != "$site" ]; then
|
|
211
|
+
echo "Error: acli active site is '$current_site', but .lisa.config.json requires '$site'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
if [ -n "$email" ] && [ -n "$current_email" ] && [ "$current_email" != "$email" ]; then
|
|
216
|
+
echo "Error: acli active account is '$current_email', but .lisa.config.json requires '$email'. Run /lisa:setup:atlassian to add or repair the matching profile." >&2
|
|
217
|
+
exit 1
|
|
193
218
|
fi
|
|
194
219
|
```
|
|
195
220
|
|
|
@@ -257,18 +282,18 @@ Substrate column meanings:
|
|
|
257
282
|
- Multiple cells filled means tier ordering applies — try acli, then MCP, then curl, taking the first that has an adapter for the op AND is identity-matched.
|
|
258
283
|
- One cell means only that substrate can perform the op.
|
|
259
284
|
|
|
260
|
-
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA
|
|
285
|
+
`<SITE>` = `.atlassian.site` (e.g. `propswap.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA curl writes use the cloudId-bound Atlassian gateway `https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/...`; JIRA curl reads may use either that gateway or `https://<SITE>/rest/api/3/...` after the token account check. Confluence uses `/wiki/rest/api/...` (v1) or `/api/v2/...` (v2).
|
|
261
286
|
|
|
262
287
|
| Operation | acli adapter | MCP adapter | curl adapter |
|
|
263
288
|
|---|---|---|---|
|
|
264
289
|
| **JIRA ops** | | | |
|
|
265
290
|
| `read-ticket key:<K>` | `acli jira workitem view <K> --fields '*all' --json` | `mcp__plugin_atlassian_atlassian__getJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>?fields=*all` |
|
|
266
|
-
| `write-ticket payload:<P>` (create) | `acli jira workitem create --from-json <P>` | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https
|
|
267
|
-
| `write-ticket payload:<P>` (edit) | `acli jira workitem edit <K> --from-json <P>` | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https
|
|
268
|
-
| `transition key:<K> to:<S>` | `acli jira workitem transition --key <K> --status "<S>" --yes` | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST
|
|
291
|
+
| `write-ticket payload:<P>` (create) | guarded fallback only: `acli jira workitem create --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__createJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue` body=`<P>` |
|
|
292
|
+
| `write-ticket payload:<P>` (edit) | guarded fallback only: `acli jira workitem edit <K> --from-json <P>` + response tenant assertion | `mcp__plugin_atlassian_atlassian__editJiraIssue` | `PUT https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>` body=`<P>` |
|
|
293
|
+
| `transition key:<K> to:<S>` | guarded fallback only: `acli jira workitem transition --key <K> --status "<S>" --yes` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/transitions` |
|
|
269
294
|
| `transitions key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getTransitionsForJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>/transitions` |
|
|
270
|
-
| `comment key:<K> body:<B>` | `acli jira workitem comment add --key <K> --body "<B>"` | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https
|
|
271
|
-
| `link from:<K> to:<K2> type:<T>` | `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https
|
|
295
|
+
| `comment key:<K> body:<B>` | guarded fallback only: `acli jira workitem comment add --key <K> --body "<B>"` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/comment` |
|
|
296
|
+
| `link from:<K> to:<K2> type:<T>` | guarded fallback only: `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` + direction and tenant assertion (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issueLink` |
|
|
272
297
|
| `remote-links key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getJiraIssueRemoteIssueLinks` | `GET https://<SITE>/rest/api/3/issue/<K>/remotelink` |
|
|
273
298
|
| `search-issues jql:<J>` | `acli jira workitem search --jql "<J>" --json` | `mcp__plugin_atlassian_atlassian__searchJiraIssuesUsingJql` | `POST https://<SITE>/rest/api/3/search/jql` |
|
|
274
299
|
| `list-projects` | `acli jira project list --paginate --json` | `mcp__plugin_atlassian_atlassian__getVisibleJiraProjects` | `GET https://<SITE>/rest/api/3/project/search` |
|
|
@@ -291,6 +316,16 @@ Substrate column meanings:
|
|
|
291
316
|
|
|
292
317
|
**acli flag note:** acli's `--output` flag does not exist; the correct flag is `--json`. List commands require `--paginate` or `--limit` (no implicit fetch-all). `acli jira workitem view` defaults to a restricted field set (`key,issuetype,summary,status,assignee,description`), so `read-ticket` MUST pass `--fields '*all'` or an explicit equivalent that includes every downstream dependency: parent, subtasks, issue links, components, labels, priority, status, issue type, summary, description, fix versions, affected versions, attachments, comments, estimates, sprint/story-point fields, and project-required custom fields. Never rely on the default view fields; they hide parent/components/labels and corrupt leaf-only, relationship-search, build-ready, and required-custom-field gates. Several documented adapters are nominal — verify against `acli <subcmd> --help` before relying on them. When acli's adapter is broken or missing for a specific op, fall through to MCP (if identity-matched) then curl per the tier ordering.
|
|
293
318
|
|
|
319
|
+
**JIRA write tenant-safety rule:** create, edit, transition, comment, and link are write operations. They MUST prefer the curl adapter whenever token auth is available because the URL includes `<CLOUDID>` and cannot be redirected by the user-global acli active account. If the flow must fall back to acli for a write, it is a guarded fallback, not the normal path:
|
|
320
|
+
|
|
321
|
+
1. Switch and assert the active `acli auth status` site/email matches config immediately before the write.
|
|
322
|
+
2. Execute the write.
|
|
323
|
+
3. Read the affected issue(s) immediately after the write.
|
|
324
|
+
4. Assert each response belongs to the configured tenant by checking one of: response `self` URL host equals `<SITE>`, response `self` URL path includes `/ex/jira/<CLOUDID>/`, or response metadata reports `<CLOUDID>`.
|
|
325
|
+
5. If the assertion fails, stop, report a cross-tenant write hazard, and best-effort roll back the write when there is a safe reversal: delete a newly created issue, remove a newly created comment/link, or revert a reversible field edit. Never continue as if the write succeeded.
|
|
326
|
+
|
|
327
|
+
Do not treat a successful `acli auth switch` or pre-write `auth status` as sufficient for tenant safety. Another process can mutate the global acli active account between the check and the write.
|
|
328
|
+
|
|
294
329
|
**acli link-create direction is invertible — flags and verification:** acli has no `--inward`/`--outward` flags; the real flags are `--in` and `--out` (confirm with `acli jira workitem link create --help`). For a `Blocks` link, **`--in` is the blocker and `--out` is the blocked** issue, i.e. `--in <X> --out <Y> --type Blocks` resolves to "X blocks Y" (Y `is blocked by` X). The lisa op `link from:<K> to:<K2> type:<T>` means "K ⟨T⟩ K2", so the blocker `from` maps to `--in` and the blocked `to` maps to `--out` (as in the adapter above). The acli success banner only echoes the `--in`/`--out` values you passed — it does NOT confirm the resolved semantic direction, so a reversed link reports success and looks fine. **After every `link` write, re-read the affected issues via `read-ticket` (which already requests `--fields '*all'`) and confirm `issuelinks[].type` + `inwardIssue`/`outwardIssue` resolve to the intended `blocks` / `is blocked by` direction.** Skipping this can silently reverse an entire epic's dependency graph — e.g. cutover tickets recorded as *blocking* the prerequisites that should block them.
|
|
295
330
|
|
|
296
331
|
**JIRA terminal-resolution note:** when a caller marks a transition as terminal per `leaf-only-lifecycle`, the substrate must not treat a Done-named status as sufficient by name alone. After `transition key:<K> to:<S>`, re-read the issue and verify `statusCategory = Done`; if the workflow requires a resolution, verify `resolution` is set. If the transition screen requires a resolution value, pass the configured default resolution when available; otherwise return a setup error so the build-intake skill can report the workflow gap instead of silently leaving an unresolved ticket in a Done-looking status.
|
|
@@ -318,6 +353,7 @@ Do not paraphrase substrate output beyond JSON normalization.
|
|
|
318
353
|
- Substrate is decided once per skill invocation and never switches mid-operation.
|
|
319
354
|
- Connection match is mandatory. Operations that bypass it (because "the user obviously meant the configured site") are forbidden.
|
|
320
355
|
- Profile mutations (`acli auth switch`) are allowed when acli is the active substrate. The curl substrate never mutates the token — if `ATLASSIAN_API_TOKEN` doesn't match the configured account, fail loud rather than silently substituting.
|
|
356
|
+
- JIRA writes are cloudId-bound by default. `acli` write adapters are fallback-only and must perform post-write tenant assertions plus safe rollback on mismatch.
|
|
321
357
|
- `.lisa.config.local.json` overrides `.lisa.config.json` per-key — the same precedence rule as every other consumer of project config.
|
|
322
358
|
|
|
323
359
|
## Headless behavior
|