plugins/lisa/skills/lisa-confluence-write-prd/SKILL.md
Creates or idempotently updates a PRD as a Confluence page parented under the configured lifecycle parent page (the draft parent by default, or the ready parent when initial_role is ready so lisa-confluence-prd-intake auto-claims it). The Confluence PRD-source writer behind lisa-prd-source-write. Confluence models PRD state by PARENT PAGE (not labels), per config-resolution. Dedupes by a stable marker embedded in the page body, found via CQL (matched by marker, never by title). All Atlassian access goes through lisa-atlassian-access.
npx skillsauth add codyswanngt/lisa lisa-confluence-write-prdInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create (or update) a PRD page in Confluence. Invoked by lisa-prd-source-write when
source = confluence; do not call directly from a vendor-neutral caller. All Confluence
operations go through lisa-atlassian-access — never call the Atlassian API/MCP or acli directly.
Confluence's PRD lifecycle uses parent pages, not labels (scoped API tokens can't write
Confluence labels — see config-resolution "Confluence PRD lifecycle uses parent pages"). A PRD's
state is which lifecycle parent it lives under; "promote to ready" = re-parent to the ready parent.
$ARGUMENTS carries the lisa-prd-source-write spec: title, body (full PRD markdown),
initial_role (draft | ready, default draft), dedupe_key, marker, optional source_ref.
read_g() { local lv gv; lv=$(jq -r "$1 // empty" .lisa.config.local.json 2>/dev/null); gv=$(jq -r "$1 // empty" .lisa.config.json 2>/dev/null); echo "${lv:-${gv:-$2}}"; }
SPACE=$(read_g '.confluence.spaceKey' '')
CLOUDID=$(read_g '.atlassian.cloudId' '')
[ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set in .lisa.config.json."; exit 1; }
# Resolve the FULL set of lifecycle parents from config (never hard-code) — needed for the target
# parent, the past-ready reverse-lookup, and to derive the space when spaceKey is absent.
DRAFT_PARENT=$(read_g '.confluence.parents.draft' '')
READY_PARENT=$(read_g '.confluence.parents.ready' '')
IN_REVIEW_PARENT=$(read_g '.confluence.parents.in_review' '')
BLOCKED_PARENT=$(read_g '.confluence.parents.blocked' '')
TICKETED_PARENT=$(read_g '.confluence.parents.ticketed' '')
SHIPPED_PARENT=$(read_g '.confluence.parents.shipped' '')
VERIFIED_PARENT=$(read_g '.confluence.parents.verified' '')
# "Progressed past ready" parents (never re-parent a PRD down from these):
PROGRESSED_PARENTS=("$IN_REVIEW_PARENT" "$BLOCKED_PARENT" "$TICKETED_PARENT" "$SHIPPED_PARENT" "$VERIFIED_PARENT")
Resolve the target parent from initial_role: ready → $READY_PARENT, otherwise $DRAFT_PARENT.
If the needed parent id is unset, stop and report that /lisa:setup:confluence must provision the
lifecycle parent pages — do not create a PRD outside the lifecycle scaffolding.
Resolve the space (config allows parent-page-only setups with no spaceKey). If $SPACE is
empty, derive it from the target lifecycle parent: lisa-atlassian-access operation: read-page id: <target parent> and read its space key from the response. If neither confluence.spaceKey is set nor
a space can be derived from the parent, stop and report that a space could not be established —
do not attempt a CQL search or create without it.
The marker is embedded in the page body. Find an existing PRD page carrying it — match the marker,
never the title:
lisa-atlassian-access operation: search-pages cql: 'space = "<SPACE>" AND text ~ "<marker>"'
If source_ref was passed, target that page directly. If a page with the marker exists → update;
else → create.
Storage-format body + marker (both paths). Convert the PRD markdown to Confluence storage
format (XHTML): #/##/### → <h1>/<h2>/<h3>, paragraphs → <p>, lists → <ul>/<ol><li>,
fenced code → <ac:structured-macro ac:name="code">. Embed the marker as a storage comment
(<!-- $MARKER -->) or a small <p> so future CQL dedupe finds it. The body must always contain
exactly one marker; never write a markerless page (CREATE or UPDATE). If the live page body
already contains the canonical managed ## Lisa Usage section, preserve it verbatim unless the
caller intentionally supplied an updated canonical section; use the shared lisa-usage-accounting
serializer/merge path rather than hand-editing ledger rows in storage XHTML.
CREATE: lisa-atlassian-access operation: write-page (create form) with a payload that sets:
title: $TITLEspace: $SPACE (resolved in Phase 1)ancestors/parentId: the resolved lifecycle parent ($DRAFT_PARENT or $READY_PARENT)body: the storage-format, marker-normalized body above.UPDATE (existing page or source_ref):
confluence-prd-intake documents): first operation: read-page id: <page-id> to read the current version.number, then operation: write-page (edit form) with
the marker-normalized storage body and version.number bumped to current+1. A PUT without the
current version is rejected; never drop the marker or an existing managed ## Lisa Usage section
on the edit.${PROGRESSED_PARENTS[@]} set (already past ready). If so, leave it
and report reused (already past ready). Reverse-lookup the current parent in
confluence.parents.* to determine its role before re-parenting.ref: "<confluence-page-id>"
url: "<page url>"
role: draft | ready # derived from the lifecycle parent the page now lives under (or its current role when reused past ready)
marker: "<MARKER>"
outcome: created | reused
The PRD body's requirements MUST conform to prd-definition-of-ready: identified atoms (R1, R2, …), one behavior each in an EARS-pattern shape, each with a measurable fit criterion, plus the non-functional checklist. This governs factory-authored bodies; human-authored PRDs are validated at intake instead (*-to-tracker Phase 1.45).
All access via lisa-atlassian-access; never call Atlassian directly.
State is the parent page, not a label — never attempt Confluence label writes (they 401 on scoped
tokens; see config-resolution).
Match dedupe by marker, never by title.
Preserve an existing canonical ## Lisa Usage section on update; never append a second usage
section or silently drop ledger rows.
Never re-parent a PRD already past ready down to draft/ready.
Resolve parents from config (confluence.parents.{draft,ready}) — never hardcode page ids.
development
Prepare a machine — a fresh laptop or a throwaway container — to run coding agents, before any repository exists. Detects which of Lisa's supported agents (Claude Code, Codex, Cursor, OpenCode, Antigravity, Copilot) are already installed, asks which credential manager the machine uses (Bitwarden, 1Password, Doppler, Vault, AWS, or none), and installs only what is missing, each by its vendor's own preferred method. Idempotent, headless by default, and emits a Dockerfile for a spin-up/spin-down environment. Run it on a new machine, in a container, or before cloning anything.
tools
Provision and verify a remote execution environment for a host project — Codex Cloud today, other remote surfaces as they are added. Generates a repository-owned setup script that installs the declared toolchain, materializes secrets through lisa-secrets-access, and runs the project's own hook. Provisions by API where one exists, by driving the vendor console where one does not, and by emitting exact config otherwise — then proves the result with the same read-back regardless of which tier did the work. Use before dispatching any work with executionEnv.
tools
Bring a developer's machine in line with the toolchain the project declares. Reports every tool in remoteEnv.tools that is missing, outdated, or unpinned for this platform, and installs the missing ones into ~/.local/bin from the same pinned, checksummed entries the remote surfaces use — but only when asked. Same manifest, same pins, same installers as lisa-setup-remote-env; what differs is consent and that the pin is a floor rather than an equality. Run it on a fresh checkout, after a manifest change, or when a tool fails at the moment of use.
tools
Route one unit of work to a remote execution surface. Reads the executionEnv parameter (local by default, codex-cloud or claude-web today), verifies the environment is provisioned and bound to this repository, submits a thin skill invocation, records the task identifier to .lisa/remote-dispatch.json, and exits without polling. Routing only — the remote runs the identical skill from the identical repository. Composable and inline: other skills invoke it via the Skill tool rather than users calling it directly.