Write GitHub PRD: $ARGUMENTS
Create (or update) a PRD issue in the configured source repo. Invoked by lisa-prd-source-write
when source = github; do not call directly from a vendor-neutral caller.
$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,
and optional ideation_ledger_payload from lisa-project-ideation via lisa-research.
Phase 1 — Resolve repo and PRD lifecycle labels
ORG=$(jq -r '.github.org // empty' .lisa.config.local.json 2>/dev/null); ORG="${ORG:-$(jq -r '.github.org // empty' .lisa.config.json)}"
REPO=$(jq -r '.github.repo // empty' .lisa.config.local.json 2>/dev/null); REPO="${REPO:-$(jq -r '.github.repo // empty' .lisa.config.json)}"
[ -z "$ORG" ] || [ -z "$REPO" ] && { echo "Error: github.org / github.repo not set in .lisa.config.json."; exit 1; }
ROLE_RESOLVER="${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-plugins/lisa}}/scripts/resolve-lifecycle-role.mjs"
# Resolve the FULL PRD lifecycle vocabulary from config (never hard-code names) — needed so the
# "exactly one role" reconcile and the past-ready check work for projects that renamed any label.
PRD_DRAFT=$(node "$ROLE_RESOLVER" --role prd.draft --vendor github --intent write) || exit $?
PRD_READY=$(node "$ROLE_RESOLVER" --role prd.ready --vendor github --intent write) || exit $?
PRD_IN_REVIEW=$(node "$ROLE_RESOLVER" --role prd.in_review --vendor github --intent write) || exit $?
PRD_BLOCKED=$(node "$ROLE_RESOLVER" --role prd.blocked --vendor github --intent write) || exit $?
PRD_TICKETED=$(node "$ROLE_RESOLVER" --role prd.ticketed --vendor github --intent write) || exit $?
PRD_SHIPPED=$(node "$ROLE_RESOLVER" --role prd.shipped --vendor github --intent write) || exit $?
PRD_VERIFIED=$(node "$ROLE_RESOLVER" --role prd.verified --vendor github --intent write) || exit $?
# All lifecycle labels (for one-of reconcile) and the "progressed past ready" set (never down-rank):
ALL_PRD_LABELS=("$PRD_DRAFT" "$PRD_READY" "$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
PROGRESSED=("$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
Resolve the target role label from initial_role: ready → $PRD_READY, otherwise $PRD_DRAFT.
Create the label lazily if missing (gh label create <name> --repo $ORG/$REPO ...).
Phase 2 — Dedupe by marker (search before create)
The marker (e.g. [lisa-project-ideation] idea=<key>) is embedded in the issue body. Search for an
existing open PRD issue carrying it — match on the marker, never on the title:
EXISTING=$(gh issue list --repo "$ORG/$REPO" --state open --search "\"$MARKER\" in:body" --json number,url --jq '.[0].number // empty')
- If
source_refwas passed, use that issue as the target (skip the search). - If an existing open PRD issue is found, this is an update — reuse it, do not create a second.
- If
gh's search index hasn't caught up (eventual consistency), additionallygh issue list … --json number,bodyand grep the body for the marker before deciding to create.
Phase 3 — Create or update
Marker + usage-ledger preservation (both paths). Before writing any body, ensure it contains
exactly one marker line — inject <!-- $MARKER --> if the caller's synthesized body doesn't
already carry it. Never write a markerless body (including on UPDATE or when source_ref is
passed): a body without the marker breaks future dedupe. If the body already has the marker, leave
the single instance. If the live issue 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.
Exploratory ideation run ledger (both paths). When the write was initiated by
lisa-project-ideation, carries a project-ideation marker, or includes
ideation_ledger_payload, persist a managed ## Exploratory Ideation Run Ledger section in the PRD
body. Prefer the managed section over a comment so the PRD itself remains the operator's source of
truth; use a managed comment only if the body cannot be updated. Populate the fields from
ideation_ledger_payload when present, falling back to marker, initial_role, repo config, and
runtime metadata only for missing fields. Keep one managed section by replacing the content between
stable markers:
## Exploratory Ideation Run Ledger
<!-- lisa:exploratory-ideation-run-ledger:start -->
- timestamp: <ISO-8601 run timestamp>
- automation_id: <Codex/Claude automation id or unavailable>
- repo: <org>/<repo>
- prd_ready: true|false
- persona_evidence_refs: <comma-separated source refs or unavailable>
- selected_idea: <selected idea title/key>
- dedupe_marker: <MARKER>
- prd_url: <created or reused PRD URL>
- outcome: created|reused
- lifecycle_role_after_write: draft|ready|in_review|blocked|ticketed|shipped|verified
- rejected_overlap_candidates: <issue refs/titles considered and rejected, or none>
- expected_empirical_verification_artifact: <artifact ref or unavailable>
<!-- lisa:exploratory-ideation-run-ledger:end -->
On CREATE, write a ledger entry with outcome: created, the selected marker, the created PRD URL,
and the lifecycle role applied by this write. On UPDATE/reuse, write outcome: reused, preserve the
same dedupe marker, record the reused PRD URL, and report the lifecycle role that remains after
reconciliation. If the live PRD has progressed past ready, do not downgrade it while recording the
reuse ledger; the lifecycle_role_after_write value must be the existing progressed role. Preserve
exactly one PRD lifecycle label in the same pass as the ledger write.
CREATE (no existing issue):
- Write the marker-normalized PRD body to a temp file.
gh issue create --repo "$ORG/$REPO" --title "$TITLE" --body-file /tmp/prd-body.md --label "$ROLE_LABEL"- Capture the returned issue number/URL.
- Rewrite the PRD body with the managed
## Exploratory Ideation Run Ledgersection populated foroutcome: createdwhen the caller supplied project-ideation ledger inputs, thengh issue edit <n> --body-file /tmp/prd-body.md. This second write is allowed because the URL is not known until after creation. - If
github.projects.v2is enabled, resolve the created PRD issue node id and invokelisa-github-project-v2withoperation: ensure-itemandcontent_node_id: <issue-node-id>.outcome: disabled→ continue normally.outcome: addedorreused→ continue normally; membership is now present.outcome: warning(required: false) → preserve the exact warning and keep the PRD issue write as the durable success.outcome: blocked(required: true) → surface the exact failure and stop returning success; do not report Project coordination as completed.
UPDATE (existing issue or source_ref):
gh issue edit <n> --repo "$ORG/$REPO" --body-file /tmp/prd-body.mdwith the marker-normalized body (regenerate in place; never drop the marker, the managed## Exploratory Ideation Run Ledgersection, or an existing managed## Lisa Usagesection). When the caller supplied project-ideation ledger inputs, replace the managed ledger content with anoutcome: reusedentry.- Reconcile the lifecycle label to exactly one: add
$ROLE_LABEL, remove every other label in the resolved${ALL_PRD_LABELS[@]}set (the config-resolved names — not a hard-coded list) viagh issue edit <n> --add-label / --remove-label. Never leave a PRD carrying two lifecycle labels.- Exception: do not down-rank a PRD whose current label is in the resolved
${PROGRESSED[@]}set (already pastready). If so, leave it and reportreused (already past ready).
- Exception: do not down-rank a PRD whose current label is in the resolved
- Re-resolve the live PRD issue node id and invoke
lisa-github-project-v2withoperation: ensure-itemso updates keep the PRD present in the configured shared Project without duplicating membership writes. Branch ondisabled/added/reused/warning/blockedexactly as in CREATE.
Phase 4 — Return
Return a structured result for lisa-prd-source-write to surface:
ref: "<org>/<repo>#<n>"
url: "https://github.com/<org>/<repo>/issues/<n>"
role: draft | ready # the lifecycle label now applied (or the PRD's current role when reused past ready)
marker: "<MARKER>"
outcome: created | reused
Rules
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-trackerPhase 1.45).Exactly one PRD lifecycle label at all times (leaf-only does not apply — PRDs are not build leaves).
Match dedupe by marker, never by title.
Preserve an existing canonical
## Lisa Usagesection on update; never append a second usage section or silently drop ledger rows.Never down-rank a PRD already past
ready.A closed prior PRD does not suppress a new one — a recurrence after closure is a genuine new PRD.
This is a source-side writer (
prd-*labels). It never touches build labels (status:*) — that islisa-github-write-issue's lane. Seeconfig-resolution"Self-host edge case".When GitHub Project coordination is enabled, always delegate membership to
lisa-github-project-v2; never inline separate ProjectV2 GraphQL from this skill.