plugins/lisa-copilot/skills/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 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 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
lisa:atlassian-access; never call Atlassian directly.config-resolution).## Lisa Usage section on update; never append a second usage
section or silently drop ledger rows.ready down to draft/ready.confluence.parents.{draft,ready}) — never hardcode page ids.documentation
Onboard a user to the project via its LLM Wiki. Interviews the user about themselves in relation to the project, captures that to project-scoped memory only, then gives a guided tour of what the project is and sample questions they can ask. Use when someone is new to the project or asks to be onboarded. Read-mostly — it does not open PRs or write PII into the wiki.
documentation
Migrate an existing, hand-rolled wiki implementation onto the lisa-wiki kernel — phased and compatibility-first, with a strict no-loss guarantee. Use when adopting lisa-wiki in a repo that already has its own wiki/, ingest skills, docs, or roles. Renaming things into the canonical shape is fine; losing functionality or data is not. Ends by running /doctor.
development
Health-check the LLM Wiki. Reports orphan pages, contradictions, stale claims, broken internal links, missing index/log coverage, structure-manifest violations, and secret/tenant leaks. Use periodically or before hardening a wiki. Read-only — it reports findings, it does not fix them.
testing
Ingest source material into the LLM Wiki. With an argument (URL, file path, or prompt) it ingests that one source; with no argument it runs a full ingest across every enabled non-external-write source. Routes to the right connector, then runs the ordered pipeline (source note → synthesis → index → log → verify → state → commit/PR). Use whenever new knowledge should enter the wiki.