plugins/lisa-cursor/skills/verify-prd/SKILL.md
Initiative-level PRD acceptance gate. Given a PRD ref/URL (GitHub Issue, Linear project/issue, Notion page, Confluence page, or JIRA issue), resolves the source vendor, reads the PRD body and its generated top-level child work set via the prd-lifecycle-rollup contract (native hierarchy first, machine-readable generated-work section fallback — never reimplementing child enumeration), and confirms every required generated top-level work item is terminal before any verification runs. If any required top-level child is non-terminal, it reports the incomplete child set and STOPS without verifying or transitioning the PRD. When the guard passes, it runs spec-conformance against the original PRD requirements (via the spec-conformance skill) plus empirical verification appropriate to the shipped surface (via verification-lifecycle). On a CONFORMS verdict with all empirical checks passing it runs the PASS path: transitions the PRD shipped → verified and posts verification evidence. On a PARTIAL/DIVERGES conformance verdict or any failing empirical check it runs the self-healing FAIL path: it re-opens the PRD shipped → ticketed (NEVER blocked), creates build-ready fix tickets (via tracker-write with build_ready: true) for each missing/incorrect/divergent behavior — registered as the PRD's generated work — and posts a product-readable failure report (with a verification-round count) naming which requirements/ACs failed with observed-vs-expected evidence. The fix tickets auto-build, rollup re-ships the PRD once they are terminal, and a later intake cycle re-verifies — the loop closes itself and never auto-halts. Re-runs are idempotent: evidence/failure-report comments are regenerated in place via a stable sentinel marker (never appended, round incremented), fix tickets are deduped by a stable PRD-ref + requirement marker (referenced/updated, never duplicated), and the lifecycle transition is a no-op when the PRD already carries the target role (exactly one lifecycle label/status remains) — per the prd-lifecycle-rollup idempotency dedupe key (match by stable ref, never by title).
npx skillsauth add codyswanngt/lisa verify-prdInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
Security scan pending...
This skill is queued for security scanning. Results will appear when the scan completes.
/lisa:verify-prd <prd> is the initiative-level acceptance gate. It runs after a PRD is shipped (all generated top-level work terminal, per prd-lifecycle-rollup's rollup phase) and proves the shipped product actually matches the PRD — not merely that every ticket is closed. shipped is necessary but not sufficient for verified: a PRD can have every child ticket closed while still missing a requirement, diverging from its acceptance criteria, or failing a real user workflow.
This is distinct from /lisa:verify, which empirically verifies a single work item (a ticket / story / sub-task) in its target environment as part of that item's Build/Fix/Improve flow. /lisa:verify drives a build ticket to done at the leaf/build level; it does not read the PRD or judge initiative-level acceptance. /lisa:verify-prd operates one level up, over the whole initiative. See the prd-lifecycle-rollup rule's "PRD-level verification vs ticket verification" section for the full distinction.
$ARGUMENTS is a single PRD reference: a GitHub issue URL (or <org>/<repo>#<n>), a Linear project/issue URL, a Notion page URL, a Confluence page URL, or a JIRA issue key/URL.
Do not re-prompt once invoked. Like the *-prd-intake skills, the caller has already authorized the run by invoking the skill; asking the user to confirm before reading or before applying the guard defeats the purpose of a batchable acceptance gate. Run the front-half (resolve → read child set → guard) to completion and report.
This skill covers the read/guard front-half, both verdict branches (PASS and FAIL), and the idempotency of PRD-level verification:
prd-lifecycle-rollup contract.spec-conformance skill with the PRD as the spec source to build a section-by-section coverage matrix against the shipped product (never reimplementing the matrix).verification-lifecycle to run empirical checks appropriate to the PRD's surface (browser/computer-use, API, CLI, DB, screenshots, logs), honoring the verification rule. Quality gates (test/typecheck/lint) are not verification.CONFORMS and every applicable empirical check passes, transition the PRD lifecycle from the resolved shipped role to the resolved verified role (vendor-neutral via config-resolution) and post verification evidence (the coverage matrix + empirical proof artifacts) back on the PRD.ticketed + build-ready fix tickets (self-healing, never blocked) — when spec conformance is PARTIAL/DIVERGES, or any applicable empirical check fails (or a required surface is unavailable), move the PRD from the resolved shipped role back to the resolved ticketed role, create build-ready fix tickets via tracker-write (build_ready: true) for each missing/incorrect/divergent behavior — added to the PRD's generated top-level work — and post a product-readable failure report (with the verification-round count). PRD verification never moves the PRD to blocked. The fix tickets auto-build, rollup (*-prd-intake Phase 3f) re-ships the PRD once they are terminal, and a later intake cycle (Phase 3g) re-verifies — the loop closes itself.When verification passes, this skill runs the PASS branch of Phase 6 (shipped → verified). When it does not pass — spec conformance not CONFORMS, or any empirical check failing — this skill runs the FAIL branch of Phase 7 (shipped → ticketed + build-ready fix tickets + failure report); it does not leave the PRD at shipped and never uses blocked (PRD verification is self-healing — the fix tickets auto-build and the PRD re-ships and re-verifies). Re-running the skill against the same PRD produces no duplicate evidence comments, no duplicate fix tickets, and no duplicate lifecycle labels/statuses — the Phase 8 guards make each Phase 6/7 write idempotent, exactly as prd-backlink regenerates its ## Tickets section in place and github-prd-intake no-ops a rollup on an already-shipped PRD.
Detect the vendor from $ARGUMENTS the same way prd-ticket-coverage / prd-backlink do — from the host (or key shape for JIRA), never by guessing:
| Input shape | Vendor | Read surface |
|---|---|---|
| github.com/<org>/<repo>/issues/<n> or <org>/<repo>#<n> | GitHub Issues | gh CLI (Lisa uses the CLI exclusively for GitHub — no GitHub MCP) |
| linear.app/... | Linear | lisa:linear-access operation: get-project / lisa:linear-access operation: get-issue |
| notion.so / notion.site | Notion | mcp__claude_ai_Notion__notion-fetch (include_discussions: true) |
| *.atlassian.net/wiki/... | Confluence | mcp__atlassian__getConfluencePage (+ descendants) |
| JIRA issue key (e.g. PROJ-123) or *.atlassian.net/browse/... | JIRA | mcp__atlassian__getJiraIssue |
Read the PRD body via the vendor-appropriate surface. The vendor that owns the PRD source is what Phase 2 reads the child set from; it is independent of which tracker hosts the generated tickets (a Notion PRD can own JIRA tickets — the cross-vendor case is handled by the documented generated-work section in Phase 2).
Where the PRD lives in the same vendor as the configured tracker (.lisa.config.json), prefer reading the PRD ticket through lisa:tracker-read so this skill stays agnostic of which tracker hosts the work — tracker-read dispatches to lisa:github-read-issue / lisa:jira-read-ticket / lisa:linear-read-issue per config and returns the consolidated context bundle (PRD body, native children, links). For PRD sources with no tracker counterpart (Notion / Confluence / cross-vendor), read the PRD body directly via the vendor surface above.
Do NOT reimplement child enumeration. Consume the PRD→generated-top-level-work relationship recorded by the merged child-linking work (prd-backlink native linking + its always-written machine-readable generated-work section), exactly as github-prd-intake's rollup phase consumes it. Read the generated top-level work only — the PRD's created Epics and any top-level Story created directly under it — and exclude leaf Sub-tasks and any Story nested under a generated Epic, per the prd-lifecycle-rollup rule's generated-top-level-work contract.
Use two sources, native first, deduped by child-ref identity:
Native hierarchy (primary). Read the PRD's direct native children — these are its top-level children:
subIssues nodes (the GraphQL subIssues query lisa:github-read-issue Phase 3 uses; capture number title state url stateReason labels).list_issues({project})); for a PRD Issue, its sub-Issues (get_issue children). Capture each child's state (workflow state + category).lisa:jira-read-ticket Phase 5 uses ("Epic Link" = <PRD-KEY> or parent = <PRD-KEY>). Capture each child's statusCategory.Documented generated-work section (fallback / cross-vendor). When native hierarchy is unavailable (older host, cross-vendor PRD→tracker, or the relationship was only recorded in the PRD body), parse the machine-readable generated-work section prd-backlink writes to the PRD body (## Tickets, alias ## Generated Work). Enumerate the <!-- lisa:gw ref=… url=… type=… parent=… --> tokens; the generated top-level child set is every token whose parent is empty (top-level), exactly as prd-backlink documents. Tokens with a non-empty parent are descendants — skip them. For GitHub, this is the same awk extraction github-prd-intake Phase 3f.2 uses.
Dedupe by child-ref identity (owner/repo#number for GitHub, the issue/project identifier for Linear, the issue key for JIRA, the recorded ref for Notion/Confluence — the prd-lifecycle-rollup idempotency dedupe key) so a child appearing in both the native graph and the documented section is counted once. Match by stable ref, never by title.
If neither source yields any generated top-level child (the PRD generated nothing, or the relationship was never recorded), report no generated top-level children — cannot verify an empty PRD and STOP. Do not transition the PRD.
Apply the per-vendor terminal-state predicate from the prd-lifecycle-rollup rule to every generated top-level work item (cite the rule by slug — do not restate its predicate table here). In summary, a top-level child is:
done role label where used; Linear: a done-category completed state; JIRA: statusCategory.key == "done"; Notion/Confluence: the documented generated-work entry marked done). A generated Epic is terminal only when it has rolled up to its own terminal state per leaf-only-lifecycle — read the child's own resolved state; do not re-derive it from its leaves.stateReason == "not_planned") / canceled (Linear) / won't-do. It does not hold the PRD open and is excluded from the required set.done role). It holds the PRD open.The required set is the top-level children minus the terminal-but-dropped ones. Branch:
Any required top-level child is non-terminal (the "Given a PRD has generated child work that is not terminal" scenario):
shipped. Do not transition it to verified or blocked; do not close or archive it.- <ref> "<title>" — <state>, so product can see exactly what is blocking PRD-level verification.This guard exists because PRD-level acceptance is only meaningful once the work graph is actually complete. shipped is the precondition; verifying a PRD whose generated work is still in flight would produce a false PASS or FAIL against an incomplete product.
All required top-level children are terminal (at least one required child exists): the guard passes. Proceed to Phase 4 — Spec conformance and the rest of the PASS path below. The guard is the precondition for verification; only once the work graph is complete is it meaningful to check the shipped product against the PRD.
The guard proves the work graph is complete; spec conformance proves the shipped product matches what the PRD asked for, section by section. This is the "accountant lens" — did the initiative ship exactly the PRD's requirements, nothing silently dropped, nothing scope-crept?
Do NOT reimplement the coverage matrix. Invoke the existing spec-conformance skill with the PRD as the spec source:
/spec-conformance <PRD ref/URL>
Pass the same $ARGUMENTS PRD reference resolved in Phase 1. spec-conformance Phase 1 already accepts a GitHub issue URL / Linear identifier / JIRA key / Notion / Confluence PRD as its spec source and loads the full PRD body (via tracker-read or the vendor surface), so the PRD is the spec here — not a plan file or a leaf ticket. It then:
CONFORMS, PARTIAL, or DIVERGES.Capture the coverage matrix and verdict verbatim — both feed the evidence comment in Phase 6 and gate the PASS branch.
Spec conformance at the PRD level differs from the leaf-ticket conformance run during a single item's Build/Fix/Improve flow: the spec is the PRD itself (the whole initiative's requirements), and the shipped work is the union of all generated top-level children, not one branch's diff. The spec-conformance skill handles both because its spec source is parameterized; this skill simply hands it the PRD.
Branch on the verdict:
CONFORMS — continue to Phase 5 (empirical verification).PARTIAL or DIVERGES — verification has not passed. Do not run Phase 5 (empirical verification adds nothing once the spec already diverges) and do not enter the PASS path. Record the verdict and the matrix, then go to Phase 7 — FAIL with a CONFORMANCE_FAILED cause: the failed/missing/scope-crept requirements the matrix flagged become the failure report's findings and the seeds for the linked fix issues.Spec conformance reads the diff and the requirements; empirical verification runs the actual shipped system and observes results. Quality gates (tests, typecheck, lint) are prerequisites, not verification — a green test suite never substitutes for exercising the shipped product (see the verification rule).
The verification surface is PRD-dependent. A PRD that shipped a UI flow is verified through the browser; one that shipped an API through request/response captures; a CLI through command runs; a schema change through database queries; a background job through logs and queue inspection. Do not assume a fixed surface — classify it from what the PRD actually delivered.
Do NOT reinvent the verification machinery. Invoke the verification-lifecycle skill and follow its mandatory sequence (confirm quality gates → classify empirical types → check tooling → fail-fast → plan → execute → codify → loop) against the shipped initiative:
/verification-lifecycle
verification rule.product-walkthrough skill to drive the live product through a real browser and ground verification (and the eventual evidence comment) in what actually renders.test/typecheck/lint is not a verification plan.codify-verification to encode it as a regression test (Playwright for UI, integration test for API/DB, benchmark for performance) so the PRD's behavior cannot silently regress. Honor that step; it is mandatory for every empirical type except the inherently non-behavioral set the lifecycle exempts.If a required surface or its tooling is unavailable, follow the lifecycle's Escalation Protocol — declare the verification level (PARTIALLY VERIFIED / UNVERIFIED) and surface the gap rather than declaring a false PASS.
Branch on the result:
EMPIRICAL_FAILED cause: each failed check (or unavailable required surface) becomes a failure-report finding and a seed for a linked fix issue.Single-environment note. In a single-environment project (
main/production only, no dev/staging), the shipped surface is whatever production exposes. A project with no deployed application, sign-in, or end-to-end environment variables (Lisa itself) verifies on its CLI/dry-run surface — running the documentation/skill build and drift check — which is the empirical surface the PRD's Validation Journey declares. The surface is always PRD-dependent: read the PRD's Empirical Verification Plan and verify what it says ships.
shipped → verified and post evidenceReach this phase only when both are true:
CONFORMS, andIf either is false, do not enter this phase — record the verdict and route to Phase 7 — FAIL, which owns the shipped → ticketed (re-open + build-ready fix tickets) path.
verified and shipped rolesResolve the PRD-lifecycle roles from .lisa.config.json (then .lisa.config.local.json override) per the config-resolution rule — the same role-resolution the *-prd-intake skills use. Cite config-resolution for the role vocabulary; do not hardcode label strings except as the documented defaults. Resolution per vendor:
| Vendor | shipped role | verified role | Default verified |
|---|---|---|---|
| GitHub | github.labels.prd.shipped | github.labels.prd.verified | prd-verified (label) |
| Linear | linear.labels.prd.shipped | linear.labels.prd.verified | prd-verified (project/issue label) |
| Notion | notion.values.shipped | notion.values.verified | Verified (status value) |
| Confluence | confluence.parents.shipped | confluence.parents.verified | the Verified parent page id |
| JIRA | the configured shipped status | the configured verified status | per config-resolution |
For GitHub, resolve with the same helper github-prd-intake uses:
read_role() { # role default → resolved value (local override wins)
local role="$1" default="$2" local_v global_v
local_v=$(jq -r ".github.labels.prd.${role} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r ".github.labels.prd.${role} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
SHIPPED=$(read_role shipped prd-shipped)
VERIFIED=$(read_role verified prd-verified)
shipped → verifiedIdempotency guard (no-op if already verified). Before transitioning, read the PRD's current lifecycle role. If the PRD already carries $VERIFIED, the transition is a no-op — do not re-add the label/status, do not re-remove $SHIPPED (already gone). Record it as already verified (no-op) and proceed to 6.3 (the evidence comment is still refreshed in place, per Phase 8). This mirrors github-prd-intake Phase 3f.1's "no-op if already shipped" guard and the prd-lifecycle-rollup "rollup is keyed by the PRD's current state" rule (cite both by slug).
Otherwise apply the vendor-appropriate transition. This is the shipped → verified PASS hop the prd-lifecycle-rollup rule defines (cite it by slug; this skill is its PASS-path implementation, not a second source of truth):
shipped label and add the verified label. For GitHub:
gh issue edit <prd-num> --repo <org>/<repo> --remove-label "$SHIPPED" --add-label "$VERIFIED"
Verify exactly one PRD-lifecycle label remains afterward (the single-label invariant github-prd-intake enforces) — a re-run must never leave both $SHIPPED and $VERIFIED, nor two copies of $VERIFIED. For GitHub, close the PRD issue immediately after the label transition with gh issue close <prd-num> --repo <org>/<repo> --reason completed; if it is already closed, record that native closure was already satisfied. For Linear, set the project/issue label equivalently, then archive/complete the project or issue using the vendor's native terminal mechanism where available.notion.statusProperty (default Status) to the resolved verified value (default Verified). A status property holds exactly one value, so re-setting the same value is inherently a no-op. Then archive the page through lisa:notion-access where supported; if the page is already archived, record that native archival was already satisfied.parentId to confluence.parents.verified (the parent-page-based lifecycle; Atlassian scoped tokens cannot write labels — see config-resolution). A page has exactly one parent, so re-parenting to the same parent is a no-op. Then archive the page where the deployment supports archival; if archival is unavailable, report the capability-aware no-op or setup gap.verified status. An issue holds exactly one status; if already verified, skip the transition. Then resolve/close the issue using the configured terminal workflow transition where supported.verified is the terminal, product-owned PRD state; this skill is the only automated writer of it (intake/rollup never set it). After the verified transition, close, archive, or complete the PRD natively where the source vendor supports it. This close-out is mandatory and idempotent; do not introduce a configuration flag to skip it.
Post a verification-evidence comment back on the PRD, in the spirit of tracker-evidence (the vendor-neutral evidence poster). Because the evidence lands on the PRD source — which may be Notion or Confluence, not a tracker ticket — post via the vendor surface that owns the PRD: gh issue comment for GitHub, the Linear comment API, the Notion/Confluence page comment surface, or a JIRA comment. Where the PRD lives in the configured tracker, you may dispatch through tracker-evidence; for Notion/Confluence/cross-vendor PRDs, comment on the PRD page directly.
Idempotent — regenerate the evidence comment in place, never append. Lead the comment body with the stable sentinel marker <!-- lisa:verify-prd-evidence -->. Before posting, look up an existing evidence comment authored by this skill on the PRD whose body contains that sentinel (the same regenerate-don't-append discipline prd-backlink uses for its ## Tickets section; match by the marker, never by comment text or position). If one exists, edit it in place with the freshly regenerated body; only create a new comment when none exists. Per vendor:
gh issue view <prd-num> --repo <org>/<repo> --json comments and select the comment whose body contains <!-- lisa:verify-prd-evidence -->; update it with gh api -X PATCH /repos/<org>/<repo>/issues/comments/<comment-id> -f [email protected]. Only gh issue comment <prd-num> --body-file evidence.md when no marked comment exists.The marked comment is the single canonical evidence comment for the PRD — a re-run refreshes it, never stacking a second one. The evidence comment MUST include:
<!-- lisa:verify-prd-evidence --> as the first line, so the next run finds and regenerates this exact comment.shipped → verified — PASS.CONFORMS verdict.gh release upload pr-assets <files> --clobber and reference as plain URLs, per the tracker-evidence UI Evidence Checklist), request/response captures, query outputs, log excerpts, and the codified regression test(s) added.Then emit the PASS output block (below).
ticketed, create build-ready fix tickets, post a failure report (never blocked)Reach this phase when verification did not pass — i.e. either is true:
PARTIAL or DIVERGES (CONFORMANCE_FAILED cause), orEMPIRICAL_FAILED cause).PRD verification failure is self-healing, not a dead end. Instead of parking the PRD in blocked for a human, this phase: (1) moves the PRD back to ticketed (work in flight again), (2) creates build-ready fix tickets for the gaps so the build queue picks them up with no human promotion, and (3) posts a failure report. The fix tickets are added to the PRD's generated top-level work, so the existing machinery closes the loop on its own: the fix tickets build → reach terminal → the *-prd-intake rollup (Phase 3f) re-ships the PRD ticketed → shipped → the next intake cycle (Phase 3g) re-dispatches /lisa:verify-prd → PASS gives verified, FAIL runs this phase again with another round of fix tickets. This is the FAIL counterpart of the Phase 6 PASS hop and is the shipped → ticketed FAIL hop the prd-lifecycle-rollup rule's "Closing the loop" section defines (cite it by slug; this skill is its FAIL-path implementation, not a second source of truth).
PRD verification never moves the PRD to blocked. blocked is the intake (ready-stage validation) failure state, not the verification failure state — there is no prd-verifying / prd-verification-failed state either; the lifecycle stays small. The loop never auto-halts; the failure report carries a verification-round count so a human can spot a PRD stuck across repeated rounds, but the skill keeps creating fix tickets and re-verifying.
Carry forward the verdict cause and the concrete findings that produced it: from CONFORMANCE_FAILED, the matrix's missed/divergent/scope-crept requirements; from EMPIRICAL_FAILED, each failing check (the requirement/AC it exercised, the tool/command, observed vs expected, and any captured artifacts). These findings drive both the failure report (7.3) and the fix issues (7.4).
shipped and ticketed rolesResolve the PRD-lifecycle roles from .lisa.config.json (then .lisa.config.local.json override) per the config-resolution rule — the same role-resolution Phase 6.1 and the *-prd-intake skills use. Cite config-resolution for the role vocabulary; do not hardcode label strings except as the documented defaults. Resolution per vendor:
| Vendor | shipped role | ticketed role | Default ticketed |
|---|---|---|---|
| GitHub | github.labels.prd.shipped | github.labels.prd.ticketed | prd-ticketed (label) |
| Linear | linear.labels.prd.shipped | linear.labels.prd.ticketed | prd-ticketed (project/issue label) |
| Notion | notion.values.shipped | notion.values.ticketed | Ticketed (status value) |
| Confluence | confluence.parents.shipped | confluence.parents.ticketed | the Ticketed parent page id |
| JIRA | the configured shipped status | the configured ticketed status | per config-resolution |
For GitHub, resolve with the same helper Phase 6.1 / github-prd-intake use:
read_role() { # role default → resolved value (local override wins)
local role="$1" default="$2" local_v global_v
local_v=$(jq -r ".github.labels.prd.${role} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r ".github.labels.prd.${role} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
SHIPPED=$(read_role shipped prd-shipped)
TICKETED=$(read_role ticketed prd-ticketed)
shipped → ticketedIdempotency guard (no-op if already ticketed). Before transitioning, read the PRD's current lifecycle role. If the PRD already carries $TICKETED (the common re-run case — a prior failed round already re-opened it and its fix tickets are still in flight), the transition is a no-op — do not re-add the label/status, do not re-remove $SHIPPED (already gone). Record it as already ticketed (no-op) and proceed to 7.3, where the existing failure report is updated in place (not stacked, round count incremented) and 7.4, where existing fix tickets are referenced rather than re-created. This mirrors github-prd-intake Phase 3f.1's "no-op if already shipped" guard and the prd-lifecycle-rollup "rollup is keyed by the PRD's current state" rule (cite both by slug).
Otherwise apply the vendor-appropriate transition. This is the shipped → ticketed FAIL hop from the prd-lifecycle-rollup rule's "Closing the loop" section (cite it by slug) — a deliberate backward hop that puts the PRD back into "work in flight" so its new fix tickets are the in-flight work the rollup waits on:
shipped label and add the ticketed label. For GitHub:
gh issue edit <prd-num> --repo <org>/<repo> --remove-label "$SHIPPED" --add-label "$TICKETED"
Verify exactly one PRD-lifecycle label remains afterward (the single-label invariant github-prd-intake enforces) — a re-run must never leave both $SHIPPED and $TICKETED, nor two copies of $TICKETED. For Linear, set the project/issue label equivalently.notion.statusProperty (default Status) to the resolved ticketed value (default Ticketed). A status property holds exactly one value, so re-setting the same value is inherently a no-op.parentId to confluence.parents.ticketed (the parent-page-based lifecycle; Atlassian scoped tokens cannot write labels — see config-resolution). A page has exactly one parent, so re-parenting to the same parent is a no-op.ticketed status. An issue holds exactly one status; if already ticketed, skip the transition.Do not close or archive the PRD here, and never move it to blocked — ticketed signals "verification found gaps; fix work is in flight," and the PRD re-ships and re-verifies automatically once that work lands.
Post a failure report comment back on the PRD, via the same vendor surface Phase 6.3 uses for evidence (gh issue comment for GitHub, the Linear comment API, the Notion/Confluence page comment surface, or a JIRA comment; dispatch through tracker-evidence where the PRD lives in the configured tracker). The report is written for a non-engineer product owner — plain language, no stack traces dumped raw. Capture its URL/anchor so the fix issues in 7.4 can back-link to it.
Idempotent — regenerate the failure report in place, never append. Lead the comment body with the stable sentinel marker <!-- lisa:verify-prd-failure-report -->. Before posting, look up an existing failure-report comment on the PRD whose body contains that sentinel (match by the marker, never by comment text or position — the same regenerate-don't-append discipline as Phase 6.3). If one exists, edit it in place with the freshly regenerated findings (so a re-run-after-a-previous-failure refreshes the same report rather than stacking a second one); only create a new comment when none exists. The GitHub mechanics are identical to Phase 6.3 (gh issue view --json comments to find the marked comment, gh api -X PATCH .../issues/comments/<id> to update, gh issue comment only when absent). It MUST include:
<!-- lisa:verify-prd-failure-report --> as the first line, so the next run finds and regenerates this exact comment.shipped → ticketed — FAIL (re-opened for fixes), the cause (CONFORMANCE_FAILED or EMPIRICAL_FAILED), and Round: N — the count of failed verification rounds for this PRD. Read the prior in-place failure report's round and increment (start at 1). The loop never auto-halts on a high count, but surfacing it lets a human notice a PRD stuck across repeated fix-and-re-verify rounds.CONFORMANCE_FAILED cause, the section-by-section matrix from Phase 4 verbatim with the PARTIAL/DIVERGES verdict, so the missed/divergent/scope-crept rows are visible.gh release upload pr-assets <files> --clobber and referenced as plain URLs per the tracker-evidence UI Evidence Checklist, request/response captures, query outputs, log excerpts).For each failed/missing/incorrect/divergent finding, create a build-ready fix ticket via tracker-write with build_ready: true (the vendor-neutral writer) — never by hand-rolling gh issue create, so each ticket passes the same quality gates (tracker-validate) every Lisa ticket does: three-audience description, Gherkin acceptance criteria, labels, and explicit relationship discovery. build_ready: true makes the build queue (lisa:intake build side / *-build-intake) auto-claim it with no human promotion — that is what makes the loop self-healing. Group findings that share one root cause into one fix ticket; do not fan out one ticket per matrix cell when several rows are the same defect.
Idempotent — dedupe fix issues by a stable marker; reference/update, never duplicate. This is the "re-run after a previous failure with the same missing behavior" scenario: the prior run already opened a fix issue for that requirement, so the re-run must find and reuse it, not create a second one. Apply the prd-lifecycle-rollup idempotency dedupe key discipline (match by a stable ref, never by title):
<!-- lisa:verify-prd-fix prd=<prd-ref> req=<stable-req-id> -->.gh issue list --repo <org>/<repo> --state open --search '"<!-- lisa:verify-prd-fix prd=<prd-ref> req=<stable-req-id> -->"' --json number,url,body — or fetch and grep the marker); on Linear/JIRA, query by the marker stored in the body/a custom field. Match on the marker, never on the issue title (a title may have been edited; the marker is the stable identity).tracker-write.Each fix issue (whether freshly created or referenced/updated) MUST:
<!-- lisa:verify-prd-fix prd=<prd-ref> req=<stable-req-id> --> in its body, so the next run finds and reuses it.Relates to link; on Linear, set the relation; on JIRA, add the issue link and remote link.tracker-write → the vendor *-validate-issue gate.tracker-write with build_ready: true, and registered in the PRD's generated top-level work: refresh the PRD's ## Tickets / generated-work section via lisa:prd-backlink and, where the host supports it, link it as a native sub-issue/child of the PRD. This is what closes the loop — the *-prd-intake rollup (Phase 3f) holds the PRD in ticketed until every fix ticket is terminal, and a re-verify's Phase 2/3 then counts them as required children.Pass each new fix ticket's spec to tracker-write with build_ready: true (which dispatches to github-write-issue / jira-write-ticket / linear-write-issue per config — each honors build_ready). Collect the created and referenced refs/URLs, register them as PRD generated work (item 6), and fold them into the failure report's Fix tickets list (7.3 item 7).
Why not reopen children? The generated top-level children are already terminal (that is the Phase 3 precondition for verification). A failed PRD-level acceptance is a new defect discovered against the shipped initiative, so it gets new fix issues linked to the PRD — not a reopen of closed build tickets, which would corrupt their build lifecycle (
leaf-only-lifecycle).
Then emit the FAIL output block (below).
/lisa:verify-prd MUST be safe to re-run against the same PRD — after a fix attempt, in a batch sweep, or simply twice. A re-run produces no duplicate evidence comments, no duplicate fix issues, and no duplicate lifecycle labels/statuses. This is the same guarantee prd-backlink gives for its ## Tickets section and github-prd-intake gives for its rollup; this skill consumes the prd-lifecycle-rollup rule's idempotency dedupe key (cite by slug — match by stable ref, never by title), it does not invent a second one.
The guards are woven into Phases 6 and 7 above; this phase collects them as one contract:
Evidence / failure-report comments — regenerate in place, never append. Each is led by a stable HTML-comment sentinel: <!-- lisa:verify-prd-evidence --> (PASS, Phase 6.3) and <!-- lisa:verify-prd-failure-report --> (FAIL, Phase 7.3). Before posting, find the existing comment whose body contains the sentinel and edit it in place; create a new comment only when none exists. The sentinel is matched literally — never the comment text, author display name, or position. A second run thus refreshes the one canonical comment rather than stacking a duplicate (the regenerate-don't-append discipline from prd-backlink).
Fix issues — dedupe by a stable marker, reference don't duplicate. Each fix issue carries <!-- lisa:verify-prd-fix prd=<prd-ref> req=<stable-req-id> -->, keyed by the PRD ref + a stable requirement/AC identity. Before creating a fix issue, search for an open issue carrying that exact marker; if found, reference/update it instead of creating a second one (Phase 7.4). The dedupe key is the marker (a stable ref), never the issue title — a renamed fix issue is still matched by its marker, and two distinct requirements get two distinct markers even if their titles collide (prd-lifecycle-rollup: "Match by stable ref, never by title"). A closed prior fix issue does not suppress a new one (a re-failure after a closed fix is a genuine regression).
Lifecycle transition + verified native close-out — no-op when already satisfied. The Phase 6.2 / 7.2 transition is keyed by the PRD's current state: if the PRD already carries $VERIFIED (PASS) or $TICKETED (FAIL), the transition is a no-op — no re-label, no second copy of the label/status — mirroring github-prd-intake Phase 3f.1's "no-op if already shipped." After any transition, exactly one PRD-lifecycle label/status remains (the single-label invariant); a re-run never leaves both $SHIPPED and the target role, nor two copies of the target role. For Notion/Confluence/JIRA the single-value status/parent makes re-setting the same value inherently idempotent. On the PASS path, provider-native closure/archive/completion is also idempotent: if it is already closed, archived, or completed, record the satisfied state and do not error.
Because every Phase 6/7 write is one of these three idempotent operations, the whole skill is idempotent: the end state after N runs equals the end state after 1 run — one evidence-or-failure comment, one fix issue per still-failing requirement, one lifecycle label/status. Computing the verdict itself is a pure function of the PRD's current state and its children's current states, so recomputing it on a re-run is safe (prd-lifecycle-rollup idempotency rule).
Emit a single fenced text block so callers can parse it.
## verify-prd: <PRD title>
PRD: <ref/URL> (vendor: <github|linear|notion|confluence|jira>)
PRD lifecycle state: <shipped | verified | ticketed>
Generated top-level children read: <n> (source: native | documented | both)
### Terminal-child guard
- <ref> "<title>" — <terminal|terminal-but-dropped|incomplete>: <state>
- ...
Required top-level children: <n> Terminal: <n> Incomplete: <n>
### Spec conformance (only when guard passed)
Verdict: <CONFORMS | PARTIAL | DIVERGES>
<coverage matrix summary: requirements covered / missed / scope-crept>
### Empirical verification (only when conformance CONFORMS)
Surface: <browser | api | cli | db | logs | ...> (PRD-dependent)
<each check — tool/command → PASS/FAIL → artifact ref; codified test(s)>
### Lifecycle transition (PASS or FAIL)
shipped → verified (role: <resolved verified role>) evidence posted: <link> # on VERIFIED_PASS (re-run: evidence comment regenerated in place; transition no-op if already verified)
shipped → ticketed (re-opened for fixes; round: N) fix tickets (ready): <refs> failure report: <link> # on CONFORMANCE_FAILED | EMPIRICAL_FAILED (re-run: failure report regenerated in place + round incremented; fix tickets deduped by marker; transition no-op if already ticketed; never blocked)
### Verdict: VERIFIED_PASS | CONFORMANCE_FAILED | EMPIRICAL_FAILED | GUARD_BLOCKED | NO_CHILDREN
GUARD_BLOCKED — one or more required top-level children are non-terminal; verification did not run; the PRD was left at shipped.NO_CHILDREN — no generated top-level children found; cannot verify; the PRD was left untouched.CONFORMANCE_FAILED — guard passed but spec conformance returned PARTIAL/DIVERGES; empirical verification was skipped; the FAIL path ran — the PRD was re-opened shipped → ticketed, build-ready fix tickets were created and registered as PRD generated work, and a product-readable failure report (with the round count) was posted (Phase 7). The PRD re-ships and re-verifies once the fix tickets are terminal; it is never moved to blocked.EMPIRICAL_FAILED — guard passed and conformance CONFORMS, but an applicable empirical check failed or a required surface was unavailable; the FAIL path ran — the PRD was re-opened shipped → ticketed, build-ready fix tickets were created and registered as PRD generated work, and a product-readable failure report (with the round count) was posted (Phase 7). The PRD re-ships and re-verifies once the fix tickets are terminal; it is never moved to blocked.VERIFIED_PASS — guard passed, conformance CONFORMS, every applicable empirical check passed and was codified; the PRD was transitioned shipped → verified and verification evidence was posted (Phase 6).shipped → verified and the FAIL hop shipped → ticketed. The front-half (resolve → read child set → guard) is read-only and never transitions the PRD. After the guard passes and verification runs, this skill writes exactly one of two transitions: the Phase 6 PASS hop shipped → verified (when spec conformance is CONFORMS and every applicable empirical check passes), or the Phase 7 FAIL hop shipped → ticketed (when conformance is PARTIAL/DIVERGES or any applicable empirical check fails). The FAIL hop never uses blocked — it re-opens the PRD to ticketed with build-ready fix tickets (the self-healing loop), introducing no new failure state. The guard-blocked and no-children paths run no verification and leave the PRD at shipped untouched.<!-- lisa:verify-prd-evidence --> / <!-- lisa:verify-prd-failure-report -->); fix issues are deduped by a stable PRD-ref + requirement marker (<!-- lisa:verify-prd-fix prd=… req=… -->) and referenced/updated rather than re-created; the lifecycle transition is a no-op when the PRD already carries the target role, leaving exactly one lifecycle label/status. The dedupe key is the prd-lifecycle-rollup idempotency dedupe key — match by stable ref, never by title — and the no-op-already-at-target-role guard mirrors github-prd-intake Phase 3f.1.tracker-write, never by hand. Each fix issue is created through the vendor-neutral writer so it passes the same tracker-validate quality gate (three-audience description, Gherkin ACs, labels, relationships) every Lisa ticket does. Fix issues are new defects against the shipped initiative, back-linked to the PRD and the failure report — never reopens of the already-terminal generated children (leaf-only-lifecycle).prd-lifecycle-rollup native linking + machine-readable generated-work section). The two-source read here mirrors github-prd-intake Phase 3f.2 — same sources, same dedupe-by-child-ref, same top-level-only boundary.spec-conformance skill (the single source of truth for the coverage matrix and the CONFORMS/PARTIAL/DIVERGES verdict); Phase 5 invokes verification-lifecycle (which in turn invokes codify-verification and, for UI, product-walkthrough). This skill orchestrates those skills against the PRD; it does not duplicate their logic.verification rule).verified is product-owned and terminal. This skill is the only automated writer of the verified role; intake/rollup never set it. The PASS hop also performs provider-native close/archive/completion where supported, and that close-out is mandatory and idempotent.blocked; it re-opens to ticketed. The FAIL hop sets the existing ticketed PRD role (config-resolution) and creates build-ready fix tickets registered as the PRD's generated work, so the lifecycle stays small (prd-lifecycle-rollup "No extra failure states") and self-heals — the fix tickets auto-build, rollup re-ships the PRD, and a later cycle re-verifies. blocked remains the intake (ready-stage validation) failure role, not the verification one; the FAIL hop never closes or archives the PRD.prd-lifecycle-rollup generated-top-level-work contract).done resolution, the dedupe-by-child-ref idempotency key, and the shipped → verified | ticketed PRD-level verification hops all come from the prd-lifecycle-rollup rule; the verified/shipped/blocked/ticketed role vocabulary comes from config-resolution. This skill is a consumer of those contracts, not a second source of truth.spec-conformance — Phase 4 invokes it with the PRD as the spec source; it owns the section-by-section coverage matrix and the CONFORMS/PARTIAL/DIVERGES verdict. This skill never reimplements that matrix.verification-lifecycle — Phase 5 invokes it to run empirical verification of the shipped surface (classify → check tooling → plan → execute → codify → loop). It in turn invokes codify-verification and, for UI surfaces, product-walkthrough.codify-verification — turns each passing empirical verification into a regression test so the PRD's verified behavior cannot silently regress; invoked transitively via verification-lifecycle.product-walkthrough — drives the live product through a real browser to ground UI-surface verification and the evidence comment in what actually renders.tracker-evidence — the vendor-neutral evidence poster whose UI Evidence Checklist and pr-assets upload mechanics Phase 6.3 (PASS evidence) and Phase 7.3 (FAIL failure report) follow when commenting on the PRD.tracker-write — the vendor-neutral ticket writer Phase 7.4 invokes to create each linked fix issue (dispatching to github-write-issue / jira-write-ticket / linear-write-issue per config), so every fix issue clears the tracker-validate quality gate (Gherkin ACs, three-audience description, labels, relationships). This skill never hand-rolls issue creation.prd-backlink — the regenerate-in-place-via-marker idempotency pattern Phase 6.3 / 7.3 / 8 follow: it regenerates its ## Tickets section from the current child set on every run (never appending) and dedupes by child-ref. The evidence/failure-report sentinel comments here apply the same discipline to PRD comments.github-prd-intake — the no-op-if-already-at-target-role guard Phase 6.2 / 7.2 / 8 mirror: its Phase 3f.1 rollup is a no-op on a PRD already carrying $SHIPPED, and it enforces the single-label invariant after every transition. This skill applies the same guard to the verified / ticketed hops.prd-lifecycle-rollup — the vendor-neutral source of truth for PRD→generated-top-level-work ownership, the per-vendor terminal predicate, the shipped rollup, the shipped → verified (pass) / shipped → ticketed (fail) PRD-level verification hops, the "no extra failure states" rule (the FAIL hop re-opens to ticketed and never uses blocked), the "Closing the loop" self-healing dispatch, and the idempotency dedupe key ("match by stable ref, never by title"; no-op already-shipped rollup). This skill consumes that contract — implementing the shipped → verified PASS hop, the shipped → ticketed FAIL hop, and the Phase 8 idempotency guards (marker-based comment regeneration, marker-based fix-ticket dedupe, no-op-already-at-target-role transition) — citing the rule by slug rather than restating its taxonomy.verification — defines what counts as empirical verification (the Verification Types table) and that quality gates (test/typecheck/lint) are prerequisites, not verification. Phase 5 honors it when classifying and running the surface-appropriate checks.leaf-only-lifecycle — governs the build lifecycle of leaf work units and how a generated Epic rolls up from its own children; this skill trusts that bottom-up rollup when reading a top-level child's resolved state.config-resolution — the PRD-lifecycle role vocabulary (shipped, verified, blocked, ticketed), the per-vendor verified role maps (prd-verified label for GitHub/Linear, Verified status for Notion, confluence.parents.verified parent page) Phase 6.1 resolves, the per-vendor ticketed role maps Phase 7.1 resolves (the FAIL hop re-opens to ticketed, not blocked; blocked remains the intake-stage failure role, not used by verify-prd), and the env-keyed done map the terminal predicate resolves against.tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.