@codyswann/lisa 2.61.1 → 2.62.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/package.json +2 -2
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/agents/confluence-prd-intake.md +1 -1
  5. package/plugins/lisa/agents/github-prd-intake.md +1 -1
  6. package/plugins/lisa/agents/linear-prd-intake.md +1 -1
  7. package/plugins/lisa/agents/notion-prd-intake.md +1 -1
  8. package/plugins/lisa/commands/intake.md +1 -1
  9. package/plugins/lisa/commands/project-ideation.md +3 -3
  10. package/plugins/lisa/commands/repair-intake.md +6 -0
  11. package/plugins/lisa/commands/research.md +3 -3
  12. package/plugins/lisa/commands/setup-automations.md +6 -0
  13. package/plugins/lisa/commands/tear-down-automations.md +6 -0
  14. package/plugins/lisa/commands/verify-prd.md +2 -2
  15. package/plugins/lisa/rules/config-resolution.md +60 -0
  16. package/plugins/lisa/rules/intent-routing.md +4 -3
  17. package/plugins/lisa/rules/prd-lifecycle-rollup.md +10 -2
  18. package/plugins/lisa/rules/repo-scope-split.md +18 -1
  19. package/plugins/lisa/skills/confluence-prd-intake/SKILL.md +11 -1
  20. package/plugins/lisa/skills/confluence-write-prd/SKILL.md +103 -0
  21. package/plugins/lisa/skills/confluence-write-prd/agents/openai.yaml +4 -0
  22. package/plugins/lisa/skills/github-build-intake/SKILL.md +13 -0
  23. package/plugins/lisa/skills/github-prd-intake/SKILL.md +15 -1
  24. package/plugins/lisa/skills/github-write-issue/SKILL.md +13 -4
  25. package/plugins/lisa/skills/github-write-prd/SKILL.md +100 -0
  26. package/plugins/lisa/skills/github-write-prd/agents/openai.yaml +4 -0
  27. package/plugins/lisa/skills/implement/SKILL.md +13 -6
  28. package/plugins/lisa/skills/intake/SKILL.md +3 -2
  29. package/plugins/lisa/skills/jira-build-intake/SKILL.md +13 -0
  30. package/plugins/lisa/skills/jira-write-ticket/SKILL.md +8 -0
  31. package/plugins/lisa/skills/linear-build-intake/SKILL.md +13 -0
  32. package/plugins/lisa/skills/linear-prd-intake/SKILL.md +11 -1
  33. package/plugins/lisa/skills/linear-write-issue/SKILL.md +10 -2
  34. package/plugins/lisa/skills/linear-write-prd/SKILL.md +90 -0
  35. package/plugins/lisa/skills/linear-write-prd/agents/openai.yaml +4 -0
  36. package/plugins/lisa/skills/notion-access/SKILL.md +2 -0
  37. package/plugins/lisa/skills/notion-prd-intake/SKILL.md +11 -1
  38. package/plugins/lisa/skills/notion-write-prd/SKILL.md +107 -0
  39. package/plugins/lisa/skills/notion-write-prd/agents/openai.yaml +4 -0
  40. package/plugins/lisa/skills/prd-source-write/SKILL.md +80 -0
  41. package/plugins/lisa/skills/prd-source-write/agents/openai.yaml +4 -0
  42. package/plugins/lisa/skills/project-ideation/SKILL.md +183 -80
  43. package/plugins/lisa/skills/repair-intake/SKILL.md +403 -0
  44. package/plugins/lisa/skills/repair-intake/agents/openai.yaml +4 -0
  45. package/plugins/lisa/skills/research/SKILL.md +19 -3
  46. package/plugins/lisa/skills/research/agents/openai.yaml +2 -2
  47. package/plugins/lisa/skills/setup-automations/SKILL.md +78 -0
  48. package/plugins/lisa/skills/setup-automations/agents/openai.yaml +4 -0
  49. package/plugins/lisa/skills/tear-down-automations/SKILL.md +34 -0
  50. package/plugins/lisa/skills/tear-down-automations/agents/openai.yaml +4 -0
  51. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +4 -0
  52. package/plugins/lisa/skills/verify-prd/SKILL.md +41 -38
  53. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-expo/commands/exploratory-qa.md +3 -3
  58. package/plugins/lisa-expo/skills/exploratory-qa/SKILL.md +48 -18
  59. package/plugins/lisa-expo/skills/exploratory-qa/agents/openai.yaml +2 -2
  60. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-harper-fabric/commands/exploratory-qa.md +3 -3
  63. package/plugins/lisa-harper-fabric/skills/exploratory-qa/SKILL.md +48 -18
  64. package/plugins/lisa-harper-fabric/skills/exploratory-qa/agents/openai.yaml +2 -2
  65. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails/commands/exploratory-qa.md +3 -3
  72. package/plugins/lisa-rails/skills/exploratory-qa/SKILL.md +48 -18
  73. package/plugins/lisa-rails/skills/exploratory-qa/agents/openai.yaml +2 -2
  74. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki/skills/lisa-wiki-ingest/SKILL.md +30 -1
  79. package/plugins/src/base/agents/confluence-prd-intake.md +1 -1
  80. package/plugins/src/base/agents/github-prd-intake.md +1 -1
  81. package/plugins/src/base/agents/linear-prd-intake.md +1 -1
  82. package/plugins/src/base/agents/notion-prd-intake.md +1 -1
  83. package/plugins/src/base/commands/intake.md +1 -1
  84. package/plugins/src/base/commands/project-ideation.md +3 -3
  85. package/plugins/src/base/commands/repair-intake.md +6 -0
  86. package/plugins/src/base/commands/research.md +3 -3
  87. package/plugins/src/base/commands/setup-automations.md +6 -0
  88. package/plugins/src/base/commands/tear-down-automations.md +6 -0
  89. package/plugins/src/base/commands/verify-prd.md +2 -2
  90. package/plugins/src/base/rules/config-resolution.md +60 -0
  91. package/plugins/src/base/rules/intent-routing.md +4 -3
  92. package/plugins/src/base/rules/prd-lifecycle-rollup.md +10 -2
  93. package/plugins/src/base/rules/repo-scope-split.md +18 -1
  94. package/plugins/src/base/skills/confluence-prd-intake/SKILL.md +11 -1
  95. package/plugins/src/base/skills/confluence-write-prd/SKILL.md +103 -0
  96. package/plugins/src/base/skills/github-build-intake/SKILL.md +13 -0
  97. package/plugins/src/base/skills/github-prd-intake/SKILL.md +15 -1
  98. package/plugins/src/base/skills/github-write-issue/SKILL.md +13 -4
  99. package/plugins/src/base/skills/github-write-prd/SKILL.md +100 -0
  100. package/plugins/src/base/skills/implement/SKILL.md +13 -6
  101. package/plugins/src/base/skills/intake/SKILL.md +3 -2
  102. package/plugins/src/base/skills/jira-build-intake/SKILL.md +13 -0
  103. package/plugins/src/base/skills/jira-write-ticket/SKILL.md +8 -0
  104. package/plugins/src/base/skills/linear-build-intake/SKILL.md +13 -0
  105. package/plugins/src/base/skills/linear-prd-intake/SKILL.md +11 -1
  106. package/plugins/src/base/skills/linear-write-issue/SKILL.md +10 -2
  107. package/plugins/src/base/skills/linear-write-prd/SKILL.md +90 -0
  108. package/plugins/src/base/skills/notion-access/SKILL.md +2 -0
  109. package/plugins/src/base/skills/notion-prd-intake/SKILL.md +11 -1
  110. package/plugins/src/base/skills/notion-write-prd/SKILL.md +107 -0
  111. package/plugins/src/base/skills/prd-source-write/SKILL.md +80 -0
  112. package/plugins/src/base/skills/project-ideation/SKILL.md +183 -80
  113. package/plugins/src/base/skills/repair-intake/SKILL.md +403 -0
  114. package/plugins/src/base/skills/research/SKILL.md +19 -3
  115. package/plugins/src/base/skills/setup-automations/SKILL.md +78 -0
  116. package/plugins/src/base/skills/tear-down-automations/SKILL.md +34 -0
  117. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +4 -0
  118. package/plugins/src/base/skills/verify-prd/SKILL.md +41 -38
  119. package/plugins/src/expo/commands/exploratory-qa.md +3 -3
  120. package/plugins/src/expo/skills/exploratory-qa/SKILL.md +48 -18
  121. package/plugins/src/harper-fabric/commands/exploratory-qa.md +3 -3
  122. package/plugins/src/harper-fabric/skills/exploratory-qa/SKILL.md +48 -18
  123. package/plugins/src/rails/commands/exploratory-qa.md +3 -3
  124. package/plugins/src/rails/skills/exploratory-qa/SKILL.md +48 -18
  125. package/plugins/src/wiki/skills/lisa-wiki-ingest/SKILL.md +30 -1
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: github-write-prd
3
+ description: "Creates or idempotently updates a PRD as a GitHub Issue in the configured source repo, carrying exactly one PRD lifecycle label (`prd-draft` by default, or `prd-ready` when initial_role is ready so lisa:github-prd-intake auto-claims it). The GitHub PRD-source writer behind lisa:prd-source-write — the source-side counterpart of lisa:github-write-issue. Dedupes by a stable marker embedded in the issue body (matched by marker, never by title) so re-running ideation references the existing PRD instead of opening a duplicate. Uses the `gh` CLI exclusively."
4
+ allowed-tools: ["Skill", "Bash"]
5
+ ---
6
+
7
+ # Write GitHub PRD: $ARGUMENTS
8
+
9
+ Create (or update) a PRD issue in the configured source repo. Invoked by `lisa:prd-source-write`
10
+ when `source = github`; do not call directly from a vendor-neutral caller.
11
+
12
+ `$ARGUMENTS` carries the `lisa:prd-source-write` spec: `title`, `body` (full PRD markdown),
13
+ `initial_role` (`draft` | `ready`, default `draft`), `dedupe_key`, `marker`, optional `source_ref`.
14
+
15
+ ## Phase 1 — Resolve repo and PRD lifecycle labels
16
+
17
+ ```bash
18
+ ORG=$(jq -r '.github.org // empty' .lisa.config.local.json 2>/dev/null); ORG="${ORG:-$(jq -r '.github.org // empty' .lisa.config.json)}"
19
+ REPO=$(jq -r '.github.repo // empty' .lisa.config.local.json 2>/dev/null); REPO="${REPO:-$(jq -r '.github.repo // empty' .lisa.config.json)}"
20
+ [ -z "$ORG" ] || [ -z "$REPO" ] && { echo "Error: github.org / github.repo not set in .lisa.config.json."; exit 1; }
21
+
22
+ read_role() { # path default
23
+ 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)
24
+ echo "${lv:-${gv:-$2}}"
25
+ }
26
+ # Resolve the FULL PRD lifecycle vocabulary from config (never hard-code names) — needed so the
27
+ # "exactly one role" reconcile and the past-ready check work for projects that renamed any label.
28
+ PRD_DRAFT=$(read_role '.github.labels.prd.draft' 'prd-draft')
29
+ PRD_READY=$(read_role '.github.labels.prd.ready' 'prd-ready')
30
+ PRD_IN_REVIEW=$(read_role '.github.labels.prd.in_review' 'prd-in-review')
31
+ PRD_BLOCKED=$(read_role '.github.labels.prd.blocked' 'prd-blocked')
32
+ PRD_TICKETED=$(read_role '.github.labels.prd.ticketed' 'prd-ticketed')
33
+ PRD_SHIPPED=$(read_role '.github.labels.prd.shipped' 'prd-shipped')
34
+ PRD_VERIFIED=$(read_role '.github.labels.prd.verified' 'prd-verified')
35
+ # All lifecycle labels (for one-of reconcile) and the "progressed past ready" set (never down-rank):
36
+ ALL_PRD_LABELS=("$PRD_DRAFT" "$PRD_READY" "$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
37
+ PROGRESSED=("$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
38
+ ```
39
+
40
+ Resolve the target role label from `initial_role`: `ready` → `$PRD_READY`, otherwise `$PRD_DRAFT`.
41
+ Create the label lazily if missing (`gh label create <name> --repo $ORG/$REPO ...`).
42
+
43
+ ## Phase 2 — Dedupe by marker (search before create)
44
+
45
+ The `marker` (e.g. `[lisa-project-ideation] idea=<key>`) is embedded in the issue body. Search for an
46
+ existing **open** PRD issue carrying it — match on the marker, **never** on the title:
47
+
48
+ ```bash
49
+ EXISTING=$(gh issue list --repo "$ORG/$REPO" --state open --search "\"$MARKER\" in:body" --json number,url --jq '.[0].number // empty')
50
+ ```
51
+
52
+ - If `source_ref` was passed, use that issue as the target (skip the search).
53
+ - If an existing open PRD issue is found, this is an **update** — reuse it, do not create a second.
54
+ - If `gh`'s search index hasn't caught up (eventual consistency), additionally `gh issue list … --json number,body` and grep the body for the marker before deciding to create.
55
+
56
+ ## Phase 3 — Create or update
57
+
58
+ **Marker normalization (both paths).** Before writing any body, ensure it contains **exactly one**
59
+ marker line — inject `<!-- $MARKER -->` if the caller's synthesized body doesn't already carry it.
60
+ **Never write a markerless body** (including on UPDATE or when `source_ref` is passed): a body without
61
+ the marker breaks future dedupe. If the body already has the marker, leave the single instance.
62
+
63
+ **CREATE** (no existing issue):
64
+
65
+ 1. Write the marker-normalized PRD body to a temp file.
66
+ 2. ```bash
67
+ gh issue create --repo "$ORG/$REPO" --title "$TITLE" --body-file /tmp/prd-body.md --label "$ROLE_LABEL"
68
+ ```
69
+ 3. Capture the returned issue number/URL.
70
+
71
+ **UPDATE** (existing issue or `source_ref`):
72
+
73
+ 1. `gh issue edit <n> --repo "$ORG/$REPO" --body-file /tmp/prd-body.md` with the **marker-normalized**
74
+ body (regenerate in place; never drop the marker).
75
+ 2. Reconcile the lifecycle label to **exactly one**: add `$ROLE_LABEL`, remove every other label in
76
+ the resolved `${ALL_PRD_LABELS[@]}` set (the config-resolved names — not a hard-coded list) via
77
+ `gh issue edit <n> --add-label / --remove-label`. Never leave a PRD carrying two lifecycle labels.
78
+ - Exception: do **not** down-rank a PRD whose current label is in the resolved `${PROGRESSED[@]}`
79
+ set (already past `ready`). If so, leave it and report `reused (already past ready)`.
80
+
81
+ ## Phase 4 — Return
82
+
83
+ Return a structured result for `lisa:prd-source-write` to surface:
84
+
85
+ ```yaml
86
+ ref: "<org>/<repo>#<n>"
87
+ url: "https://github.com/<org>/<repo>/issues/<n>"
88
+ role: draft | ready # the lifecycle label now applied (or the PRD's current role when reused past ready)
89
+ marker: "<MARKER>"
90
+ outcome: created | reused
91
+ ```
92
+
93
+ ## Rules
94
+
95
+ - Exactly one PRD lifecycle label at all times (leaf-only does not apply — PRDs are not build leaves).
96
+ - Match dedupe by marker, never by title.
97
+ - Never down-rank a PRD already past `ready`.
98
+ - A *closed* prior PRD does not suppress a new one — a recurrence after closure is a genuine new PRD.
99
+ - This is a source-side writer (`prd-*` labels). It never touches build labels (`status:*`) — that is
100
+ `lisa:github-write-issue`'s lane. See `config-resolution` "Self-host edge case".
@@ -0,0 +1,4 @@
1
+ display_name: "Github Write PRD"
2
+ short_description: "Creates or idempotently updates a PRD as a GitHub Issue in the configured source repo, carrying exactly one PRD lifecycle label…"
3
+ default_prompt:
4
+ - "Use $github-write-prd: Creates or idempotently updates a PRD as a GitHub Issue in the configured source repo, carrying exactly one PRD lifecycle label…."
@@ -49,16 +49,23 @@ When deciding the agents to use, consider:
49
49
 
50
50
  Using the general-purpose agent in Team Lead session, Determine the name of this plan
51
51
 
52
- Using the general-purpose agent in Team Lead session, Determine what branch to use:
53
- 1. Are we already on a feature branch with an open pull request? Use that and set the target branch to the existing target of the pull request
54
- 2. Are we on a feature branch without an open pull request? Use the branch, but ask the human what branch to target for the PR
55
- 3. Are we on an environment branch (dev, staging, main, prod, production)? Check out a feature branch named for this plan and set the target branch of the PR to the environment branch
52
+ Using the general-purpose agent in Team Lead session, **determine the base branch from the ticket's target environment, then sync the working branch onto the latest of it before any work** — so implementation always builds on current target-environment code:
53
+
54
+ 1. **Resolve the target environment** from the resolved work item its `## Target Backend Environment` section (the field the `*-write-*` / `*-add-journey` skills record).
55
+ 2. **Map the environment to a base branch** via `.lisa.config.json` `deploy.branches` (e.g. `staging staging`, `production → main`) the forward direction of the same map the env-keyed `done` resolution uses in reverse (see the `config-resolution` rule). If the work item names **no** environment, the base branch is the **remote default branch** (`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, or `git remote set-head origin -a` then read `origin/HEAD`). If the named environment is absent from `deploy.branches`, or its branch does not exist on the remote, **stop and report** — never guess a base.
56
+ 3. **Establish the feature branch off the latest base, conflict-free:**
57
+ - `git fetch origin`.
58
+ - Already on a feature branch with an **open PR** → reuse it. If the PR's base ≠ the resolved base branch, surface the mismatch and re-target only with confirmation — the ticket's environment is the source of truth.
59
+ - Already on a feature branch with **no open PR** → reuse it; its PR base will be the resolved base branch (do not ask the human — the environment determines it).
60
+ - On an **environment / default branch** → check out a feature branch named for this plan (with the work-item ref prefix, per the linkage rules below) **from `origin/<base>`**.
61
+ - **Rebase the feature branch onto `origin/<base>` and resolve any merge conflicts BEFORE starting work.** If the conflicts cannot be resolved cleanly and safely, create a fix task for the agent team (with the conflicting file list and current merge state) and resolve it before implementation begins — never start work on stale or conflicted code.
62
+ 4. **The PR targets the resolved base branch** — carry it as `target_branch=<base>` into `lisa:git-submit-pr` (Verify flow). `git-submit-pr` already chooses a closing keyword when the base is the production/default branch and a non-closing reference for a non-terminal environment branch.
56
63
 
57
64
  When the request came from a tracker work item, preserve its native identifier for development linkage:
58
65
 
59
66
  - Capture `tracker_provider` and `work_item_ref` from the resolved input before creating or reusing a branch. Examples: `github` + `CodySwannGT/lisa#614`, `linear` + `ENG-123`, `jira` + `ENG-123`.
60
67
  - If a new branch is needed and the provider can link branches by identifier, include the identifier in the branch name before the human-readable slug. Linear and JIRA integrations commonly link from branch names; GitHub issue linkage is PR-body driven, but including the issue number in the branch name is still useful. Keep branch names URL-safe, for example `codex/ENG-123-add-checkout-copy` or `codex/614-add-checkout-copy`.
61
- - Pass the work-item ref and target branch to `lisa:git-submit-pr` when opening or updating the PR, for example `work_item_ref=CodySwannGT/lisa#614 target_branch=main`. The PR workflow owns provider-specific body text and must decide whether to use a closing keyword or a non-closing reference.
68
+ - Pass the work-item ref and target branch to `lisa:git-submit-pr` when opening or updating the PR, for example `work_item_ref=CodySwannGT/lisa#614 target_branch=<base resolved from the ticket's environment above>` (not hardcoded `main`). The PR workflow owns provider-specific body text and must decide whether to use a closing keyword or a non-closing reference.
62
69
  - If the provider has no native branch or PR development-linkage surface, continue without linkage and mention that the provider was skipped.
63
70
 
64
71
  Using the general-purpose agent in Team Lead session, Determine which flow applies:
@@ -122,7 +129,7 @@ Before shutting down the team, execute the Verify flow:
122
129
  3. Write e2e test encoding the verification
123
130
  4. Commit ALL outstanding changes in logical batches on the branch (minus sensitive data/information) — not just changes made by the agent team. This includes pre-existing uncommitted changes that were on the branch before the plan started. Do NOT filter commits to only "task-related" files. If it shows up in git status, it gets committed (unless it contains secrets).
124
131
  5. Push the changes - if any pre-push hook blocks you, create a task for the agent team to fix the error/problem whether it was pre-existing or not
125
- 6. Open a pull request with auto-merge on via `lisa:git-submit-pr`, including the work-item ref when one exists so the PR can be linked natively to the source issue.
132
+ 6. Open a pull request with auto-merge on via `lisa:git-submit-pr`, targeting the **base branch resolved from the ticket's environment** (`target_branch=<base>`, per the branch step above), and including the work-item ref when one exists so the PR can be linked natively to the source issue.
126
133
  7. PR Watch Loop: Monitor the PR using `git-submit-pr`'s drive-to-merge behavior. Create a task for the agent team to resolve any code review comments by either implementing the suggestions or commenting why they should not be implemented and close the comment. Fix any failing checks and repush. If the PR is `BEHIND`, blocked by stale review state, or cannot enable auto-merge, follow the harness-agnostic `git-submit-pr` re-sync, review-gate, and direct-merge fallback loop until the PR is actually merged or a blocking failure is surfaced.
127
134
  8. Merge the PR
128
135
  9. Monitor the deploy action that triggers automatically from the successful merge
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: intake
3
- description: "Vendor-agnostic scanner for Ready queues. Given a Notion PRD database URL → finds the first Ready PRD and runs lisa:plan. Given a Confluence space or parent page URL → finds the first prd-ready PRD and runs lisa:plan. Given a Linear workspace URL or team key → finds the first prd-ready Linear project and runs lisa:plan. Given a GitHub repo URL or `org/repo` token → finds the first prd-ready GitHub issue and runs lisa:plan. Given a JIRA project key or JQL filter → finds the first Ready ticket and runs lisa:implement. Given a GitHub repo URL or `org/repo` token when `tracker = github` → finds the first `status:ready` issue and runs lisa:implement. Designed as the cron target for /schedule — one eligible item per invocation, exits cleanly on empty. Symmetric counterpart to the single-item lisa:plan and lisa:implement skills."
3
+ description: "Vendor-agnostic scanner for Ready queues. Given a Notion PRD database URL → finds the first Ready PRD and runs lisa:plan. Given a Confluence space or parent page URL → finds the first prd-ready PRD and runs lisa:plan. Given a Linear workspace URL or team key → finds the first prd-ready Linear project and runs lisa:plan. Given a GitHub repo URL or `org/repo` token → finds the first prd-ready GitHub issue and runs lisa:plan. Given a JIRA project key or JQL filter → finds the first Ready ticket and runs lisa:implement. Given a GitHub repo URL or `org/repo` token when `tracker = github` → finds the first `status:ready` issue and runs lisa:implement. On the PRD side it also closes the loop: each cycle rolls a ticketed PRD up to shipped and dispatches lisa:verify-prd for one shipped PRD (shipped → verified on pass; on fail, re-opened shipped → ticketed with build-ready fix tickets that auto-build and re-verify — never blocked). Designed as the cron target for /schedule — one eligible item per invocation, exits cleanly on empty. Symmetric counterpart to the single-item lisa:plan and lisa:implement skills."
4
4
  allowed-tools: ["Skill", "Bash", "mcp__claude_ai_Notion__notion-fetch", "mcp__claude_ai_Notion__notion-search", "mcp__atlassian__getConfluencePage", "mcp__atlassian__getConfluenceSpaces", "mcp__atlassian__searchConfluenceUsingCql", "mcp__atlassian__getAccessibleAtlassianResources", "mcp__atlassian__searchJiraIssuesUsingJql", "mcp__atlassian__getJiraIssue", "mcp__linear-server__list_projects", "mcp__linear-server__list_teams", "mcp__linear-server__list_project_labels"]
5
5
  ---
6
6
 
@@ -88,7 +88,8 @@ The single-item skills (`lisa:plan`, `lisa:implement`) and the per-vendor batch
88
88
  - GitHub PRDs → `lisa:github-prd-intake` handles per-item: claim (relabel issue to `prd-in-review`), dry-run validate, branch to `prd-blocked` or `prd-ticketed` (with clarifying-question comments posted directly on the PRD issue), coverage audit
89
89
  - JIRA tickets → `lisa:jira-build-intake` handles per-item: claim, dispatch to `lisa:jira-agent`, transition to On Dev on success
90
90
  - GitHub build issues (when `tracker = github`) → `lisa:tracker-build-intake` → `lisa:github-build-intake` handles per-item: claim (relabel to `status:in-progress`), dispatch to `lisa:github-agent`, relabel to `status:on-dev` on success
91
- 5. **Stop after one item** — a claimed item, a safe-blocked container, or a per-item error ends the cycle. Remaining Ready items stay untouched for later scheduler invocations.
91
+ - **Closing the PRD loop:** beyond claiming one Ready PRD, every PRD scanner also runs the closure rollup (`ticketed → shipped`, Phase 3f) and **dispatches `lisa:verify-prd` for one shipped PRD** (Phase 3g) each cycle so a shipped PRD does not sit unverified. On pass the PRD goes `shipped → verified`; on fail it is re-opened `shipped → ticketed` with **build-ready fix tickets** that auto-build and trigger a re-verify (never `blocked`). The scanner only dispatches; `lisa:verify-prd` owns the transition (per the `prd-lifecycle-rollup` rule's "Closing the loop" section), and the self-healing loop continues until the PRD verifies.
92
+ 5. **Stop after one item** — a claimed Ready item, a safe-blocked container, or a per-item error ends the *ready-claim* portion of the cycle. The per-vendor PRD scanner still runs its rollup and one verify-prd dispatch. Remaining Ready items stay untouched for later scheduler invocations.
92
93
  6. **Summary report** — the single processed/skipped/error item, total processed, total errors.
93
94
 
94
95
  ## Schedule examples
@@ -118,6 +118,19 @@ If empty, report `"No tickets with Status=$READY. Nothing to do."` and exit. Thi
118
118
 
119
119
  ### Phase 3 — Process the first eligible ready ticket
120
120
 
121
+ #### 3a.0 Repo-scope gate (claim only current-repo tickets)
122
+
123
+ A JIRA project can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only tickets for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
124
+
125
+ 1. **Resolve the current repo** per `config-resolution` "Repo scoping" (`.repo` → `.github.repo` → `git remote get-url origin` basename). If unresolvable, stop and report — do not claim tickets you cannot scope.
126
+ 2. **Cheap path first.** Prefer candidates already carrying `repo:<current>` — a JIRA **label**, or a **component** equal to the repo name (accepted as an alias). Keep the Phase 2 scan broad (it must still see unlabeled tickets so they can be determined and stamped); this gate orders/filters the results.
127
+ 3. **Per candidate, apply the repo-scope decision (`repo-scope-split`):**
128
+ - Carries `repo:<other>` (label or component) → **skip** (leave it `ready` for that repo's own intake); next candidate.
129
+ - **Unlabeled** → determine the target repo(s) from the ticket (description, AC, technical approach) confirmed against the code surfaces, then **stamp** `repo:<name>` via `lisa:atlassian-access` `operation: write-ticket` (add the label / set the component) so later cycles filter cheaply; re-apply with the now-known repo.
130
+ - **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure to break it into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
131
+ - **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
132
+ 4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the ready set is exhausted — exit cleanly with `"No ready tickets for repo <current>. Nothing to do."`.
133
+
121
134
  #### 3a. Leaf-only claim gate (skip / safe-block containers)
122
135
 
123
136
  Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready status (e.g. `Ready` applied before this rule existed, or hand-applied to an Epic/Story) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:jira-write-ticket` and the validate-time S15 gate in `lisa:jira-validate-ticket`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
@@ -200,6 +200,13 @@ Before create/update, verify each field is populated where applicable:
200
200
  - Sprint: only if actively sprinting this work
201
201
  - Assignee: leave unset if unknown rather than auto-assigning
202
202
 
203
+ ### Build-ready control input (`build_ready`)
204
+
205
+ `build_ready` is an optional write-control input (default: **omitted**). It governs whether a **leaf** work unit is promoted to the build-ready role on create. It never overrides `leaf-only-lifecycle` — a container is never promoted regardless of `build_ready`. Unlike the label-based trackers, JIRA tickets are **already created not-ready** (in the project's default initial status, e.g. `TODO`/`Backlog`); the build-ready role is the configured `ready` status (`jira.workflow.ready`, default `Ready`), reached by an explicit transition.
206
+
207
+ - **Omitted** or **`build_ready: false`** → current behavior: leave the ticket in the project's default created status. No transition. A human (or `build_ready: true`) promotes it to the `ready` status later.
208
+ - **`build_ready: true`** → after create, transition the **leaf** to the resolved `ready` role so `lisa:intake` / `lisa:jira-build-intake` auto-picks it up (see Phase 6 CREATE step). Resolve the role name with the standard pattern (`.jira.workflow.ready` // `Ready`, local overrides global). Best-effort: if the transition is unreachable, record it and leave the ticket in its default status rather than failing the write.
209
+
203
210
  ## Phase 5.5 — Validate (Pre-write Gate)
204
211
 
205
212
  Before any write, invoke `lisa:jira-validate-ticket` with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the `lisa:jira-validate-ticket` schema, including `runtime_behavior_change`, `authenticated_surface`, and `artifacts_attached` flags so the right gates run.
@@ -222,6 +229,7 @@ If the validator reports `PASS`, continue to Phase 6.
222
229
  3. For each relationship from Phase 4b, invoke `lisa:atlassian-access` with `operation: link from: <K1> to: <K2> type: "<link-type>"`. Use the exact link-type names supported by the project; surface errors if an unknown type is passed.
223
230
  4. Attach remote links from Phase 4c (via `lisa:atlassian-access` `operation: write-ticket` UPDATE form or whatever remote-link operation is dispatched).
224
231
  5. If the ticket changes runtime behavior, invoke the `lisa:jira-add-journey` skill to append the Validation Journey section.
232
+ 6. **Build-ready promotion (only when `build_ready: true` and the ticket is a leaf work unit):** transition the new ticket to the resolved `ready` status via `lisa:atlassian-access` `operation: transition key: <K> to: "<ready-status>"` (resolve `<ready-status>` as `.jira.workflow.ready` // `Ready`, local overrides global). Skip this step entirely when `build_ready` is omitted or `false`, or for any container. If the transition is unreachable, record it and leave the ticket in its default status — do not fail the write.
225
233
 
226
234
  ### UPDATE
227
235
 
@@ -128,6 +128,19 @@ If empty, report `"No Linear Issues labeled $READY. Nothing to do."` and exit. C
128
128
 
129
129
  ### Phase 3 — Process the first eligible ready Issue
130
130
 
131
+ #### 3a.0 Repo-scope gate (claim only current-repo Issues)
132
+
133
+ A Linear team can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only Issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
134
+
135
+ 1. **Resolve the current repo** per `config-resolution` "Repo scoping" (`.repo` → `.github.repo` → `git remote get-url origin` basename). If unresolvable, stop and report.
136
+ 2. **Cheap path first.** Prefer candidates already carrying the `repo:<current>` label. Keep the Phase 2 scan broad so unlabeled Issues are still seen, determined, and stamped.
137
+ 3. **Per candidate, apply the repo-scope decision (`repo-scope-split`):**
138
+ - Carries `repo:<other>` → **skip** (leave it `ready` for that repo's own intake); next candidate.
139
+ - **Unlabeled** → determine the target repo(s) from the Issue + code surfaces, then **stamp** `repo:<name>` via `mcp__linear-server__save_issue` (resolve/create the label via `list_issue_labels`/`create_issue_label`) so later cycles filter cheaply; re-apply with the now-known repo.
140
+ - **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
141
+ - **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
142
+ 4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the ready set is exhausted — exit cleanly with `"No ready Issues for repo <current>. Nothing to do."`.
143
+
131
144
  #### 3a. Leaf-only claim gate (skip / safe-block containers)
132
145
 
133
146
  Build intake claims **only independently implementable leaf work units**. This enforces the claim-time arm of the vendor-neutral `leaf-only-lifecycle` rule: a parent/container that still carries a stale build-ready label (e.g. `status:ready` applied before this rule existed, or hand-applied to a Project-grouped parent Issue) is **never claimed** — intake skips it or safe-blocks it with a clear lifecycle-repair message. It is the claim-time complement to the write-time labeling in `lisa:linear-write-issue` and the validate-time S15 gate in `lisa:linear-validate-issue`; all three cite the same rule so the classification never drifts. **Never silently implement a container.**
@@ -81,7 +81,7 @@ draft → ready → in_review → blocked | ticketed → shipped → verified
81
81
 
82
82
  (Defaults: `prd-draft` / `prd-ready` / `prd-in-review` / `prd-blocked` / `prd-ticketed` / `prd-shipped` / `prd-verified`.)
83
83
 
84
- `verified` is the terminal state after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification reuses `blocked` rather than introducing a separate `verifying` / `verification-failed` state. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never sets, clears, or otherwise touches it. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
84
+ `verified` is the terminal state after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification does **not** use `blocked`; `/lisa:verify-prd` re-opens the PRD `shipped → ticketed` and creates build-ready fix tickets that auto-build and trigger a re-verify (the self-healing loop), introducing no `verifying` / `verification-failed` state. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never sets, clears, or otherwise touches it. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
85
85
 
86
86
  This skill transitions:
87
87
 
@@ -296,6 +296,16 @@ The set of **required** children for the all-terminal check is the top-level chi
296
296
 
297
297
  This phase implements exactly one PRD-lifecycle hop — `$TICKETED → $SHIPPED` — and the optional config-gated archive that follows it. All terminal-state semantics, the generated-top-level-work boundary, and the dedupe-by-child-ref idempotency come from the `prd-lifecycle-rollup` rule; this skill is its Linear implementation, not a second source of truth.
298
298
 
299
+ #### 3g. PRD verification dispatch (close the loop on shipped PRDs)
300
+
301
+ `shipped` and `verified` are distinct facts about a PRD (see the `prd-lifecycle-rollup` rule's "PRD-level verification vs ticket verification" and "Closing the loop" sections). Rollup (3f) only reaches `$SHIPPED`; the `shipped → verified` (pass) / `shipped → ticketed` (fail) hops are owned by `/lisa:verify-prd`. This phase **closes that loop** by dispatching the initiative-level acceptance gate for shipped PRDs. It never performs the verification transition itself — the "never sets the verification outcome" invariant holds: `lisa:verify-prd`, not this skill, sets `verified` (or, on failure, re-opens the PRD to `ticketed`).
302
+
303
+ Re-query the projects currently carrying the `$SHIPPED` label via `mcp__linear-server__list_projects` (filtered by the `$SHIPPED` project label, **including archived projects** — so PRDs archived on ship via `linear.labels.prd.rollup.closeOnShipped = true` are still dispatched to `lisa:verify-prd` for the shipped→verified progression). Pick the **first** one and invoke `lisa:verify-prd <project-url>`. Process **one shipped PRD per cycle** — `lisa:verify-prd` is a heavy full flow (spec-conformance + empirical verification + fix-issue creation), so it is bounded exactly like the single-ready-PRD claim in Phase 3; the scheduler drains the rest.
304
+
305
+ **Per-cycle combined bound:** each scheduler cycle dispatches at most one ready PRD (the Phase 3 single-ready-PRD claim) **and** at most one shipped PRD for verification (this Phase 3g dispatch), for a maximum of two PRD operations per cycle. Ready intake runs first (Phase 3), then shipped verify (Phase 3g).
306
+
307
+ `lisa:verify-prd` owns the outcome: on a CONFORMS verdict with all empirical checks passing it transitions `$SHIPPED → verified` and posts evidence; on a conformance miss or a failing/unavailable check it **re-opens the PRD `$SHIPPED → ticketed`** (never `blocked`) and creates **build-ready** fix tickets registered as the PRD's generated work, then posts a failure report — the fix tickets auto-build, rollup (3f) re-ships the PRD once they are terminal, and a later cycle re-verifies (the self-healing loop). Either branch moves the PRD out of `$SHIPPED`, so it is not re-picked this cycle; a PRD whose generated work is not actually terminal is guard-stopped by `lisa:verify-prd` (left `$SHIPPED`) — that is verify-prd's gate, not this skill's. This phase, like 3f, is **behaviorally identical across all four intake skills** (`github-prd-intake`, `linear-prd-intake`, `notion-prd-intake`, `confluence-prd-intake`) — only the `$SHIPPED` query surface differs; keep them aligned. Record the dispatched PRD + verify-prd's verdict in the summary.
308
+
299
309
  ### Phase 4 — Summary report
300
310
 
301
311
  After processing the single selected PRD, emit a summary:
@@ -203,7 +203,7 @@ If the item modifies an existing user-facing surface, a `lisa:product-walkthroug
203
203
 
204
204
  Before create/update, verify each field is populated where applicable:
205
205
 
206
- - **Labels**: include `status:ready` for new items; component labels (`component:<name>`); status / priority labels are NOT redundant with native fields — labels exist for portability and downstream queries.
206
+ - **Labels**: include `status:ready` for a new **leaf** work unit (Bug / Task / Sub-task / Improvement with no child work) per `leaf-only-lifecycle`, **unless `build_ready: false`** (see the Build-ready control input below); component labels (`component:<name>`); status / priority labels are NOT redundant with native fields — labels exist for portability and downstream queries. A container (Epic Project / Story with sub-issues / Spike) never receives `status:ready`.
207
207
  - **Native priority field**: 0–4 per Linear's scale; explicit, not "unset".
208
208
  - **Native estimate**: per Linear's team-configured estimate scale (often 0–8 Fibonacci); skip for Epic / Spike.
209
209
  - **ProjectMilestone**: when the team uses dated milestones, set the milestone on the Project (Epic) or on the Issue (when an Issue belongs to a milestone).
@@ -212,6 +212,14 @@ Before create/update, verify each field is populated where applicable:
212
212
 
213
213
  For Bug / Task / Sub-task, ensure the summary is prefixed with `[<repo-name>]`.
214
214
 
215
+ ### Build-ready control input (`build_ready`)
216
+
217
+ `build_ready` is an optional write-control input (default: **omitted**). It governs whether a **leaf** work unit's `status:ready` label is applied on create. It never overrides `leaf-only-lifecycle` — a container is never stamped build-ready regardless of `build_ready`. "Not build-ready" is not a special state: the Issue is still created with Linear's default native `Todo` state; it just lacks the `status:ready` **label** the build lifecycle keys off, so a human can promote it later.
218
+
219
+ - **Omitted** → current behavior: a leaf work unit receives `status:ready`. Preserves what every existing caller (`lisa:plan`, the `*-to-tracker` skills) relies on.
220
+ - **`build_ready: false`** → create the leaf **without** the `status:ready` label, so it sits in the backlog for a human to review and promote into the queue.
221
+ - **`build_ready: true`** → ensure the leaf carries `status:ready` so `lisa:intake` / `lisa:linear-build-intake` auto-picks it up.
222
+
215
223
  ## Phase 5.5 — Validate (Pre-write Gate)
216
224
 
217
225
  Before any write, invoke `lisa:linear-validate-issue` with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the `lisa:linear-validate-issue` schema, including `runtime_behavior_change`, `authenticated_surface`, and `artifacts_attached` flags so the right gates run.
@@ -236,7 +244,7 @@ If the validator reports `PASS`, continue to Phase 6.
236
244
 
237
245
  ### CREATE — Story / Task / Bug / Spike / Improvement (Issue with projectId)
238
246
 
239
- 1. Resolve any required Issue labels (`status:ready`, `component:<name>`, `prd-intake-feedback` only if this is a sentinel issue, etc.) via `mcp__linear-server__list_issue_labels` (create via `create_issue_label` if missing).
247
+ 1. Resolve any required Issue labels (`component:<name>`, `prd-intake-feedback` only if this is a sentinel issue, etc.) via `mcp__linear-server__list_issue_labels` (create via `create_issue_label` if missing). Include `status:ready` in `labelIds` only for a **leaf** work unit and only when `build_ready` is not `false` (per the Build-ready control input) — omit it for a container, and for a `build_ready: false` leaf which then waits in the backlog for a human to promote it.
240
248
  2. Call `mcp__linear-server__save_issue` with: `team` (teamId), `title` (summary), `description` (markdown), `projectId` (the Epic Project), `priority` (0–4), `estimate`, `labelIds`, `assignee` if known.
241
249
  3. Capture the returned identifier (e.g. `ENG-123`) — Phase 4 sub-tasks need it as `parentId`.
242
250
  4. Add relationships from Phase 4b via `save_issue` (relations field) or paired relation calls.
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: linear-write-prd
3
+ description: "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."
4
+ allowed-tools: ["Skill", "Bash", "mcp__linear-server__list_teams", "mcp__linear-server__list_projects", "mcp__linear-server__get_project", "mcp__linear-server__save_project", "mcp__linear-server__list_project_labels", "mcp__linear-server__create_project_label"]
5
+ ---
6
+
7
+ # Write Linear PRD: $ARGUMENTS
8
+
9
+ Create (or update) a PRD as a Linear **Project** in the configured workspace/team. Invoked by
10
+ `lisa:prd-source-write` when `source = linear`; do not call directly from a vendor-neutral caller.
11
+
12
+ Linear's PRD lifecycle uses **project-level labels** (`prd-*`), per `config-resolution`. (The Linear
13
+ PRD source models a PRD as a Project — the same shape `lisa:linear-prd-intake` scans.)
14
+
15
+ `$ARGUMENTS` carries the `lisa:prd-source-write` spec: `title`, `body` (full PRD markdown),
16
+ `initial_role` (`draft` | `ready`, default `draft`), `dedupe_key`, `marker`, optional `source_ref`.
17
+
18
+ ## Phase 1 — Resolve workspace/team + PRD lifecycle labels
19
+
20
+ ```bash
21
+ 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}}"; }
22
+ WORKSPACE=$(read_g '.linear.workspace' '')
23
+ TEAM=$(read_g '.linear.teamKey' '')
24
+ # Resolve the FULL PRD lifecycle vocabulary from config (never hard-code names) so the one-of
25
+ # reconcile and the past-ready check are correct even when a project renamed any label.
26
+ PRD_DRAFT=$(read_g '.linear.labels.prd.draft' 'prd-draft')
27
+ PRD_READY=$(read_g '.linear.labels.prd.ready' 'prd-ready')
28
+ PRD_IN_REVIEW=$(read_g '.linear.labels.prd.in_review' 'prd-in-review')
29
+ PRD_BLOCKED=$(read_g '.linear.labels.prd.blocked' 'prd-blocked')
30
+ PRD_TICKETED=$(read_g '.linear.labels.prd.ticketed' 'prd-ticketed')
31
+ PRD_SHIPPED=$(read_g '.linear.labels.prd.shipped' 'prd-shipped')
32
+ PRD_VERIFIED=$(read_g '.linear.labels.prd.verified' 'prd-verified')
33
+ ALL_PRD_LABELS=("$PRD_DRAFT" "$PRD_READY" "$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
34
+ PROGRESSED=("$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
35
+ [ -z "$WORKSPACE" ] && { echo "Error: linear.workspace not set in .lisa.config.json."; exit 1; }
36
+ [ -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; }
37
+ ```
38
+
39
+ Resolve the target project-label from `initial_role`: `ready` → `$PRD_READY`, else `$PRD_DRAFT`.
40
+ Resolve its label id via `mcp__linear-server__list_project_labels` (create via
41
+ `mcp__linear-server__create_project_label` if missing).
42
+
43
+ ## Phase 2 — Dedupe by marker (search before create)
44
+
45
+ The `marker` is embedded in the Project description. Find an existing Project carrying it — match the
46
+ marker, **never** the project name:
47
+
48
+ 1. `mcp__linear-server__list_projects` scoped to the team/workspace (filtered by the `prd-*` label
49
+ set when supported), then inspect each candidate's description via
50
+ `mcp__linear-server__get_project` for the marker. If `source_ref` was passed, target it directly.
51
+ 2. If a Project with the marker exists → **update**; else → **create**.
52
+
53
+ ## Phase 3 — Create or update
54
+
55
+ **Marker normalization (both paths).** Before writing the `description`, ensure it contains
56
+ **exactly one** marker line — inject the marker if the synthesized description lacks it. **Never write
57
+ a markerless description** (including UPDATE / `source_ref`): that breaks future dedupe.
58
+
59
+ **CREATE:** `mcp__linear-server__save_project` with:
60
+ - `name`: `$TITLE`
61
+ - `description`: the marker-normalized full PRD markdown
62
+ - `teamIds`: `[<resolved team id>]`
63
+ - `labelIds`: `[<role label id>]` (exactly one PRD lifecycle label)
64
+ - `state`: Linear Project default (e.g. `backlog`)
65
+
66
+ **UPDATE** (existing project or `source_ref`): `save_project` with the project id and **only** the
67
+ changed fields — regenerate the marker-normalized `description`, and reconcile labels to **exactly
68
+ one** PRD lifecycle label: add the role label, remove every other label in the resolved
69
+ `${ALL_PRD_LABELS[@]}` set (config-resolved names, not a hard-coded list). Do **not** down-rank a
70
+ Project whose current label is in the resolved `${PROGRESSED[@]}` set (already past `ready`) — leave
71
+ it and report `reused (already past ready)`.
72
+
73
+ ## Phase 4 — Return
74
+
75
+ ```yaml
76
+ ref: "<linear-project-id-or-slug>"
77
+ url: "<project url>"
78
+ role: draft | ready # (or the Project's current role when reused past ready)
79
+ marker: "<MARKER>"
80
+ outcome: created | reused
81
+ ```
82
+
83
+ ## Rules
84
+
85
+ - Exactly one PRD lifecycle project-label at all times.
86
+ - Match dedupe by marker, never by project name.
87
+ - Never down-rank a Project already past `ready`.
88
+ - Source-side writer (`prd-*` project labels) — never touches issue-level build labels (`status:*`),
89
+ which are `lisa:linear-write-issue`'s lane (see `config-resolution` self-host separation).
90
+ - Resolve label names from config (`linear.labels.prd.*`) — never hardcode.
@@ -0,0 +1,4 @@
1
+ display_name: "Linear Write PRD"
2
+ short_description: "Creates or idempotently updates a PRD as a Linear Project carrying exactly one PRD lifecycle project-label (`prd-draft` by default, or…"
3
+ default_prompt:
4
+ - "Use $linear-write-prd: Creates or idempotently updates a PRD as a Linear Project carrying exactly one PRD lifecycle project-label (`prd-draft` by default, or…."
@@ -12,6 +12,7 @@ Single chokepoint for all Notion operations. Routes each op to a substrate, enfo
12
12
 
13
13
  ```text
14
14
  operation: read-page id: <uuid>
15
+ operation: create-page parent_database_id: <uuid> properties: {...} [children: [...]] # create a new page (e.g. a PRD row) in a database; children is optional — omit to create a page without initial block content
15
16
  operation: write-page payload: {...} # update page properties
16
17
  operation: archive-page id: <uuid>
17
18
  operation: query-database id: <uuid> filter: {...} sort: {...}
@@ -164,6 +165,7 @@ Substrate columns: try the column matching `$substrate` first. If that column is
164
165
  |---|---|---|
165
166
  | **Pages** | | |
166
167
  | `read-page id:<I>` | `mcp__claude_ai_Notion__notion-fetch` | `GET /v1/pages/<I>` |
168
+ | `create-page parent_database_id:<D> properties:<P> [children:<arr>]` | `mcp__claude_ai_Notion__notion-create-pages` | `POST /v1/pages` body `{ "parent": { "database_id": "<D>" }, "properties": <P>, "children": <arr?> }` (children optional per Notion API) |
167
169
  | `write-page payload:<P>` | `mcp__claude_ai_Notion__notion-update-page` | `PATCH /v1/pages/<I>` body `{ "properties": {...}, "archived": true/false }` |
168
170
  | `archive-page id:<I>` | `mcp__claude_ai_Notion__notion-update-page` (with `archived: true`) | `PATCH /v1/pages/<I>` body `{ "archived": true }` |
169
171
  | `append-blocks page_id:<P> children:<arr>` | (no direct equivalent) | `PATCH /v1/blocks/<P>/children` body `{ "children": <arr> }` |
@@ -82,7 +82,7 @@ draft → ready → in_review → blocked | ticketed → shipped → verified
82
82
 
83
83
  (Default status values: `Draft` / `Ready` / `In Review` / `Blocked` / `Ticketed` / `Shipped` / `Verified`.)
84
84
 
85
- `verified` is the terminal status after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification reuses `blocked` rather than introducing a separate `verifying` / `verification-failed` status. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never sets, clears, or otherwise touches it. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
85
+ `verified` is the terminal status after `shipped`: it means the shipped product has been empirically checked against the PRD (set by `/lisa:verify-prd`, not by this intake skill). A failed post-ship verification does **not** use `blocked`; `/lisa:verify-prd` re-opens the PRD `shipped → ticketed` and creates build-ready fix tickets that auto-build and trigger a re-verify (the self-healing loop), introducing no `verifying` / `verification-failed` status. Like `draft` and `shipped`, `verified` is **product-owned** — this intake skill never sets, clears, or otherwise touches it. See the "PRD-level verification vs ticket verification" section of the `prd-lifecycle-rollup` rule.
86
86
 
87
87
  This skill transitions `ready → in_review`, then `in_review → blocked` or `in_review → ticketed`, then (via the rollup phase 3f) `ticketed → shipped`. It never touches `draft` or `verified` — those statuses are owned by product (`verified` is set by `/lisa:verify-prd` after empirical PRD-level acceptance). The `shipped` status is set by this skill's **rollup phase (3f)** when, and only when, the PRD's generated top-level work is all terminal — per the `prd-lifecycle-rollup` rule; product may also set it by hand. Rollup never advances a PRD to `shipped` on partial completion, and never archives a PRD page unless `notion.rollup.closeOnShipped` is configured `true` (default `false` → set `$SHIPPED`, leave the page active).
88
88
 
@@ -298,6 +298,16 @@ The set of **required** children for the all-terminal check is the top-level chi
298
298
 
299
299
  This phase implements exactly one PRD-lifecycle hop — `$TICKETED → $SHIPPED` — and the optional config-gated archive that follows it. All terminal-state semantics, the generated-top-level-work boundary, and the dedupe-by-child-ref idempotency come from the `prd-lifecycle-rollup` rule; this skill is its Notion implementation, not a second source of truth.
300
300
 
301
+ #### 3g. PRD verification dispatch (close the loop on shipped PRDs)
302
+
303
+ `shipped` and `verified` are distinct facts about a PRD (see the `prd-lifecycle-rollup` rule's "PRD-level verification vs ticket verification" and "Closing the loop" sections). Rollup (3f) only reaches `$SHIPPED`; the `shipped → verified` (pass) / `shipped → ticketed` (fail) hops are owned by `/lisa:verify-prd`. This phase **closes that loop** by dispatching the initiative-level acceptance gate for shipped PRDs. It never performs the verification transition itself — the "never sets the verification outcome" invariant holds: `lisa:verify-prd`, not this skill, sets `verified` (or, on failure, re-opens the PRD to `ticketed`).
304
+
305
+ Re-query the PRDs currently in the `$SHIPPED` status via `lisa:notion-access` `operation: query-database` filtered on `$STATUS_PROP = $SHIPPED`. Pick the **first** one and invoke `lisa:verify-prd <PRD-page-url>`. Process **one shipped PRD per cycle** — `lisa:verify-prd` is a heavy full flow (spec-conformance + empirical verification + fix-issue creation), so it is bounded exactly like the single-ready-PRD claim in Phase 3; the scheduler drains the rest.
306
+
307
+ **Per-cycle combined bound:** each scheduler cycle dispatches at most one ready PRD (the Phase 3 single-ready-PRD claim) **and** at most one shipped PRD for verification (this Phase 3g dispatch), for a maximum of two PRD operations per cycle. Ready intake runs first (Phase 3), then shipped verify (Phase 3g).
308
+
309
+ `lisa:verify-prd` owns the outcome: on a CONFORMS verdict with all empirical checks passing it transitions `$SHIPPED → verified` and posts evidence; on a conformance miss or a failing/unavailable check it **re-opens the PRD `$SHIPPED → ticketed`** (never `blocked`) and creates **build-ready** fix tickets registered as the PRD's generated work, then posts a failure report — the fix tickets auto-build, rollup (3f) re-ships the PRD once they are terminal, and a later cycle re-verifies (the self-healing loop). Either branch moves the PRD out of `$SHIPPED`, so it is not re-picked this cycle; a PRD whose generated work is not actually terminal is guard-stopped by `lisa:verify-prd` (left `$SHIPPED`) — that is verify-prd's gate, not this skill's. A PRD archived on ship (`notion.rollup.closeOnShipped = true`) is out of the open verification queue. This phase, like 3f, is **behaviorally identical across all four intake skills** (`github-prd-intake`, `linear-prd-intake`, `notion-prd-intake`, `confluence-prd-intake`) — only the `$SHIPPED` query surface differs; keep them aligned. Record the dispatched PRD + verify-prd's verdict in the summary.
310
+
301
311
  ### Phase 4 — Summary report
302
312
 
303
313
  After processing the single selected PRD, emit a summary:
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: notion-write-prd
3
+ description: "Creates or idempotently updates a PRD as a page in the configured Notion PRD database, setting the lifecycle Status property to the draft value by default (or the ready value when initial_role is ready so lisa:notion-prd-intake auto-claims it). The Notion PRD-source writer behind lisa:prd-source-write. Dedupes by a stable marker embedded in the page (matched by marker, never by title). All Notion access goes through lisa:notion-access — never call the Notion API or MCP directly."
4
+ allowed-tools: ["Skill", "Bash"]
5
+ ---
6
+
7
+ # Write Notion PRD: $ARGUMENTS
8
+
9
+ Create (or update) a PRD page in the configured Notion PRD database. Invoked by
10
+ `lisa:prd-source-write` when `source = notion`; do not call directly from a vendor-neutral caller.
11
+ **All Notion operations go through `lisa:notion-access`** (the access chokepoint) — never curl the
12
+ Notion API or call a `mcp__*notion*` tool yourself.
13
+
14
+ `$ARGUMENTS` carries the `lisa:prd-source-write` spec: `title`, `body` (full PRD markdown),
15
+ `initial_role` (`draft` | `ready`, default `draft`), `dedupe_key`, `marker`, optional `source_ref`.
16
+
17
+ ## Phase 1 — Resolve database + Status vocabulary
18
+
19
+ ```bash
20
+ 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}}"; }
21
+ PRD_DB=$(read_g '.notion.prdDatabaseId' '')
22
+ [ -z "$PRD_DB" ] && { echo "Error: notion.prdDatabaseId not set in .lisa.config.json."; exit 1; }
23
+ STATUS_PROP=$(read_g '.notion.statusProperty' 'Status')
24
+ # Resolve the FULL PRD Status vocabulary from config (never hard-code) so the past-ready check is
25
+ # correct even when a project renamed any Status value.
26
+ DRAFT=$(read_g '.notion.values.draft' 'Draft')
27
+ READY=$(read_g '.notion.values.ready' 'Ready')
28
+ IN_REVIEW=$(read_g '.notion.values.in_review' 'In Review')
29
+ BLOCKED=$(read_g '.notion.values.blocked' 'Blocked')
30
+ TICKETED=$(read_g '.notion.values.ticketed' 'Ticketed')
31
+ SHIPPED=$(read_g '.notion.values.shipped' 'Shipped')
32
+ VERIFIED=$(read_g '.notion.values.verified' 'Verified')
33
+ # "Progressed past ready" set (never down-rank): the resolved in_review/blocked/ticketed/shipped/verified.
34
+ PROGRESSED=("$IN_REVIEW" "$BLOCKED" "$TICKETED" "$SHIPPED" "$VERIFIED")
35
+ ```
36
+
37
+ Resolve the target Status value from `initial_role`: `ready` → `$READY`, otherwise `$DRAFT`.
38
+
39
+ ## Phase 2 — Dedupe by marker (search before create)
40
+
41
+ The `marker` is embedded in the page (as the first body block). Find an existing PRD page in the DB
42
+ carrying it — match the marker, **never** the title:
43
+
44
+ 1. `lisa:notion-access` `operation: search query: "<marker>"` (Notion indexes page content).
45
+ 2. Filter results to pages whose parent is `$PRD_DB`. If `source_ref` was passed, target that page
46
+ directly and skip the search.
47
+ 3. If a matching page is found, this is an **update** — reuse it. If none is found, **create**.
48
+ Note: Notion search is eventually consistent; if a just-created page isn't found yet, the marker
49
+ still lives in the page so a later run will dedupe — surface "dedupe degraded (search lag)" rather
50
+ than silently creating a duplicate when uncertain.
51
+
52
+ ## Phase 3 — Create or update
53
+
54
+ **Markdown → Notion blocks (conversion boundary).** Convert the PRD markdown to Notion block objects:
55
+ `#`/`##`/`###` → `heading_1/2/3`, paragraphs → `paragraph`, `-`/`*` → `bulleted_list_item`, `1.` →
56
+ `numbered_list_item`, fenced code → `code`. The Notion API caps a single request at **100 blocks**
57
+ and ~2000 characters of rich text per block: split long paragraphs across blocks, and if the PRD
58
+ exceeds 100 blocks, create the page with the first ≤100 blocks then add the remainder with batched
59
+ `operation: append-blocks` calls (≤100 each). When the MCP substrate is active, `create-page` may
60
+ accept the markdown content directly (it performs this conversion) — prefer that; the explicit block
61
+ conversion is the curl-substrate path.
62
+
63
+ **Marker normalization (both paths).** The page must always carry **exactly one** marker. On CREATE
64
+ the marker is the first body block; on UPDATE never remove it. Never write a markerless page.
65
+
66
+ **CREATE:**
67
+
68
+ 1. Build the page body as Notion blocks per the conversion above: the **first block is the marker**
69
+ (a paragraph/callout containing `<!-- $MARKER -->`), then the converted PRD blocks.
70
+ 2. Invoke `lisa:notion-access` `operation: create-page` with:
71
+ ```json
72
+ { "parent_database_id": "<PRD_DB>",
73
+ "properties": { "<title-prop>": { "title": [{ "text": { "content": "<TITLE>" } }] },
74
+ "<STATUS_PROP>": { "status": { "name": "<ROLE_VALUE>" } } },
75
+ "children": [ <marker block>, <PRD body blocks> ] }
76
+ ```
77
+ Use the DB's actual title property name (read it via `operation: read-database id: <PRD_DB>` if
78
+ unknown) and the correct property type for `$STATUS_PROP` (`status` vs `select`).
79
+ 3. Capture the returned page id + URL.
80
+
81
+ **UPDATE** (existing page or `source_ref`):
82
+
83
+ 1. Set the Status to the resolved role via `lisa:notion-access` `operation: write-page payload: { "id": "<page-id>", "properties": { "<STATUS_PROP>": { "status": { "name": "<ROLE_VALUE>" } } } }` — **unless** the page's current Status is in the resolved `${PROGRESSED[@]}` set (already past `ready`), in which case leave the Status and report `reused (already past ready)`.
84
+ 2. Refresh the canonical spec, not append-only notes: keep the existing marker block, then make the
85
+ managed PRD body current — archive the previously generated body blocks below the marker
86
+ (`operation: archive-page` is page-level, so for blocks delete via the blocks API through
87
+ `notion-access` or, where block deletion isn't available, replace their text in place) and
88
+ `operation: append-blocks` the regenerated blocks. Do not duplicate the whole spec as a dated
89
+ note, and never drop the marker.
90
+
91
+ ## Phase 4 — Return
92
+
93
+ ```yaml
94
+ ref: "<notion-page-id>"
95
+ url: "<page url>"
96
+ role: draft | ready # (or the page's current Status role when reused past ready)
97
+ marker: "<MARKER>"
98
+ outcome: created | reused
99
+ ```
100
+
101
+ ## Rules
102
+
103
+ - All access via `lisa:notion-access`; never touch the Notion API/MCP directly.
104
+ - Match dedupe by marker, never by title.
105
+ - Never down-rank a PRD whose Status is already past `ready`.
106
+ - Resolve the Status vocabulary from config (`notion.statusProperty`, `notion.values.*`) — never
107
+ hardcode value names.
@@ -0,0 +1,4 @@
1
+ display_name: "Notion Write PRD"
2
+ short_description: "Creates or idempotently updates a PRD as a page in the configured Notion PRD database, setting the lifecycle Status property to the draft…"
3
+ default_prompt:
4
+ - "Use $notion-write-prd: Creates or idempotently updates a PRD as a page in the configured Notion PRD database, setting the lifecycle Status property to the draft…."