plugins/lisa-copilot/skills/lisa-linear-write-prd/SKILL.md
Creates or idempotently updates a PRD as a Linear Project carrying exactly one PRD lifecycle project-label (`prd-draft` by default, or `prd-ready` when initial_role is ready so lisa-linear-prd-intake auto-claims it). The Linear PRD-source writer behind lisa-prd-source-write. Dedupes by a stable marker embedded in the Project description (matched by marker, never by name). Uses the Linear MCP.
npx skillsauth add codyswanngt/lisa lisa-linear-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 as a Linear Project in the configured workspace/team. Invoked by
lisa-prd-source-write when source = linear; do not call directly from a vendor-neutral caller.
Linear's PRD lifecycle uses project-level labels (prd-*), per config-resolution. (The Linear
PRD source models a PRD as a Project — the same shape lisa-linear-prd-intake scans.)
$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}}"; }
WORKSPACE=$(read_g '.linear.workspace' '')
TEAM=$(read_g '.linear.teamKey' '')
# Resolve the FULL PRD lifecycle vocabulary from config (never hard-code names) so the one-of
# reconcile and the past-ready check are correct even when a project renamed any label.
PRD_DRAFT=$(read_g '.linear.labels.prd.draft' 'prd-draft')
PRD_READY=$(read_g '.linear.labels.prd.ready' 'prd-ready')
PRD_IN_REVIEW=$(read_g '.linear.labels.prd.in_review' 'prd-in-review')
PRD_BLOCKED=$(read_g '.linear.labels.prd.blocked' 'prd-blocked')
PRD_TICKETED=$(read_g '.linear.labels.prd.ticketed' 'prd-ticketed')
PRD_SHIPPED=$(read_g '.linear.labels.prd.shipped' 'prd-shipped')
PRD_VERIFIED=$(read_g '.linear.labels.prd.verified' 'prd-verified')
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")
[ -z "$WORKSPACE" ] && { echo "Error: linear.workspace not set in .lisa.config.json."; exit 1; }
[ -z "$TEAM" ] && { echo "Error: linear.teamKey not set in .lisa.config.json. A team key is required to create or scope a Linear Project."; exit 1; }
Resolve the target project-label from initial_role: ready → $PRD_READY, else $PRD_DRAFT.
Resolve its label id via lisa-linear-access operation: list-project-labels (create via
lisa-linear-access operation: create-project-label if missing).
The marker is embedded in the Project description. Find an existing Project carrying it — match the
marker, never the project name:
lisa-linear-access operation: list-projects scoped to the team/workspace (filtered by the prd-* label
set when supported), then inspect each candidate's description via
lisa-linear-access operation: get-project for the marker. If source_ref was passed, target it directly.Marker + usage-ledger preservation (both paths). Before writing the description, ensure it
contains exactly one marker line — inject the marker if the synthesized description lacks it.
Never write a markerless description (including UPDATE / source_ref): that breaks future
dedupe. If the live Project description 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.
CREATE: lisa-linear-access operation: save-project with:
name: $TITLEdescription: the marker-normalized full PRD markdownteamIds: [<resolved team id>]labelIds: [<role label id>] (exactly one PRD lifecycle label)state: Linear Project default (e.g. backlog)UPDATE (existing project or source_ref): save_project with the project id and only the
changed fields — regenerate the marker-normalized description without dropping the managed
## Lisa Usage section, and reconcile labels to exactly one PRD lifecycle label: add the role
label, remove every other label in the resolved
${ALL_PRD_LABELS[@]} set (config-resolved names, not a hard-coded list). Do not down-rank a
Project whose current label is in the resolved ${PROGRESSED[@]} set (already past ready) — leave
it and report reused (already past ready).
ref: "<linear-project-id-or-slug>"
url: "<project url>"
role: draft | ready # (or the Project's 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).
Exactly one PRD lifecycle project-label at all times.
Match dedupe by marker, never by project name.
Preserve an existing canonical ## Lisa Usage section on update; never append a second usage
section or silently drop ledger rows.
Never down-rank a Project already past ready.
Source-side writer (prd-* project labels) — never touches issue-level build labels (status:*),
which are lisa-linear-write-issue's lane (see config-resolution self-host separation).
Resolve label names from config (linear.labels.prd.*) — never hardcode.
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.