@codyswann/lisa 2.21.1 → 2.23.0

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.
Files changed (122) hide show
  1. package/package.json +3 -2
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/agents/confluence-prd-intake.md +11 -9
  5. package/plugins/lisa/agents/github-agent.md +18 -10
  6. package/plugins/lisa/agents/github-build-intake.md +10 -8
  7. package/plugins/lisa/agents/github-prd-intake.md +11 -9
  8. package/plugins/lisa/agents/jira-agent.md +12 -8
  9. package/plugins/lisa/agents/jira-build-intake.md +9 -7
  10. package/plugins/lisa/agents/linear-agent.md +15 -9
  11. package/plugins/lisa/agents/linear-build-intake.md +13 -11
  12. package/plugins/lisa/agents/linear-prd-intake.md +11 -9
  13. package/plugins/lisa/agents/notion-prd-intake.md +11 -9
  14. package/plugins/lisa/commands/setup/atlassian.md +7 -0
  15. package/plugins/lisa/commands/setup/confluence.md +7 -0
  16. package/plugins/lisa/commands/setup/jira.md +7 -0
  17. package/plugins/lisa/commands/setup/notion.md +7 -0
  18. package/plugins/lisa/rules/base-rules.md +1 -1
  19. package/plugins/lisa/rules/config-resolution.md +242 -24
  20. package/plugins/lisa/rules/repo-scope-split.md +41 -0
  21. package/plugins/lisa/rules/verification.md +13 -0
  22. package/plugins/lisa/skills/atlassian-access/SKILL.md +260 -0
  23. package/plugins/lisa/skills/confluence-prd-intake/SKILL.md +167 -82
  24. package/plugins/lisa/skills/confluence-to-tracker/SKILL.md +39 -26
  25. package/plugins/lisa/skills/github-add-journey/SKILL.md +1 -0
  26. package/plugins/lisa/skills/github-build-intake/SKILL.md +104 -40
  27. package/plugins/lisa/skills/github-evidence/SKILL.md +22 -5
  28. package/plugins/lisa/skills/github-prd-intake/SKILL.md +87 -51
  29. package/plugins/lisa/skills/github-to-tracker/SKILL.md +2 -2
  30. package/plugins/lisa/skills/github-validate-issue/SKILL.md +11 -1
  31. package/plugins/lisa/skills/jira-add-journey/SKILL.md +1 -0
  32. package/plugins/lisa/skills/jira-build-intake/SKILL.md +110 -45
  33. package/plugins/lisa/skills/jira-create/SKILL.md +5 -3
  34. package/plugins/lisa/skills/jira-evidence/SKILL.md +19 -2
  35. package/plugins/lisa/skills/jira-journey/SKILL.md +3 -1
  36. package/plugins/lisa/skills/jira-read-ticket/SKILL.md +10 -8
  37. package/plugins/lisa/skills/jira-sync/SKILL.md +11 -5
  38. package/plugins/lisa/skills/jira-validate-ticket/SKILL.md +22 -10
  39. package/plugins/lisa/skills/jira-verify/SKILL.md +5 -3
  40. package/plugins/lisa/skills/jira-write-ticket/SKILL.md +16 -14
  41. package/plugins/lisa/skills/linear-add-journey/SKILL.md +1 -0
  42. package/plugins/lisa/skills/linear-build-intake/SKILL.md +90 -32
  43. package/plugins/lisa/skills/linear-evidence/SKILL.md +22 -5
  44. package/plugins/lisa/skills/linear-prd-intake/SKILL.md +92 -57
  45. package/plugins/lisa/skills/linear-validate-issue/SKILL.md +10 -0
  46. package/plugins/lisa/skills/notion-access/SKILL.md +193 -0
  47. package/plugins/lisa/skills/notion-prd-intake/SKILL.md +105 -46
  48. package/plugins/lisa/skills/notion-to-tracker/SKILL.md +7 -5
  49. package/plugins/lisa/skills/setup-atlassian/SKILL.md +316 -0
  50. package/plugins/lisa/skills/setup-confluence/SKILL.md +245 -0
  51. package/plugins/lisa/skills/setup-jira/SKILL.md +198 -0
  52. package/plugins/lisa/skills/setup-notion/SKILL.md +283 -0
  53. package/plugins/lisa/skills/task-decomposition/SKILL.md +2 -0
  54. package/plugins/lisa/skills/ticket-triage/SKILL.md +4 -1
  55. package/plugins/lisa/skills/tracker-evidence/SKILL.md +1 -0
  56. package/plugins/lisa/skills/verification-lifecycle/SKILL.md +2 -0
  57. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/src/base/agents/confluence-prd-intake.md +11 -9
  70. package/plugins/src/base/agents/github-agent.md +18 -10
  71. package/plugins/src/base/agents/github-build-intake.md +10 -8
  72. package/plugins/src/base/agents/github-prd-intake.md +11 -9
  73. package/plugins/src/base/agents/jira-agent.md +12 -8
  74. package/plugins/src/base/agents/jira-build-intake.md +9 -7
  75. package/plugins/src/base/agents/linear-agent.md +15 -9
  76. package/plugins/src/base/agents/linear-build-intake.md +13 -11
  77. package/plugins/src/base/agents/linear-prd-intake.md +11 -9
  78. package/plugins/src/base/agents/notion-prd-intake.md +11 -9
  79. package/plugins/src/base/commands/setup/atlassian.md +7 -0
  80. package/plugins/src/base/commands/setup/confluence.md +7 -0
  81. package/plugins/src/base/commands/setup/jira.md +7 -0
  82. package/plugins/src/base/commands/setup/notion.md +7 -0
  83. package/plugins/src/base/rules/base-rules.md +1 -1
  84. package/plugins/src/base/rules/config-resolution.md +242 -24
  85. package/plugins/src/base/rules/repo-scope-split.md +41 -0
  86. package/plugins/src/base/rules/verification.md +13 -0
  87. package/plugins/src/base/skills/atlassian-access/SKILL.md +260 -0
  88. package/plugins/src/base/skills/confluence-prd-intake/SKILL.md +167 -82
  89. package/plugins/src/base/skills/confluence-to-tracker/SKILL.md +39 -26
  90. package/plugins/src/base/skills/github-add-journey/SKILL.md +1 -0
  91. package/plugins/src/base/skills/github-build-intake/SKILL.md +104 -40
  92. package/plugins/src/base/skills/github-evidence/SKILL.md +22 -5
  93. package/plugins/src/base/skills/github-prd-intake/SKILL.md +87 -51
  94. package/plugins/src/base/skills/github-to-tracker/SKILL.md +2 -2
  95. package/plugins/src/base/skills/github-validate-issue/SKILL.md +11 -1
  96. package/plugins/src/base/skills/jira-add-journey/SKILL.md +1 -0
  97. package/plugins/src/base/skills/jira-build-intake/SKILL.md +110 -45
  98. package/plugins/src/base/skills/jira-create/SKILL.md +5 -3
  99. package/plugins/src/base/skills/jira-evidence/SKILL.md +19 -2
  100. package/plugins/src/base/skills/jira-journey/SKILL.md +3 -1
  101. package/plugins/src/base/skills/jira-read-ticket/SKILL.md +10 -8
  102. package/plugins/src/base/skills/jira-sync/SKILL.md +11 -5
  103. package/plugins/src/base/skills/jira-validate-ticket/SKILL.md +22 -10
  104. package/plugins/src/base/skills/jira-verify/SKILL.md +5 -3
  105. package/plugins/src/base/skills/jira-write-ticket/SKILL.md +16 -14
  106. package/plugins/src/base/skills/linear-add-journey/SKILL.md +1 -0
  107. package/plugins/src/base/skills/linear-build-intake/SKILL.md +90 -32
  108. package/plugins/src/base/skills/linear-evidence/SKILL.md +22 -5
  109. package/plugins/src/base/skills/linear-prd-intake/SKILL.md +92 -57
  110. package/plugins/src/base/skills/linear-validate-issue/SKILL.md +10 -0
  111. package/plugins/src/base/skills/notion-access/SKILL.md +193 -0
  112. package/plugins/src/base/skills/notion-prd-intake/SKILL.md +105 -46
  113. package/plugins/src/base/skills/notion-to-tracker/SKILL.md +7 -5
  114. package/plugins/src/base/skills/setup-atlassian/SKILL.md +316 -0
  115. package/plugins/src/base/skills/setup-confluence/SKILL.md +245 -0
  116. package/plugins/src/base/skills/setup-jira/SKILL.md +198 -0
  117. package/plugins/src/base/skills/setup-notion/SKILL.md +283 -0
  118. package/plugins/src/base/skills/task-decomposition/SKILL.md +2 -0
  119. package/plugins/src/base/skills/ticket-triage/SKILL.md +4 -1
  120. package/plugins/src/base/skills/tracker-evidence/SKILL.md +1 -0
  121. package/plugins/src/base/skills/verification-lifecycle/SKILL.md +2 -0
  122. package/scripts/check-plugins-sync.sh +45 -0
@@ -0,0 +1,316 @@
1
+ ---
2
+ name: setup-atlassian
3
+ description: "Configure Atlassian access for this project. Installs acli if missing, runs the OAuth or API-token login, optionally enables the Atlassian MCP, resolves the cloudId for the active site, and writes the `atlassian` section into `.lisa.config.json`. Prerequisite for /lisa:setup:jira and /lisa:setup:confluence (both need atlassian.cloudId). Idempotent — re-running updates the existing section rather than duplicating it."
4
+ allowed-tools: ["Bash", "Read", "Write", "Edit", "Skill", "AskUserQuestion"]
5
+ ---
6
+
7
+ # Setup Atlassian: $ARGUMENTS
8
+
9
+ Resolve and persist Atlassian access for this project. After this skill, `.lisa.config.json` contains `atlassian.cloudId` (required) and optionally `atlassian.site` / `atlassian.email` for multi-account disambiguation.
10
+
11
+ ## Workflow
12
+
13
+ ### Step 0 — Pick a setup path
14
+
15
+ Ask via `AskUserQuestion`:
16
+
17
+ > How do you want lisa to talk to Atlassian for this project?
18
+ >
19
+ > 1. **MCP-only (simplest)** — authenticate the Atlassian MCP once via browser OAuth; lisa uses it for every operation. Best for: single-Atlassian-account developers on a personal laptop. New developers onboard with one OAuth flow, no token management. Skip the rest of this setup.
20
+ > 2. **acli (CLI) + MCP fallback** — install acli, authenticate per-profile, MCP picks up anything acli can't do. Best for: developers who work across multiple Atlassian accounts and need profile switching. Continue with acli install.
21
+ > 3. **API-token path (headless / CI)** — store a per-user API token in the OS keychain; lisa uses curl for everything. Best for: CI pipelines, headless dev containers, or any case where browser OAuth is impossible. Continue through token-create steps.
22
+
23
+ If the user picks (1) and the MCP is already authenticated to the right workspace (verify by calling `getAccessibleAtlassianResources` and checking `atlassian.cloudId` is in the result), write only `atlassian.cloudId` and `atlassian.site` into `.lisa.config.json` and skip to Step 6 (cloudId resolution). If the MCP isn't authed yet, instruct the user to run `mcp__plugin_atlassian_atlassian__authenticate` (or the claude.ai equivalent) and complete the OAuth flow in their browser, then re-verify.
24
+
25
+ If the user picks (2) or (3), continue through the rest of the steps; acli and/or the API token become available alongside the MCP.
26
+
27
+ ### Step 1 — Ensure acli is installed (preferred substrate)
28
+
29
+ ```bash
30
+ if ! command -v acli >/dev/null 2>&1; then
31
+ if command -v brew >/dev/null 2>&1; then
32
+ brew tap atlassian/homebrew-acli
33
+ brew install acli
34
+ else
35
+ cat >&2 <<'EOF'
36
+ Error: Homebrew not found. Install acli manually:
37
+ https://developer.atlassian.com/cloud/acli/guides/install-macos/
38
+ or skip acli and rely on the Atlassian MCP only (CI/remote envs need acli).
39
+ EOF
40
+ # Continue — acli is preferred but MCP-only is acceptable.
41
+ fi
42
+ fi
43
+ ```
44
+
45
+ If acli install fails or is skipped, the project will operate in MCP mode. Surface this clearly to the user.
46
+
47
+ ### Step 2 — Authenticate
48
+
49
+ If acli is installed: prefer `acli auth login --web` for interactive environments; for headless, instruct the user to obtain a Rovo MCP-scoped API token and pipe via:
50
+
51
+ ```bash
52
+ echo "$ATLASSIAN_TOKEN" | acli jira auth login --site "<site>.atlassian.net" --email "<email>" --token
53
+ ```
54
+
55
+ After login, verify with `acli auth status`.
56
+
57
+ ### Step 3 — Acquire an Atlassian API token (curl substrate)
58
+
59
+ acli covers most JIRA operations but **no Confluence page writes** (only `space`-level commands, and `page view`). The classic-vs-granular OAuth scope mismatch (see `config-resolution` rule) also blocks acli's bearer token from working against the v2 Confluence REST API. So lisa needs a second substrate — **curl with Basic auth + API token** — for everything Confluence-write-related.
60
+
61
+ **Per-product tokens**: Atlassian's scoped API token UI is per-product, so the easiest path is one Confluence-scoped token. JIRA operations remain on acli (no JIRA token needed). If a future lisa op turns out to need a JIRA-scoped token (e.g., reading transition metadata or remote links — neither is required by the current dispatch), make a second token then.
62
+
63
+ **Security posture**: the token is stored in the OS keychain when available (macOS/Linux/Windows native backends) so it never lives in plaintext on disk and never flows through chat history. Env-var fallback exists for headless / CI / Linux-without-libsecret.
64
+
65
+ #### 3a. Check for existing token via the lookup ladder
66
+
67
+ Use the same ladder `atlassian-access` uses (env var → email-suffixed env var → keychain):
68
+
69
+ ```bash
70
+ EMAIL=$(jq -r '.atlassian.email // empty' .lisa.config.local.json 2>/dev/null)
71
+ SITE=$(jq -r '.atlassian.site // empty' .lisa.config.json)
72
+ CLOUDID=$(jq -r '.atlassian.cloudId // empty' .lisa.config.json)
73
+
74
+ read_token() {
75
+ local email="$1"
76
+ [ -n "$ATLASSIAN_API_TOKEN" ] && { echo "$ATLASSIAN_API_TOKEN"; return; }
77
+ local slug=$(echo "$email" | tr '[:upper:]@.' '[:lower:]__')
78
+ local varname="ATLASSIAN_API_TOKEN_${slug}"
79
+ [ -n "${!varname}" ] && { echo "${!varname}"; return; }
80
+ case "$(uname -s)" in
81
+ Darwin) security find-generic-password -s lisa-atlassian -a "$email" -w 2>/dev/null ;;
82
+ Linux) command -v secret-tool >/dev/null && secret-tool lookup service lisa-atlassian account "$email" 2>/dev/null ;;
83
+ MINGW*|MSYS*|CYGWIN*) cmdkey /list:"lisa-atlassian-${email}" 2>/dev/null | grep Password | awk '{print $NF}' ;;
84
+ esac
85
+ }
86
+
87
+ EXISTING=$(read_token "$EMAIL")
88
+ if [ -n "$EXISTING" ]; then
89
+ # Validate against Confluence.
90
+ AUTH=$(printf '%s:%s' "$EMAIL" "$EXISTING" | base64)
91
+ CODE=$(curl -s -o /dev/null -w "%{http_code}" \
92
+ -H "Authorization: Basic $AUTH" \
93
+ "https://api.atlassian.com/ex/confluence/${CLOUDID}/wiki/rest/api/space?limit=1")
94
+ if [ "$CODE" = "200" ]; then
95
+ echo "Existing Atlassian API token validated. Skipping setup."
96
+ # proceed to Step 4
97
+ fi
98
+ fi
99
+ ```
100
+
101
+ If validation fails or no token is found, continue.
102
+
103
+ #### 3b. Prompt the user to generate a token
104
+
105
+ Open the token-creation page in their browser:
106
+
107
+ ```bash
108
+ case "$(uname -s)" in
109
+ Darwin) open "https://id.atlassian.com/manage-profile/security/api-tokens" ;;
110
+ Linux) xdg-open "https://id.atlassian.com/manage-profile/security/api-tokens" 2>/dev/null ;;
111
+ MINGW*|MSYS*|CYGWIN*) start "https://id.atlassian.com/manage-profile/security/api-tokens" ;;
112
+ esac
113
+ ```
114
+
115
+ Then print these instructions for the user:
116
+
117
+ ```
118
+ 1. Click "Create API token with scopes" (NOT the legacy unscoped form).
119
+ 2. Label it: lisa-confluence-<machine-name> (anything; just for revocation traceability)
120
+ 3. App: Confluence
121
+ 4. Select EXACTLY these scopes:
122
+ Read: read:page:confluence
123
+ read:hierarchical-content:confluence
124
+ read:comment:confluence
125
+ read:space:confluence
126
+ Write: write:page:confluence
127
+ write:comment:confluence
128
+ write:label:confluence
129
+ Search: search:confluence
130
+ 5. Set an expiry (1 year max).
131
+ 6. Click "Create token" and copy the value.
132
+ ```
133
+
134
+ #### 3c. Have the user store the token via OS keychain (token never enters chat)
135
+
136
+ **Critical: don't use the interactive prompt form of `security` / `secret-tool` / `cmdkey`.** Atlassian scoped tokens end with `=<CRC>` (a checksum); terminal `getpass`-style prompts on macOS Terminal.app and iTerm have been observed to silently truncate the paste at the `=` sign, storing a 128-byte prefix instead of the full ~192-byte token. The result authenticates as 401 because the CRC fails. Symptom: the stored token validates against `printf` length checks (the prefix is well-formed) but every API call returns 401 with `x-failure-category: FAILURE_CLIENT_AUTH`.
137
+
138
+ Always pipe from the clipboard instead. Print platform-specific commands that take `$(pbpaste)` / `$(xsel)` / `$(Get-Clipboard)` directly into the `-w` / store arg:
139
+
140
+ ```bash
141
+ case "$(uname -s)" in
142
+ Darwin)
143
+ cat <<EOF
144
+ 1. Click "Copy" in the Atlassian token-create modal so the token is in your clipboard.
145
+ 2. Run this single line in your terminal — leading space keeps it out of zsh history:
146
+
147
+ security delete-generic-password -s lisa-atlassian -a "$EMAIL" 2>/dev/null; TOK="\$(pbpaste)"; security add-generic-password -U -s lisa-atlassian -a "$EMAIL" -w "\$TOK"; unset TOK
148
+
149
+ The token is piped from clipboard straight to keychain — no prompt, no truncation.
150
+ EOF
151
+ ;;
152
+ Linux)
153
+ if command -v secret-tool >/dev/null 2>&1; then
154
+ # Pick whichever clipboard tool is available.
155
+ if command -v wl-paste >/dev/null 2>&1; then CLIP=wl-paste
156
+ elif command -v xclip >/dev/null 2>&1; then CLIP="xclip -selection clipboard -o"
157
+ elif command -v xsel >/dev/null 2>&1; then CLIP="xsel --clipboard --output"
158
+ else CLIP="cat" # caller will have to paste; fallback path below
159
+ fi
160
+ cat <<EOF
161
+ 1. Click "Copy" in the Atlassian token modal so the token is in your clipboard.
162
+ 2. Run this single line in your terminal:
163
+
164
+ secret-tool clear service lisa-atlassian account "$EMAIL" 2>/dev/null; printf '%s' "\$($CLIP)" | secret-tool store --label="Lisa Atlassian ($EMAIL)" service lisa-atlassian account "$EMAIL"
165
+
166
+ (If no clipboard tool is installed, the command will read from stdin — paste your token, press Ctrl-D.)
167
+ EOF
168
+ else
169
+ cat <<EOF
170
+ libsecret / secret-tool is not installed. Options:
171
+
172
+ 1. Install it (recommended on desktop Linux):
173
+ Debian/Ubuntu: sudo apt install libsecret-tools
174
+ Fedora/RHEL: sudo dnf install libsecret
175
+ Then re-run /lisa:setup:atlassian.
176
+
177
+ 2. Fall back to env-var storage (headless / CI / Docker):
178
+ Add this line to your shell rc (.bashrc / .zshrc):
179
+ export ATLASSIAN_API_TOKEN_$(echo "$EMAIL" | tr '[:upper:]@.' '[:lower:]__')="<paste-token-here>"
180
+ Reload shell. Be aware: token will be in plaintext on disk.
181
+ EOF
182
+ fi
183
+ ;;
184
+ MINGW*|MSYS*|CYGWIN*)
185
+ cat <<EOF
186
+ 1. Click "Copy" in the Atlassian token modal so the token is in your clipboard.
187
+ 2. Run this in PowerShell (cmdkey doesn't accept piped passwords; this uses Credential Manager via PowerShell):
188
+
189
+ \$tok = Get-Clipboard; cmdkey /generic:"lisa-atlassian-$EMAIL" /user:"$EMAIL" /pass:"\$tok"; Remove-Variable tok
190
+ EOF
191
+ ;;
192
+ *)
193
+ cat <<EOF
194
+ Unknown platform. Fall back to env-var:
195
+
196
+ export ATLASSIAN_API_TOKEN_$(echo "$EMAIL" | tr '[:upper:]@.' '[:lower:]__')="<token>"
197
+
198
+ Add to your shell rc to persist.
199
+ EOF
200
+ ;;
201
+ esac
202
+ ```
203
+
204
+ **Do NOT accept the token via chat or stdin into this skill.** The user runs the command themselves; the token enters only the OS keychain. The clipboard-pipe form is mandatory — never advise the interactive `-w` (no-arg) prompt form, which truncates scoped tokens at the `=` sign on multiple terminal/readline combos.
205
+
206
+ Wait for the user to confirm they've stored it.
207
+
208
+ #### 3d. Verify retrieval
209
+
210
+ After the user confirms storage, retrieve via the same `read_token` ladder and validate. Use a **v2 endpoint** (not v1) since v1 returns 410 Gone — and v2 is what `atlassian-access` actually uses at runtime.
211
+
212
+ ```bash
213
+ NEW=$(read_token "$EMAIL")
214
+ if [ -z "$NEW" ]; then
215
+ echo "Error: token not retrievable from any source. Re-check the storage command and try again." >&2
216
+ exit 1
217
+ fi
218
+
219
+ # Length probe — Atlassian scoped tokens are ~192 chars (token body + '=' + CRC).
220
+ # A stored value shorter than ~150 chars almost certainly means the terminal truncated
221
+ # the paste at the '=' separator before the CRC.
222
+ if [ ${#NEW} -lt 150 ]; then
223
+ echo "Error: token is ${#NEW} chars — too short. Scoped tokens are ~192 chars and end with '=<CRC>'." >&2
224
+ echo "Likely your terminal truncated the paste. Use the clipboard-pipe form in Step 3c." >&2
225
+ exit 1
226
+ fi
227
+
228
+ AUTH=$(printf '%s:%s' "$EMAIL" "$NEW" | base64)
229
+ PROBE=$(curl -s -o /tmp/lisa-probe -w "%{http_code}" \
230
+ -H "Authorization: Basic $AUTH" \
231
+ "https://api.atlassian.com/ex/confluence/${CLOUDID}/wiki/api/v2/pages/$(jq -r '.confluence.parentPageId // empty' .lisa.config.json)")
232
+ # If no parentPageId in config, fall back to a generic spaces list (also v2).
233
+ if [ -z "$(jq -r '.confluence.parentPageId // empty' .lisa.config.json)" ]; then
234
+ PROBE=$(curl -s -o /tmp/lisa-probe -w "%{http_code}" \
235
+ -H "Authorization: Basic $AUTH" \
236
+ "https://api.atlassian.com/ex/confluence/${CLOUDID}/wiki/api/v2/spaces?limit=1")
237
+ fi
238
+
239
+ if [ "$PROBE" != "200" ]; then
240
+ echo "Error: token probe returned HTTP $PROBE." >&2
241
+ cat /tmp/lisa-probe >&2
242
+ echo "" >&2
243
+ echo "Likely causes: missing required scope (see Step 3b), or token was truncated during paste." >&2
244
+ exit 1
245
+ fi
246
+ rm -f /tmp/lisa-probe
247
+ echo "Token validated (${#NEW} chars). Confluence v2 access ready."
248
+ ```
249
+
250
+ #### 3e. CI / headless instructions
251
+
252
+ For pipelines that can't use keychain: the token comes in via `ATLASSIAN_API_TOKEN` as a pipeline secret env var. No setup-skill changes needed; the lookup ladder already prefers env. Document this in the project's CI README rather than the skill.
253
+
254
+ ### Step 4 — Resolve cloudId
255
+
256
+ If acli is installed and authenticated:
257
+
258
+ ```bash
259
+ acli auth status --output json
260
+ # Returns { "site": "...", "email": "...", ... } — but cloudId is not always returned by acli.
261
+ ```
262
+
263
+ If the MCP is available, call `mcp__plugin_atlassian_atlassian__getAccessibleAtlassianResources` and pick the entry whose `url` matches the desired site. Its `id` is the cloudId.
264
+
265
+ If the user has multiple sites under their account, use `AskUserQuestion` to disambiguate, presenting each site's URL + cloudId as an option.
266
+
267
+ ### Step 5 — Write config files (split by ownership)
268
+
269
+ Shared fields → `.lisa.config.json` (committed). Developer-specific fields → `.lisa.config.local.json` (gitignored). This is non-negotiable: a developer's email pinned in the committed file would break every other developer on the project.
270
+
271
+ **Shared fields (committed):**
272
+
273
+ ```bash
274
+ touch .lisa.config.json
275
+ [ -s .lisa.config.json ] || echo '{}' > .lisa.config.json
276
+ jq --arg cloudid "$CLOUDID" \
277
+ --arg site "$SITE" \
278
+ '.atlassian = ((.atlassian // {}) | .cloudId = $cloudid | .site = $site)' \
279
+ .lisa.config.json > .lisa.config.json.tmp \
280
+ && mv .lisa.config.json.tmp .lisa.config.json
281
+ ```
282
+
283
+ **Developer-specific fields (local override, gitignored):**
284
+
285
+ ```bash
286
+ touch .lisa.config.local.json
287
+ [ -s .lisa.config.local.json ] || echo '{}' > .lisa.config.local.json
288
+ jq --arg email "$EMAIL" \
289
+ '.atlassian = ((.atlassian // {}) | .email = $email)' \
290
+ .lisa.config.local.json > .lisa.config.local.json.tmp \
291
+ && mv .lisa.config.local.json.tmp .lisa.config.local.json
292
+ ```
293
+
294
+ Confirm `.lisa.config.local.json` is gitignored (it should already be — lisa's bootstrap template adds it). If not, surface the issue and add it. NEVER write `email` to the committed file under any circumstances; if a user explicitly insists, refuse and explain why.
295
+
296
+ ### Step 6 — Verify
297
+
298
+ ```bash
299
+ jq -e '.atlassian.cloudId' .lisa.config.json >/dev/null
300
+ acli auth status # if acli installed
301
+ ```
302
+
303
+ Report success with the resolved `cloudId` and `site`. Direct the user to `/lisa:setup:jira` and/or `/lisa:setup:confluence` next, depending on what they need.
304
+
305
+ ## Idempotency
306
+
307
+ - Re-running this skill replaces the `atlassian` section's fields rather than appending. Use `jq` merge semantics (above) — never raw-append JSON.
308
+ - If the section already exists and matches the resolved values, exit successfully without prompting.
309
+ - If the section exists but mismatches (different site/cloudId), prompt via `AskUserQuestion` whether to overwrite or abort.
310
+
311
+ ## Rules
312
+
313
+ - Never write secrets to `.lisa.config.json`. Tokens stay in env / acli's `~/.config/acli/` / the MCP's keychain entry.
314
+ - Never edit `.claude/settings.json` by hand-concatenation — always use `jq` to preserve the JSON shape.
315
+ - Never default `tracker` or `source` from this skill — that is the job of `setup-jira` / `setup-confluence`.
316
+ - If the user has multiple Atlassian accounts available locally, ask explicitly which to pin to this project; do not silently pick one.
@@ -0,0 +1,245 @@
1
+ ---
2
+ name: setup-confluence
3
+ description: "Configure Confluence as the PRD source for this project. Writes `confluence.spaceKey` and/or `confluence.parentPageId` into `.lisa.config.json` and offers to set top-level `source: \"confluence\"`. Depends on /lisa:setup:atlassian — atlassian.cloudId must already be present. Idempotent."
4
+ allowed-tools: ["Bash", "Read", "Write", "Edit", "Skill", "AskUserQuestion"]
5
+ ---
6
+
7
+ # Setup Confluence: $ARGUMENTS
8
+
9
+ Pin a Confluence space (or a parent page within one) as the PRD-discovery scope for this project.
10
+
11
+ ## Workflow
12
+
13
+ ### Step 1 — Verify atlassian prerequisite
14
+
15
+ ```bash
16
+ cloudid=$(jq -r '.atlassian.cloudId // empty' .lisa.config.json 2>/dev/null)
17
+ if [ -z "$cloudid" ]; then
18
+ echo "Error: atlassian.cloudId not set. Run /lisa:setup:atlassian first." >&2
19
+ exit 1
20
+ fi
21
+ ```
22
+
23
+ If `atlassian` is missing, invoke `/lisa:setup-atlassian` via the Skill tool first, then resume.
24
+
25
+ ### Step 2 — Resolve scope
26
+
27
+ Two scoping modes (mutually compatible; both can be set):
28
+
29
+ - **Space scope** — discover PRDs anywhere in a Confluence space. Honor `--space=KEY` or list spaces via the active substrate:
30
+ - CLI: `acli confluence space list --output json`
31
+ - MCP: `mcp__plugin_atlassian_atlassian__getConfluenceSpaces` with `cloudId=$cloudid`
32
+ - **Parent-page scope** — discover PRDs only as descendants of one page. Honor `--parent=PAGE_ID` or ask the user via `AskUserQuestion` to provide it.
33
+
34
+ If neither arg is supplied, ask which mode (or both). Setting only `parentPageId` is valid; setting only `spaceKey` is valid; setting both narrows the scope.
35
+
36
+ ### Step 3 — Create lifecycle parent pages
37
+
38
+ Confluence PRD lifecycle is **parent-page-based**, not label-based (see the `config-resolution` rule for why — Atlassian's scoped API tokens cannot write labels). Each lifecycle role gets its own parent page; a PRD's state = which parent it's a child of.
39
+
40
+ #### 3a. Decide where the parents live
41
+
42
+ If `confluence.parentPageId` is set in config, the six parent pages are created as children of that page (keeps the lifecycle scoped to a sub-tree of the space). Otherwise, they're created at the space root.
43
+
44
+ ```bash
45
+ SPACE_ID=$(curl -s -H "Authorization: Basic $AUTH" \
46
+ "${GW}/api/v2/spaces?keys=$(jq -r '.confluence.spaceKey' .lisa.config.json)" \
47
+ | jq -r '.results[0].id')
48
+ PARENT_ROOT=$(jq -r '.confluence.parentPageId // empty' .lisa.config.json)
49
+ ```
50
+
51
+ #### 3b. Create each parent page
52
+
53
+ For each role in `[draft, ready, in_review, blocked, ticketed, shipped]`, create a page named after the role (`Draft`, `Ready`, `In Review`, `Blocked`, `Ticketed`, `Shipped`). Body: a short description of what PRDs in this state mean.
54
+
55
+ ```bash
56
+ create_parent() {
57
+ local role="$1" title="$2" body="$3"
58
+ local payload
59
+ payload=$(jq -n \
60
+ --arg sid "$SPACE_ID" \
61
+ --arg pid "$PARENT_ROOT" \
62
+ --arg t "$title" \
63
+ --arg b "$body" '
64
+ {
65
+ spaceId: $sid,
66
+ status: "current",
67
+ title: $t,
68
+ body: { representation: "storage", value: $b }
69
+ } + (if $pid != "" then { parentId: $pid } else {} end)
70
+ ')
71
+ curl -s -X POST -H "Authorization: Basic $AUTH" -H "Content-Type: application/json" \
72
+ -d "$payload" "${GW}/api/v2/pages" | jq -r '.id'
73
+ }
74
+
75
+ P_DRAFT=$(create_parent draft "Draft" "PRDs being authored; not yet ready for the agent queue.")
76
+ P_READY=$(create_parent ready "Ready" "PRDs flagged by humans as ready for agent ticketing.")
77
+ P_REVIEW=$(create_parent in_review "In Review" "PRDs the agent has claimed and is validating.")
78
+ P_BLOCKED=$(create_parent blocked "Blocked" "Validation failed — clarifying comments posted by the agent. Edit the PRD, then move back to Ready.")
79
+ P_TICKETED=$(create_parent ticketed "Ticketed" "Validated and tickets created. Tracked through the build queue from here.")
80
+ P_SHIPPED=$(create_parent shipped "Shipped" "All child tickets shipped. Terminal state.")
81
+ ```
82
+
83
+ Handle the "title already exists" case (400 BAD_REQUEST) by searching for an existing page with that title first and re-using its id rather than failing.
84
+
85
+ #### 3c. Write `confluence.parents` to config
86
+
87
+ ```bash
88
+ jq --arg d "$P_DRAFT" --arg r "$P_READY" --arg iv "$P_REVIEW" \
89
+ --arg b "$P_BLOCKED" --arg t "$P_TICKETED" --arg s "$P_SHIPPED" '
90
+ .confluence = ((.confluence // {})
91
+ | .parents = {
92
+ draft: $d,
93
+ ready: $r,
94
+ in_review: $iv,
95
+ blocked: $b,
96
+ ticketed: $t,
97
+ shipped: $s
98
+ })
99
+ ' .lisa.config.json > .lisa.config.json.tmp \
100
+ && mv .lisa.config.json.tmp .lisa.config.json
101
+ ```
102
+
103
+ ### Step 4 — Write top-level `confluence` section (spaceKey / parentPageId)
104
+
105
+ ```bash
106
+ jq --arg space "$SPACE_KEY" --arg parent "$PARENT_ID" '
107
+ .confluence = (
108
+ (.confluence // {})
109
+ | (if $space != "" then .spaceKey = $space else . end)
110
+ | (if $parent != "" then .parentPageId = $parent else . end)
111
+ )
112
+ ' .lisa.config.json > .lisa.config.json.tmp \
113
+ && mv .lisa.config.json.tmp .lisa.config.json
114
+ ```
115
+
116
+ This step is small now that the heavy lifting moved into 3c — but it's still where `spaceKey` and (optionally) `parentPageId` get persisted.
117
+
118
+ ### Step 5 — Offer to set top-level `source`
119
+
120
+ If `.source` is unset or differs from `"confluence"`, ask via `AskUserQuestion`:
121
+
122
+ > Confluence configured. Set top-level `source: "confluence"` so `/lisa:intake` (with no args) scans this space for PRDs?
123
+
124
+ Recommend "Yes" if the team's PRDs live in Confluence. If the team uses Notion or Linear for PRDs and Confluence is only for ad-hoc reference, recommend "No".
125
+
126
+ If yes:
127
+
128
+ ```bash
129
+ jq '.source = "confluence"' .lisa.config.json > .lisa.config.json.tmp \
130
+ && mv .lisa.config.json.tmp .lisa.config.json
131
+ ```
132
+
133
+ ### Step 6 — Create / update PRD Dashboard page
134
+
135
+ Confluence has no native swimlane / kanban view for label-driven lifecycles. To approximate the Notion-board experience, create a single "PRD Dashboard" page in the configured space that renders six `Content by Label` macros side-by-side — one per PRD lifecycle status (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`). The dashboard is the team's single-screen view of the PRD pipeline.
136
+
137
+ The `draft` column captures PRDs that have been created but not yet flipped to `ready` — useful for authors to track their own in-flight work and for editors to find PRDs that need a polish pass before they hit the agent queue.
138
+
139
+ #### Idempotency
140
+
141
+ If `confluence.dashboardPageId` already exists in `.lisa.config.json`, update that page rather than create a new one. Otherwise create.
142
+
143
+ #### Build the page body (Confluence storage format)
144
+
145
+ Five columns inside a `ac:layout` block. Each column is a `Content by Label` macro filtered to one label, scoped to the configured space (or parent page, if set).
146
+
147
+ Read the parent page IDs from config:
148
+
149
+ ```bash
150
+ P_DRAFT=$(jq -r '.confluence.parents.draft' .lisa.config.json)
151
+ P_READY=$(jq -r '.confluence.parents.ready' .lisa.config.json)
152
+ P_REVIEW=$(jq -r '.confluence.parents.in_review' .lisa.config.json)
153
+ P_BLOCKED=$(jq -r '.confluence.parents.blocked' .lisa.config.json)
154
+ P_TICKETED=$(jq -r '.confluence.parents.ticketed' .lisa.config.json)
155
+ P_SHIPPED=$(jq -r '.confluence.parents.shipped' .lisa.config.json)
156
+ ```
157
+
158
+ Build a `Children Display` macro per parent. The macro shows direct children of the specified page, automatically updating as PRDs move between parents:
159
+
160
+ ```xml
161
+ <ac:structured-macro ac:name="children" ac:schema-version="2">
162
+ <ac:parameter ac:name="page"><ac:link><ri:page ri:content-title="<TITLE>"/></ac:link></ac:parameter>
163
+ <ac:parameter ac:name="depth">1</ac:parameter>
164
+ <ac:parameter ac:name="sort">modified</ac:parameter>
165
+ <ac:parameter ac:name="reverse">true</ac:parameter>
166
+ <ac:parameter ac:name="all">false</ac:parameter>
167
+ </ac:structured-macro>
168
+ ```
169
+
170
+ Children Display targets a parent by **content-title** (not by id, in storage format). The `ri:content-title` attribute references the parent by its title within the current space.
171
+
172
+ Wrap the six macros in two rows of three columns (`ac:layout-section ac:type="three_equal"`). Confluence's `ac:layout` has no native 6-column preset; two rows of three is the cleanest readable layout. Row 1: Draft, Ready, In Review. Row 2: Blocked, Ticketed, Shipped.
173
+
174
+ The grouping is semantic too — top row covers the human-driven lead-up to agent pickup; bottom row covers agent-driven states post-pickup.
175
+
176
+ Heading each column with the status name and count keeps the board scannable:
177
+
178
+ ```xml
179
+ <ac:layout-cell>
180
+ <h2>Ready</h2>
181
+ <!-- Content by Label macro for prd-ready -->
182
+ </ac:layout-cell>
183
+ ```
184
+
185
+ Above the layout, include a short header describing what the page is and how to use it:
186
+
187
+ ```xml
188
+ <p>This page is the PRD pipeline view. PRDs live as child pages of one of six lifecycle
189
+ parents: <strong>Draft</strong>, <strong>Ready</strong>, <strong>In Review</strong>,
190
+ <strong>Blocked</strong>, <strong>Ticketed</strong>, <strong>Shipped</strong>.
191
+ Move a PRD between states by re-parenting the page (drag in the page tree, or
192
+ via the page's location settings).</p>
193
+ <p>To add a new PRD: create a page under <strong>Draft</strong>. When ready for
194
+ agent pickup, move it to <strong>Ready</strong>.</p>
195
+ ```
196
+
197
+ #### Write the page
198
+
199
+ Invoke `lisa:atlassian-access` with `operation: write-page`. The payload differs by mode:
200
+
201
+ - **Create**: `{ "spaceKey": "$SPACE", "title": "PRD Dashboard", "body": "<the storage-format XML built above>", "parentId": "$PARENT" }` (omit `parentId` if not set).
202
+ - **Update**: `{ "id": "<dashboardPageId>", "title": "PRD Dashboard", "body": "<...>" }` — body is fully replaced.
203
+
204
+ `atlassian-access`'s `write-page` CLI adapter is currently nominal (acli's Confluence surface is `view`-only as of writing), so this call falls through to the MCP adapter. That's acceptable — the dashboard is a one-time setup-only write; the lifecycle hot path (label add/remove) doesn't go through this.
205
+
206
+ #### Persist the page ID
207
+
208
+ ```bash
209
+ jq --arg id "$DASHBOARD_PAGE_ID" --arg url "$DASHBOARD_URL" \
210
+ '.confluence = ((.confluence // {})
211
+ | .dashboardPageId = $id
212
+ | .dashboardUrl = $url)' \
213
+ .lisa.config.json > .lisa.config.json.tmp \
214
+ && mv .lisa.config.json.tmp .lisa.config.json
215
+ ```
216
+
217
+ Both fields are committed (shared across developers — same dashboard for everyone).
218
+
219
+ #### Skip conditions
220
+
221
+ Skip Step 6 entirely if:
222
+
223
+ - `$ARGUMENTS` includes `--no-dashboard`.
224
+ - The MCP fallback is unavailable AND acli can't create pages (i.e., we have no path to write).
225
+ - The user declines via `AskUserQuestion` when offered.
226
+
227
+ ### Step 7 — Verify
228
+
229
+ ```bash
230
+ jq -e '.confluence.spaceKey // .confluence.parentPageId' .lisa.config.json >/dev/null
231
+ ```
232
+
233
+ Report success with the resolved scope (`spaceKey`, `parentPageId`, or both), whether `source` was set, and the PRD Dashboard URL if Step 6 ran.
234
+
235
+ ## Idempotency
236
+
237
+ - Re-running replaces fields cleanly (jq merge).
238
+ - Re-running does not re-prompt for `source` if it's already `"confluence"`.
239
+ - Re-running with an existing `dashboardPageId` updates the page in place rather than creating duplicates.
240
+
241
+ ## Rules
242
+
243
+ - Never invent a `spaceKey` or `parentPageId`. Resolve via the substrate or have the user provide it explicitly.
244
+ - Setting `source` is opt-in — per-skill invocations with an explicit URL always win, so `source` is just the no-arg default for batch flows.
245
+ - If the user wants both Notion and Confluence as PRD sources (rare), pick one for `source` and document that the other requires explicit URLs.