@codyswann/lisa 2.21.1 → 2.23.0

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 (122) hide show
  1. package/package.json +3 -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 +11 -9
  5. package/plugins/lisa/agents/github-agent.md +18 -10
  6. package/plugins/lisa/agents/github-build-intake.md +10 -8
  7. package/plugins/lisa/agents/github-prd-intake.md +11 -9
  8. package/plugins/lisa/agents/jira-agent.md +12 -8
  9. package/plugins/lisa/agents/jira-build-intake.md +9 -7
  10. package/plugins/lisa/agents/linear-agent.md +15 -9
  11. package/plugins/lisa/agents/linear-build-intake.md +13 -11
  12. package/plugins/lisa/agents/linear-prd-intake.md +11 -9
  13. package/plugins/lisa/agents/notion-prd-intake.md +11 -9
  14. package/plugins/lisa/commands/setup/atlassian.md +7 -0
  15. package/plugins/lisa/commands/setup/confluence.md +7 -0
  16. package/plugins/lisa/commands/setup/jira.md +7 -0
  17. package/plugins/lisa/commands/setup/notion.md +7 -0
  18. package/plugins/lisa/rules/base-rules.md +1 -1
  19. package/plugins/lisa/rules/config-resolution.md +242 -24
  20. package/plugins/lisa/rules/repo-scope-split.md +41 -0
  21. package/plugins/lisa/rules/verification.md +13 -0
  22. package/plugins/lisa/skills/atlassian-access/SKILL.md +260 -0
  23. package/plugins/lisa/skills/confluence-prd-intake/SKILL.md +167 -82
  24. package/plugins/lisa/skills/confluence-to-tracker/SKILL.md +39 -26
  25. package/plugins/lisa/skills/github-add-journey/SKILL.md +1 -0
  26. package/plugins/lisa/skills/github-build-intake/SKILL.md +104 -40
  27. package/plugins/lisa/skills/github-evidence/SKILL.md +22 -5
  28. package/plugins/lisa/skills/github-prd-intake/SKILL.md +87 -51
  29. package/plugins/lisa/skills/github-to-tracker/SKILL.md +2 -2
  30. package/plugins/lisa/skills/github-validate-issue/SKILL.md +11 -1
  31. package/plugins/lisa/skills/jira-add-journey/SKILL.md +1 -0
  32. package/plugins/lisa/skills/jira-build-intake/SKILL.md +110 -45
  33. package/plugins/lisa/skills/jira-create/SKILL.md +5 -3
  34. package/plugins/lisa/skills/jira-evidence/SKILL.md +19 -2
  35. package/plugins/lisa/skills/jira-journey/SKILL.md +3 -1
  36. package/plugins/lisa/skills/jira-read-ticket/SKILL.md +10 -8
  37. package/plugins/lisa/skills/jira-sync/SKILL.md +11 -5
  38. package/plugins/lisa/skills/jira-validate-ticket/SKILL.md +22 -10
  39. package/plugins/lisa/skills/jira-verify/SKILL.md +5 -3
  40. package/plugins/lisa/skills/jira-write-ticket/SKILL.md +16 -14
  41. package/plugins/lisa/skills/linear-add-journey/SKILL.md +1 -0
  42. package/plugins/lisa/skills/linear-build-intake/SKILL.md +90 -32
  43. package/plugins/lisa/skills/linear-evidence/SKILL.md +22 -5
  44. package/plugins/lisa/skills/linear-prd-intake/SKILL.md +92 -57
  45. package/plugins/lisa/skills/linear-validate-issue/SKILL.md +10 -0
  46. package/plugins/lisa/skills/notion-access/SKILL.md +193 -0
  47. package/plugins/lisa/skills/notion-prd-intake/SKILL.md +105 -46
  48. package/plugins/lisa/skills/notion-to-tracker/SKILL.md +7 -5
  49. package/plugins/lisa/skills/setup-atlassian/SKILL.md +316 -0
  50. package/plugins/lisa/skills/setup-confluence/SKILL.md +245 -0
  51. package/plugins/lisa/skills/setup-jira/SKILL.md +198 -0
  52. package/plugins/lisa/skills/setup-notion/SKILL.md +283 -0
  53. package/plugins/lisa/skills/task-decomposition/SKILL.md +2 -0
  54. package/plugins/lisa/skills/ticket-triage/SKILL.md +4 -1
  55. package/plugins/lisa/skills/tracker-evidence/SKILL.md +1 -0
  56. package/plugins/lisa/skills/verification-lifecycle/SKILL.md +2 -0
  57. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/src/base/agents/confluence-prd-intake.md +11 -9
  70. package/plugins/src/base/agents/github-agent.md +18 -10
  71. package/plugins/src/base/agents/github-build-intake.md +10 -8
  72. package/plugins/src/base/agents/github-prd-intake.md +11 -9
  73. package/plugins/src/base/agents/jira-agent.md +12 -8
  74. package/plugins/src/base/agents/jira-build-intake.md +9 -7
  75. package/plugins/src/base/agents/linear-agent.md +15 -9
  76. package/plugins/src/base/agents/linear-build-intake.md +13 -11
  77. package/plugins/src/base/agents/linear-prd-intake.md +11 -9
  78. package/plugins/src/base/agents/notion-prd-intake.md +11 -9
  79. package/plugins/src/base/commands/setup/atlassian.md +7 -0
  80. package/plugins/src/base/commands/setup/confluence.md +7 -0
  81. package/plugins/src/base/commands/setup/jira.md +7 -0
  82. package/plugins/src/base/commands/setup/notion.md +7 -0
  83. package/plugins/src/base/rules/base-rules.md +1 -1
  84. package/plugins/src/base/rules/config-resolution.md +242 -24
  85. package/plugins/src/base/rules/repo-scope-split.md +41 -0
  86. package/plugins/src/base/rules/verification.md +13 -0
  87. package/plugins/src/base/skills/atlassian-access/SKILL.md +260 -0
  88. package/plugins/src/base/skills/confluence-prd-intake/SKILL.md +167 -82
  89. package/plugins/src/base/skills/confluence-to-tracker/SKILL.md +39 -26
  90. package/plugins/src/base/skills/github-add-journey/SKILL.md +1 -0
  91. package/plugins/src/base/skills/github-build-intake/SKILL.md +104 -40
  92. package/plugins/src/base/skills/github-evidence/SKILL.md +22 -5
  93. package/plugins/src/base/skills/github-prd-intake/SKILL.md +87 -51
  94. package/plugins/src/base/skills/github-to-tracker/SKILL.md +2 -2
  95. package/plugins/src/base/skills/github-validate-issue/SKILL.md +11 -1
  96. package/plugins/src/base/skills/jira-add-journey/SKILL.md +1 -0
  97. package/plugins/src/base/skills/jira-build-intake/SKILL.md +110 -45
  98. package/plugins/src/base/skills/jira-create/SKILL.md +5 -3
  99. package/plugins/src/base/skills/jira-evidence/SKILL.md +19 -2
  100. package/plugins/src/base/skills/jira-journey/SKILL.md +3 -1
  101. package/plugins/src/base/skills/jira-read-ticket/SKILL.md +10 -8
  102. package/plugins/src/base/skills/jira-sync/SKILL.md +11 -5
  103. package/plugins/src/base/skills/jira-validate-ticket/SKILL.md +22 -10
  104. package/plugins/src/base/skills/jira-verify/SKILL.md +5 -3
  105. package/plugins/src/base/skills/jira-write-ticket/SKILL.md +16 -14
  106. package/plugins/src/base/skills/linear-add-journey/SKILL.md +1 -0
  107. package/plugins/src/base/skills/linear-build-intake/SKILL.md +90 -32
  108. package/plugins/src/base/skills/linear-evidence/SKILL.md +22 -5
  109. package/plugins/src/base/skills/linear-prd-intake/SKILL.md +92 -57
  110. package/plugins/src/base/skills/linear-validate-issue/SKILL.md +10 -0
  111. package/plugins/src/base/skills/notion-access/SKILL.md +193 -0
  112. package/plugins/src/base/skills/notion-prd-intake/SKILL.md +105 -46
  113. package/plugins/src/base/skills/notion-to-tracker/SKILL.md +7 -5
  114. package/plugins/src/base/skills/setup-atlassian/SKILL.md +316 -0
  115. package/plugins/src/base/skills/setup-confluence/SKILL.md +245 -0
  116. package/plugins/src/base/skills/setup-jira/SKILL.md +198 -0
  117. package/plugins/src/base/skills/setup-notion/SKILL.md +283 -0
  118. package/plugins/src/base/skills/task-decomposition/SKILL.md +2 -0
  119. package/plugins/src/base/skills/ticket-triage/SKILL.md +4 -1
  120. package/plugins/src/base/skills/tracker-evidence/SKILL.md +1 -0
  121. package/plugins/src/base/skills/verification-lifecycle/SKILL.md +2 -0
  122. package/scripts/check-plugins-sync.sh +45 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: notion-prd-intake
3
- description: PRD intake agent. Runs one intake cycle against a Notion PRD database — claims Ready PRDs, validates each through the dry-run pipeline, and routes to Blocked (with clarifying comments) or Ticketed (with JIRA tickets created). Designed to be invoked manually via /notion-prd-intake or autonomously via a scheduled cron.
3
+ description: PRD intake agent. Runs one intake cycle against a Notion PRD database — claims PRDs in the configured `ready` status, validates each through the dry-run pipeline, and routes to the `blocked` status (with clarifying comments) or the `ticketed` status (with destination tickets created). Designed to be invoked manually via /notion-prd-intake or autonomously via a scheduled cron.
4
4
  skills:
5
5
  - notion-prd-intake
6
6
  - notion-to-tracker
@@ -15,9 +15,11 @@ skills:
15
15
 
16
16
  You are a PRD intake agent. Your single job is to run one intake cycle against the Notion PRD database whose URL is given to you, then report what happened.
17
17
 
18
+ Status role names (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`) are resolved from `.lisa.config.json` `notion.values.*` by the `notion-prd-intake` skill. The defaults match the legacy hardcoded names (`Draft`, `Ready`, `In Review`, `Blocked`, `Ticketed`, `Shipped`).
19
+
18
20
  ## Confirmation policy
19
21
 
20
- Once you have a database URL, RUN. Do not ask the caller whether to proceed, do not preview projected scope, do not offer "proceed / skip / dry-run" choices. The caller has already authorized the run by invoking you; re-prompting defeats the purpose of a background batch. `Blocked` is a valid terminal state of the lifecycle, not a failure mode — large PRDs and PRDs full of open questions are exactly what this skill is for. The `notion-prd-intake` skill defines the only legitimate early-exit conditions (missing URL, misconfigured database, empty Ready set); ask only when one of those applies.
22
+ Once you have a database URL, RUN. Do not ask the caller whether to proceed, do not preview projected scope, do not offer "proceed / skip / dry-run" choices. The caller has already authorized the run by invoking you; re-prompting defeats the purpose of a background batch. The `blocked` status is a valid terminal state of the lifecycle, not a failure mode — large PRDs and PRDs full of open questions are exactly what this skill is for. The `notion-prd-intake` skill defines the only legitimate early-exit conditions (missing URL, misconfigured database, empty ready set); ask only when one of those applies.
21
23
 
22
24
  ## Workflow
23
25
 
@@ -31,30 +33,30 @@ If no URL is provided, stop and ask. Never run intake against a default or guess
31
33
 
32
34
  Invoke the `notion-prd-intake` skill with the database URL as `$ARGUMENTS`. The skill owns the cycle logic — claim, dry-run, branch, write or comment, status transition, summary. Do not duplicate that logic here.
33
35
 
34
- Treat the skill's output as the source of truth. If it reports `Ticketed: 3 / Blocked: 1 / Errors: 0`, that's what you report.
36
+ Treat the skill's output as the source of truth (e.g. `ticketed: 3 / blocked: 1 / errors: 0`).
35
37
 
36
38
  ### 3. Surface the summary
37
39
 
38
40
  Pass the skill's summary block through to the caller verbatim — do not paraphrase or condense. The caller (often a human running `/notion-prd-intake` ad-hoc, or a future schedule wrapper) needs the structured record:
39
41
 
40
42
  - Total processed
41
- - Per-PRD outcomes (Ticketed → which tickets created; Blocked → how many gate failures; Errors → reason)
42
- - JIRA ticket count
43
+ - Per-PRD outcomes (ticketed → which tickets created; blocked → how many gate failures; errors → reason)
44
+ - Destination ticket count
43
45
 
44
46
  If the cycle errored before processing any PRDs (e.g. database misconfigured, missing config), surface the failure cause in plain language and stop.
45
47
 
46
48
  ### 4. Suggest next actions when warranted
47
49
 
48
- After a successful cycle, if any PRDs ended in `Blocked`, mention to the caller that those PRDs need product attention before they can be re-ticketed. Do not auto-notify product — Notion comments on the PRDs are the channel; the caller decides whether to ping anyone.
50
+ After a successful cycle, if any PRDs ended in the `blocked` status, mention to the caller that those PRDs need product attention before they can be re-ticketed. Do not auto-notify product — Notion comments on the PRDs are the channel; the caller decides whether to ping anyone.
49
51
 
50
- When reporting Blocked outcomes, distinguish the cause: **pre-write gate failure** (per-ticket validator caught a problem before any tickets were created) vs **post-write coverage gap** (tickets were created and remain in JIRA, but the PRD has uncovered requirements that the next intake cycle will address). Both result in `Status = Blocked`, but the implication for product is different — coverage gaps mean some tickets are already real and product should not re-author the PRD from scratch.
52
+ When reporting `blocked` outcomes, distinguish the cause: **pre-write gate failure** (per-ticket validator caught a problem before any tickets were created) vs **post-write coverage gap** (tickets were created and remain in the destination tracker, but the PRD has uncovered requirements that the next intake cycle will address). Both result in the `blocked` status, but the implication for product is different — coverage gaps mean some tickets are already real and product should not re-author the PRD from scratch.
51
53
 
52
- If all PRDs ended in `Ticketed` with coverage `COMPLETE`, mention that the next step is for product to monitor the created tickets and flip the PRDs to `Shipped` after delivery. If any are `COMPLETE_WITH_SCOPE_CREEP`, point that out so product can review the flagged tickets.
54
+ If all PRDs ended in the `ticketed` status with coverage `COMPLETE`, mention that the next step is for product to monitor the created tickets and flip the PRDs to the configured `shipped` status after delivery. If any are `COMPLETE_WITH_SCOPE_CREEP`, point that out so product can review the flagged tickets.
53
55
 
54
56
  ## Rules
55
57
 
56
58
  - **Never run a cycle without an explicit database URL.** Side effects are too high to default.
57
- - **Never modify the lifecycle**: only `ReadyIn Review Blocked|Ticketed`. Never touch `Draft` or `Shipped`. Never invent new status values.
59
+ - **Never modify the lifecycle**: only `readyin_reviewblocked|ticketed`. Never touch `draft` or `shipped`. Never invent new status values.
58
60
  - **Never write destination tickets directly.** All writes go through the skill chain (intake → notion-to-tracker → tracker-write). Bypassing this skips quality gates.
59
61
  - **Never edit a PRD's body.** Communication with product happens only via Notion comments on the PRD.
60
62
  - **Never start a second cycle while one is in flight against the same database.** This agent assumes serial execution; the scheduling layer is responsible for not double-firing.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Set up Atlassian (cloudId + acli profile) for this project. Writes the `atlassian` section of `.lisa.config.json` and enables the Atlassian MCP and/or installs acli as needed. Prerequisite for /lisa:setup:jira and /lisa:setup:confluence."
3
+ allowed-tools: ["Skill"]
4
+ argument-hint: "[--site=<site>] [--email=<email>]"
5
+ ---
6
+
7
+ Use the /lisa:setup-atlassian skill to configure Atlassian access for this project. $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Set up Confluence as the PRD source for this project. Writes the `confluence` section of `.lisa.config.json` (spaceKey and/or parentPageId) and offers to set top-level `source: \"confluence\"`. Depends on /lisa:setup:atlassian."
3
+ allowed-tools: ["Skill"]
4
+ argument-hint: "[--space=<KEY>] [--parent=<pageId>]"
5
+ ---
6
+
7
+ Use the /lisa:setup-confluence skill to configure Confluence as the PRD source. $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Set up JIRA as the tracker for this project. Writes `jira.project` and offers to set top-level `tracker: \"jira\"` in `.lisa.config.json`. Depends on /lisa:setup:atlassian (needs cloudId)."
3
+ allowed-tools: ["Skill"]
4
+ argument-hint: "[--project=<KEY>]"
5
+ ---
6
+
7
+ Use the /lisa:setup-jira skill to configure JIRA as the project tracker. $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: "Set up Notion as the PRD source for this project. Walks the user through creating a workspace-scoped internal-integration token, sharing the PRD database with it, and stores the token in OS keychain. Writes `notion.workspaceId`, `notion.prdDatabaseId`, and `notion.values` into `.lisa.config.json`. Offers to set top-level `source: \"notion\"`."
3
+ allowed-tools: ["Skill"]
4
+ argument-hint: "[--database=<uuid>] [--workspace=<slug>]"
5
+ ---
6
+
7
+ Use the /lisa:setup-notion skill to configure Notion as the PRD source. $ARGUMENTS
@@ -70,7 +70,7 @@ JIRA Discipline:
70
70
  - When reading a JIRA issue, always read ALL comments on the ticket — not just the description. Comments contain critical context: stakeholder decisions, scope changes, blockers, triage findings from other repos, and implementation notes. Use the Atlassian MCP or `jira issue view <TICKET_ID> --comments 100` to fetch them.
71
71
  - When requesting clarification on a JIRA issue, post the question as a comment using ADF (Atlassian Document Format) and @mention the Reporter so they receive a notification.
72
72
  - When creating JIRA tickets, establish issue link relationships (e.g. "is blocked by", "blocks", "relates to", "is duplicated by") between tickets that have dependencies or logical connections. Do not leave related tickets unlinked. Relationship discovery is mandatory on every create and update — search BOTH the local git history (`git log --all --grep=<keyword>`, `git log -- <path>`) AND Jira (JQL by component, keyword, label, epic siblings) before declaring "no related work." Record the searches you ran and their outcomes in the description (or a comment) so a reviewer can see the search was real.
73
- - Ticket scope by type: Bug, Task, and Sub-task tickets MUST be single-repo (one work unit, one repo). Epic, Spike, and Story tickets MAY span multiple repos. If a Bug/Task/Sub-task crosses repos, split it.
73
+ - Ticket scope by type: Bug, Task, and Sub-task tickets MUST be single-repo (one work unit, one repo). Epic, Spike, and Story tickets MAY span multiple repos. If a Bug/Task/Sub-task crosses repos, split it per the `repo-scope-split` rule — at PRD-decomposition time via `task-decomposition` step 1.5 (parent Story + per-repo children); at work-time (an existing ticket an agent is about to implement) via the work-time split procedure (narrow the original to one repo, spin off a sibling per additional repo cloning its metadata, link the producer→consumer dependency, then proceed). A cross-repo work unit is a decomposition error the agent fixes by splitting, not a reason to bounce the ticket to the reporter.
74
74
  - Ticket descriptions MUST contain everything an implementer needs to start work without asking the reporter. In particular:
75
75
  - **Target backend environment** (`dev` / `staging` / `prod`) when the ticket has runtime behavior. QA/product report against a deployed env; the implementer verifies locally first against that backend before CI/CD. Skip only for doc-only, config-only, type-only, or Epic tickets.
76
76
  - **Sign-in account / credentials** when the work requires being signed in. Name the account (or the source — 1Password item, env var, seeded fixture) and the role. Omit entirely when sign-in is not required.
@@ -18,9 +18,15 @@ A typical Bash read:
18
18
  ```bash
19
19
  local_value=$(jq -r '.tracker // empty' .lisa.config.local.json 2>/dev/null)
20
20
  global_value=$(jq -r '.tracker // empty' .lisa.config.json 2>/dev/null)
21
- tracker="${local_value:-${global_value:-jira}}"
21
+ tracker="${local_value:-${global_value}}"
22
+ if [ -z "$tracker" ]; then
23
+ echo "Error: 'tracker' not set in .lisa.config.json. Run /lisa:setup:jira (or :github, :linear) to configure." >&2
24
+ exit 1
25
+ fi
22
26
  ```
23
27
 
28
+ `tracker` is **required** — there is no implicit default. Projects must declare their destination explicitly via one of the `/lisa:setup:*` skills.
29
+
24
30
  ## Schema
25
31
 
26
32
  ```json
@@ -28,12 +34,88 @@ tracker="${local_value:-${global_value:-jira}}"
28
34
  "tracker": "jira",
29
35
  "source": "notion",
30
36
 
31
- "atlassian": { "cloudId": "<uuid>" },
32
- "jira": { "project": "<KEY>" },
33
- "confluence": { "spaceKey": "<KEY>", "parentPageId": "<id>" },
34
- "github": { "org": "<org-or-user>", "repo": "<repo>" },
35
- "notion": { "prdDatabaseId": "<uuid>", "statusProperty": "Status", "readyValue": "Ready" },
36
- "linear": { "workspace": "<workspace-slug>", "teamKey": "<TEAM>" }
37
+ "atlassian": { "cloudId": "<uuid>", "site": "<host>" },
38
+ "jira": {
39
+ "project": "<KEY>",
40
+ "workflow": {
41
+ "ready": "Ready",
42
+ "claimed": "In Progress",
43
+ "review": "Code Review",
44
+ "blocked": "Blocked",
45
+ "done": { "dev": "On Dev", "staging": "On Stg", "production": "Done" }
46
+ }
47
+ },
48
+ "confluence": {
49
+ "spaceKey": "<KEY>",
50
+ "parentPageId": "<id>",
51
+ "parents": {
52
+ "draft": "<page-id>",
53
+ "ready": "<page-id>",
54
+ "in_review": "<page-id>",
55
+ "blocked": "<page-id>",
56
+ "ticketed": "<page-id>",
57
+ "shipped": "<page-id>"
58
+ },
59
+ "dashboardPageId": "<page-id>",
60
+ "feedbackPageId": "<page-id>"
61
+ },
62
+ "github": {
63
+ "org": "<org-or-user>",
64
+ "repo": "<repo>",
65
+ "labels": {
66
+ "build": {
67
+ "ready": "status:ready",
68
+ "claimed": "status:in-progress",
69
+ "review": "status:code-review",
70
+ "blocked": "status:blocked",
71
+ "done": { "dev": "status:on-dev", "staging": "status:on-stg", "production": "status:done" }
72
+ },
73
+ "prd": {
74
+ "draft": "prd-draft",
75
+ "ready": "prd-ready", "in_review": "prd-in-review",
76
+ "blocked": "prd-blocked", "ticketed": "prd-ticketed",
77
+ "shipped": "prd-shipped",
78
+ "sentinel": "prd-intake-feedback"
79
+ }
80
+ }
81
+ },
82
+ "notion": {
83
+ "workspaceId": "<workspace-uuid-or-human-slug>",
84
+ "prdDatabaseId": "<uuid>",
85
+ "statusProperty": "Status",
86
+ "values": {
87
+ "draft": "Draft", "ready": "Ready", "in_review": "In Review",
88
+ "blocked": "Blocked", "ticketed": "Ticketed", "shipped": "Shipped"
89
+ }
90
+ },
91
+ "linear": {
92
+ "workspace": "<workspace-slug>",
93
+ "teamKey": "<TEAM>",
94
+ "labels": {
95
+ "build": {
96
+ "ready": "status:ready",
97
+ "claimed": "status:in-progress",
98
+ "review": "status:code-review",
99
+ "blocked": "status:blocked",
100
+ "done": { "dev": "status:on-dev", "staging": "status:on-stg", "production": "status:done" }
101
+ },
102
+ "prd": {
103
+ "draft": "prd-draft",
104
+ "ready": "prd-ready", "in_review": "prd-in-review",
105
+ "blocked": "prd-blocked", "ticketed": "prd-ticketed",
106
+ "shipped": "prd-shipped",
107
+ "sentinel": "prd-intake-feedback"
108
+ }
109
+ }
110
+ },
111
+
112
+ "deploy": {
113
+ "branches": {
114
+ "dev": "dev",
115
+ "staging": "staging",
116
+ "production": "main"
117
+ }
118
+ }
37
119
  }
38
120
  ```
39
121
 
@@ -41,7 +123,7 @@ tracker="${local_value:-${global_value:-jira}}"
41
123
 
42
124
  | Field | Required | Default | Notes |
43
125
  |-------|----------|---------|-------|
44
- | `tracker` | no | `"jira"` | Destination for ticket writes. One of `"jira"`, `"github"`, `"linear"`. Missing key resolves to `"jira"` for back-compat. |
126
+ | `tracker` | **yes** | | Destination for ticket writes. One of `"jira"`, `"github"`, `"linear"`. Missing fail with instruction to run the matching `/lisa:setup:*` skill. |
45
127
  | `source` | no | — | Default PRD source for batch skills (`/lisa:intake`) and arg-less single-PRD skills. One of `"notion"`, `"confluence"`, `"linear"`, `"github"`, `"jira"`. Explicit URLs/keys passed to a skill always win over `source`; this is a default, not a lock. |
46
128
 
47
129
  ### Vendor sections
@@ -50,9 +132,11 @@ Each vendor section is **conditionally required**: required only when that vendo
50
132
 
51
133
  #### `atlassian`
52
134
 
53
- | Field | Required when | Notes |
54
- |-------|---------------|-------|
55
- | `atlassian.cloudId` | `tracker = "jira"`, `source = "jira"`, `source = "confluence"`, or any `confluence-*` / `jira-*` skill is invoked | Atlassian Cloud site UUID. Resolve once via the Atlassian MCP `getAccessibleAtlassianResources` and paste in. Shared between JIRA and Confluence (same Atlassian site). If a project ever needs separate sites for JIRA vs Confluence, override via `jira.cloudId` / `confluence.cloudId` (not currently implemented — file an issue). |
135
+ | Field | Required when | Where it lives | Notes |
136
+ |-------|---------------|----------------|-------|
137
+ | `atlassian.cloudId` | `tracker = "jira"`, `source = "jira"`, `source = "confluence"`, or any `confluence-*` / `jira-*` skill is invoked | **committed** (`.lisa.config.json`) | Atlassian Cloud site UUID. Same for every developer on the project. Resolve once via `curl https://<site>/_edge/tenant_info` or `getAccessibleAtlassianResources`. Shared between JIRA and Confluence (same Atlassian site). |
138
+ | `atlassian.site` | same as above | **committed** | Human-readable site URL (e.g. `propswap.atlassian.net`). Same for every developer. |
139
+ | `atlassian.email` | when the developer's machine has multiple Atlassian accounts that can access the configured site | **local** (`.lisa.config.local.json`) | Per-developer. `--site` alone cannot disambiguate which acli profile to switch to when two accounts both have access to the same site (e.g., a personal account and a work account both invited to a customer's site). The setup skill writes this to the local override file, NEVER the committed file. |
56
140
 
57
141
  #### `jira`
58
142
 
@@ -78,11 +162,12 @@ When `tracker = "github"` AND `source = "github"` (self-host), both reads and wr
78
162
 
79
163
  #### `notion`
80
164
 
81
- | Field | Required when | Notes |
82
- |-------|---------------|-------|
83
- | `notion.prdDatabaseId` | `source = "notion"` | Notion database ID (UUID, dashes optional). The database is the PRD queue. |
84
- | `notion.statusProperty` | `source = "notion"` | Name of the database property that drives the lifecycle. Defaults to `"Status"` if absent. |
85
- | `notion.readyValue` | `source = "notion"` | Status value that means "ready for ticketing". Defaults to `"Ready"` if absent. The full lifecycle (Ready → In Review → Blocked / Ticketed → Shipped) is hardcoded; only the entry-point value name is configurable for now. |
165
+ | Field | Required when | Where it lives | Notes |
166
+ |-------|---------------|----------------|-------|
167
+ | `notion.workspaceId` | `source = "notion"` | **committed** | Workspace identifier (Notion workspace UUID, or a stable human slug the user picks at setup). Same for every developer on the project. Used as the keychain `account` value when looking up the Notion API token, so each project's `notion-access` finds the right per-workspace token. |
168
+ | `notion.prdDatabaseId` | `source = "notion"` | **committed** | Notion database ID (UUID, dashes optional). The database is the PRD queue. Same for every developer on the project. |
169
+ | `notion.statusProperty` | `source = "notion"` | **committed** | Name of the database property that drives the lifecycle. Defaults to `"Status"` if absent. |
170
+ | `notion.values` | optional | **committed** | Map of role → Notion status-value name (`draft`, `ready`, `in_review`, `blocked`, `ticketed`, `shipped`). Defaults match the role names in title case. Override here if your Notion DB uses different value names. |
86
171
 
87
172
  #### `linear`
88
173
 
@@ -91,12 +176,70 @@ When `tracker = "github"` AND `source = "github"` (self-host), both reads and wr
91
176
  | `linear.workspace` | `tracker = "linear"`, `source = "linear"`, or any `linear-*` skill is invoked | Linear workspace slug (e.g. `acme`). |
92
177
  | `linear.teamKey` | `tracker = "linear"` | Linear team key (e.g. `ENG`). The team owns the destination Issues. For source mode, projects are workspace-scoped or team-scoped per the URL passed. |
93
178
 
179
+ ## Workflow & vocabulary roles
180
+
181
+ Every lifecycle skill operates on a fixed set of **roles** (`ready`, `claimed`, `done`, etc.), not concrete status/label strings. The role → string mapping lives in the per-vendor section above, with defaults that match the legacy hardcoded names. A project that uses different names overrides the relevant key; everything else inherits.
182
+
183
+ ### Roles
184
+
185
+ **Build lifecycle** (work items):
186
+
187
+ | Role | What it means | JIRA default | GitHub/Linear default |
188
+ |---|---|---|---|
189
+ | `ready` | Human signal "this is buildable; agent may claim" | `Ready` (status) | `status:ready` (label) |
190
+ | `claimed` | Agent has picked the item up | `In Progress` (status) | `status:in-progress` (label) |
191
+ | `review` | Build complete, in code review | `Code Review` (status) | `status:code-review` (label) |
192
+ | `blocked` | Agent stopped on triage ambiguities or external blocker | `Blocked` (status) | `status:blocked` (label) |
193
+ | `done` | Terminal state for this work, **env-keyed** | map of env → status | map of env → label |
194
+
195
+ `review` is required for label-driven systems (GitHub, Linear) because that's how the agent signals "PR opened, awaiting human review." For JIRA, `review` is optional — projects that keep the ticket in `claimed` until terminal can omit it and lifecycle skills will skip the intermediate transition.
196
+
197
+ `blocked` is what every vendor agent flips to when triage finds unresolved ambiguities or the build path is blocked by something the agent can't resolve. Different from `claimed` because it explicitly signals "human attention required."
198
+
199
+ **PRD lifecycle** (specifications):
200
+
201
+ | Role | What it means | Notion default | Confluence/GitHub/Linear default |
202
+ |---|---|---|---|
203
+ | `draft` | Author drafting; agent ignores until promoted to `ready` | `Draft` (status) | `prd-draft` (GitHub/Linear label); parent-page lookup (Confluence) |
204
+ | `ready` | "Ready for ticketing"; agent claims | `Ready` (status) | `prd-ready` (label) |
205
+ | `in_review` | Agent has claimed and is validating | `In Review` (status) | `prd-in-review` (label) |
206
+ | `blocked` | Validation failed; clarifying-comments posted | `Blocked` (status) | `prd-blocked` (label) |
207
+ | `ticketed` | Validated and tickets created | `Ticketed` (status) | `prd-ticketed` (label) |
208
+ | `shipped` | All child tickets shipped | `Shipped` (status) | `prd-shipped` (label) |
209
+ | `sentinel` | (PRD-intake feedback issue marker, GitHub/Linear self-host only) | — | `prd-intake-feedback` |
210
+
211
+ ### Env-keyed `done`
212
+
213
+ The `done` role is special: the terminal status/label depends on which environment a PR was merged into. A hotfix to staging ends at `On Stg`; a production hotfix ends at `Done`. So `done` is a **map** keyed by env name (`dev`, `staging`, `production`).
214
+
215
+ Skills that transition to `done` MUST resolve the env first:
216
+
217
+ 1. **Explicit caller arg** (`target_env=staging`) — always wins.
218
+ 2. **Branch inference** — derive from the PR's base branch via `deploy.branches`. Reverse-lookup: if base branch is `staging`, env is `staging`.
219
+ 3. **Failure** — if neither resolves and `done` is a map, fail loudly. Never pick arbitrarily.
220
+
221
+ If a project's terminal state is the same regardless of env, set `done` to a string instead of a map (lifecycle skills accept either shape).
222
+
223
+ ### What's configurable, what's not
224
+
225
+ - **Status / label NAMES** are configurable per project — that's the point of the vocabulary maps.
226
+ - **Role SEMANTICS and TRANSITIONS** are not. The build lifecycle is always `ready → claimed → done` (with optional `review` for label-driven systems). The PRD lifecycle is always `ready → in_review → (blocked | ticketed) → shipped`. Lisa skills hardcode these transitions because they encode the design intent of the framework, not the project's preferences.
227
+ - **Extra statuses/labels** the project uses outside these roles are fine — lisa never touches them.
228
+
229
+ ### Defaults vs. requirements
230
+
231
+ Vocabulary maps are **optional** in `.lisa.config.json`. Missing keys inherit the defaults shown in the schema above. The setup skills probe the project's actual workflow / labels at setup time and either:
232
+
233
+ - Confirm the default name exists → proceed silently.
234
+ - Confirm a different name exists (e.g., `Resolved` instead of `Done`) → prompt the user to either rename in the tracker or override the key in config.
235
+ - Find nothing matching → stop and ask the user to (a) create the missing status/label in the tracker, or (b) provide the actual name to write into config.
236
+
94
237
  ## Resolution algorithm
95
238
 
96
239
  Every `tracker-*` shim and every vendor-neutral caller follows this:
97
240
 
98
241
  1. Read `.lisa.config.local.json` first (if present), then `.lisa.config.json`. Local overrides global on a per-key basis. Use `jq` — never hand-parse JSON.
99
- 2. Extract the `tracker` field. If missing or null, default to `"jira"`.
242
+ 2. Extract the `tracker` field. If missing or null, stop and report: `"'tracker' is not set in .lisa.config.json. Run /lisa:setup:jira (or :github, :linear) to configure."`
100
243
  3. Dispatch:
101
244
  - `tracker = "jira"` → delegate to the matching `jira-*` skill. Validate `atlassian.cloudId` and `jira.project` are present.
102
245
  - `tracker = "github"` → delegate to the matching `github-*` skill. Validate `github.org` and `github.repo` are present.
@@ -167,21 +310,96 @@ Never overload one label across both lifecycles.
167
310
 
168
311
  The same separation applies for Linear self-host (`source = "linear"` AND `tracker = "linear"`): project-level labels (`prd-*`) drive the PRD lifecycle; issue-level labels (`status:*`) drive the build lifecycle; the sentinel feedback issue carries the issue-level `prd-intake-feedback` label.
169
312
 
313
+ ## Notion access (substrate ladder)
314
+
315
+ `notion-access` selects a substrate per operation in this order:
316
+
317
+ 1. **Notion MCP** — used when authenticated and its identity covers `notion.workspaceId`. Identity-match is verified by attempting to fetch `notion.prdDatabaseId` through the MCP; success means the MCP is authed to the correct workspace. If the MCP is authed elsewhere or unauthenticated, this tier is skipped.
318
+ 2. **curl + API token** — used when MCP isn't viable. Token is read via the standard lookup ladder (env → workspace-suffixed env → keychain → `tokenSource`).
319
+ 3. Fail with a clear diagnostic.
320
+
321
+ (No CLI tier — Notion has no first-party CLI; community wrappers aren't taken as a dependency.)
322
+
323
+ **Identity-match is mandatory.** A Notion MCP authed to the wrong workspace must be skipped, not used. `notion-access` verifies the configured `prdDatabaseId` is fetchable through the MCP before any operation; failure routes to the next tier.
324
+
325
+ **Token type**: Notion **internal-integration tokens** (`ntn_*` prefix). Created at notion.so/profile/integrations or workspace settings → Connections → New integration. Each token is **bound to one workspace** by construction. There is no v1/v2 scope mess like Atlassian — the token's access is uniform across whichever pages have been explicitly shared with the integration.
326
+
327
+ **Multi-account / multi-workspace**: same approach as Atlassian. The keychain entry is keyed by the workspace identifier (workspace id or human slug) declared in `.lisa.config.json` `notion.workspaceId`. Different projects targeting different Notion workspaces resolve to different keychain entries, no collision.
328
+
329
+ **Per-page access**: Notion's integration model requires each PRD page (or the parent database) to be explicitly **shared** with the integration before the API can see it. `setup-notion` prompts the user to share the PRD database with the freshly-created integration; downstream lifecycle skills assume the share has happened and fail loudly if a page isn't visible.
330
+
331
+ **Token storage and lookup ladder** (mirrors `atlassian-access`):
332
+
333
+ ```bash
334
+ read_notion_token() {
335
+ local workspace="$1"
336
+ [ -n "$NOTION_API_TOKEN" ] && { echo "$NOTION_API_TOKEN"; return; }
337
+ local slug=$(echo "$workspace" | tr '[:upper:]-' '[:lower:]_')
338
+ local varname="NOTION_API_TOKEN_${slug}"
339
+ [ -n "${!varname}" ] && { echo "${!varname}"; return; }
340
+ case "$(uname -s)" in
341
+ Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
342
+ Linux) command -v secret-tool >/dev/null && \
343
+ secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null ;;
344
+ MINGW*|MSYS*|CYGWIN*) cmdkey /list:"lisa-notion-${workspace}" 2>/dev/null | grep Password | awk '{print $NF}' ;;
345
+ esac
346
+ }
347
+ ```
348
+
349
+ **Schema additions** to `notion` section:
350
+
351
+ ```json
352
+ "notion": {
353
+ "workspaceId": "<uuid-or-human-slug>",
354
+ "prdDatabaseId": "<uuid>",
355
+ "statusProperty": "Status",
356
+ "values": { "draft": "Draft", "ready": "Ready", ... }
357
+ }
358
+ ```
359
+
360
+ `workspaceId` is the connection-match key. The notion-access skill calls `GET /v1/users/me` with the token and verifies the returned `bot.workspace_name` (or workspace id when Notion exposes it) matches the configured value before allowing operations to proceed.
361
+
362
+ ## Confluence PRD lifecycle uses parent pages, not labels
363
+
364
+ GitHub and Linear PRD lifecycles use labels (`prd-ready` / `prd-in-review` / etc.). **Confluence does not** — it uses parent pages instead. Each lifecycle role maps to a parent page; a PRD's current state is determined by which parent it's a child of; transitions are `PUT /wiki/api/v2/pages/{id}` with a new `parentId`.
365
+
366
+ **Why this asymmetry exists**: scoped API tokens (the only secure form Atlassian offers) cannot write labels on Confluence pages. The v1 label endpoint `POST /wiki/rest/api/content/{id}/label` rejects scoped-token granular scopes with 401 "scope does not match"; the v2 Label API group has no POST endpoint at all (see open bug `CONFCLOUD-76866`). Until Atlassian ships v2 label writes, labels are read-only via scoped tokens. Parent-id transitions, by contrast, are first-class in v2 and work with `write:page:confluence` scope.
367
+
368
+ **`confluence.parents` map**: each role's parent page id is stored in `.lisa.config.json` after `setup-confluence` creates the lifecycle scaffolding. Skills that need to discover the current state of a PRD read its `parentId` and reverse-lookup in `confluence.parents`. Skills that need to transition update the page's `parentId` to the new role's value.
369
+
370
+ **Native UX benefit**: parent-page state shows up automatically in Confluence's left-sidebar page tree — users see PRDs grouped by state without ever opening the Dashboard page. The Dashboard is still produced, but as a `Children Display`-macro aggregation rather than `Content by Label`.
371
+
372
+ ## Atlassian access (substrate ladder)
373
+
374
+ `atlassian-access` selects a substrate per operation in this order:
375
+
376
+ 1. **acli** — preferred when installed and authenticated, and when its active profile's site matches `atlassian.site` from config. `atlassian-access` calls `acli auth status` and compares the returned site/email to config before routing.
377
+ 2. **Atlassian MCP** — used when acli is unavailable for an op (e.g., Confluence page writes — acli has no `confluence page` write surface), or when acli isn't installed at all. Before routing, `atlassian-access` calls `getAccessibleAtlassianResources` and verifies `atlassian.cloudId` is in the returned list. If the configured cloudId isn't visible to the MCP's authed identity, the MCP tier is skipped.
378
+ 3. **curl + API token** — used when neither acli nor MCP is viable (headless, multi-account where MCP is authed elsewhere, scoped-token-only deployments). Token is read via the standard lookup ladder (env → email-suffixed env → keychain → `tokenSource`).
379
+ 4. Fail with a clear diagnostic listing what was attempted.
380
+
381
+ **Identity-match is mandatory at every tier.** A substrate that's authenticated as the *wrong* Atlassian account is more dangerous than no substrate — it silently performs operations against the wrong workspace. `atlassian-access` verifies identity before every operation and skips substrates that don't match.
382
+
383
+ **Why curl is still needed**: acli's Confluence surface only covers `space` and `page view`. v1 page-write endpoints accept scoped tokens but return 410 Gone (deprecated); v2 endpoints require granular OAuth scopes acli doesn't request. API tokens via Basic auth bypass this with full user scope, so curl is the headless-friendly path for ops neither acli nor MCP can do.
384
+
170
385
  ## Invariants
171
386
 
172
387
  - Project tracker selection is **persistent** within a project — always read from config, never infer from the shape of `$ARGUMENTS`. If a developer wants a different destination for one run, they edit `.lisa.config.local.json`.
388
+ - **Developer-specific fields (e.g., `atlassian.email`) live in `.lisa.config.local.json`, never in the committed file.** The committed file describes the project (which site, which tracker, which space); the local file describes the developer's identity (which account, which profile, which override). Setup skills MUST write developer-specific fields to the local override and shared fields to the committed file.
173
389
  - A vendor-neutral skill never embeds vendor-specific terminology in its prompts (no "JIRA ticket key", "epic parent" — use "tracker key", "parent issue"). The vendor skill is responsible for translating its inputs.
174
390
  - The shim layer is intentionally thin — its only job is dispatch. Gate logic, validation rules, and field schemas all live in the vendor skills.
175
- - Secrets stay in env (`JIRA_API_TOKEN`, `LINEAR_API_KEY`, `GH_TOKEN`, `NOTION_API_KEY`, `ATLASSIAN_API_TOKEN`). Configuration in `.lisa.config.json` is non-secret only — IDs, keys, slugs, project codes.
391
+ - Secrets stay in env (`ATLASSIAN_API_TOKEN`, `NOTION_API_TOKEN`, `LINEAR_API_KEY`, `GH_TOKEN`). Configuration in `.lisa.config.json` is non-secret only — IDs, keys, slugs, project codes.
392
+ - **`ATLASSIAN_API_TOKEN`** is required when the project uses JIRA or Confluence and any operation that acli doesn't cover (Confluence page writes, label edits, etc. — see `atlassian-access` skill's dispatch table). It's per-developer and per-project (different projects under different Atlassian accounts get different tokens). Setup-atlassian guides token generation and persists it to a gitignored `.envrc` (direnv) or `.env.lisa` (manual source); CI sets it directly as a pipeline secret. The token MUST belong to the account whose email is declared in `.lisa.config.local.json` `atlassian.email` — `atlassian-access` validates the pairing on first use of the curl substrate.
176
393
  - E2E test config (`E2E_BASE_URL`, `E2E_TEST_PHONE`, `E2E_TEST_OTP`, `E2E_TEST_ORG`, `E2E_GRAPHQL_URL`) stays in env for now — not tracker-related and frequently per-environment.
177
394
 
178
395
  ## Migration from the previous schema
179
396
 
180
- The pre-expansion `.lisa.config.json` had only `tracker` and `github.{org,repo}`. Existing projects continue to work the new fields are all conditionally required, and `tracker = "jira"` (the default) only requires `atlassian.cloudId` and `jira.project`, which were previously read from env (`JIRA_SERVER`, `JIRA_PROJECT`).
397
+ The pre-expansion `.lisa.config.json` had only `tracker` and `github.{org,repo}`, and a missing `tracker` defaulted to `"jira"`. That default has been removed `tracker` is now required.
398
+
399
+ To migrate a project to the new requirements:
181
400
 
182
- To migrate a project off env vars:
401
+ 1. Run `/lisa:setup:atlassian` (or `/lisa:setup:github`, `/lisa:setup:linear`) — installs the vendor MCP if needed, authenticates, and writes the vendor section.
402
+ 2. Run `/lisa:setup:jira` (or matching) — writes `jira.project` and prompts to set top-level `tracker`.
403
+ 3. Optionally run `/lisa:setup:confluence` / `/lisa:setup:notion` / etc. for source vendors — writes their sections and prompts to set top-level `source`.
183
404
 
184
- 1. Add `atlassian.cloudId` (resolve via `getAccessibleAtlassianResources` once).
185
- 2. Add `jira.project` (was `JIRA_PROJECT`).
186
- 3. Drop `JIRA_SERVER` from env (replaced by `atlassian.cloudId`).
187
- 4. Optionally add `source` to set the default PRD source for batch skills.
405
+ Projects that previously relied on the `"jira"` default will now fail loudly at the next vendor-neutral skill invocation; the error message points the user at the right setup skill.
@@ -0,0 +1,41 @@
1
+ # Repo Scope & Work-Time Splitting
2
+
3
+ Leaf work units are single-repo. A **leaf work unit** is an individually implementable ticket with no child tickets — issue types **Bug, Task, Sub-task, Improvement**. Each must name exactly one repository. **Epic, Story, Spike** are coordination containers and may span repos.
4
+
5
+ This invariant is enforced at three points: gate **S10** in the `*-validate-*` skills (write time), `task-decomposition` step 1.5 (PRD-decomposition time), and the work-time split procedure below (when an agent picks up an existing ticket to implement it).
6
+
7
+ ## Two splitting strategies, by phase
8
+
9
+ The strategy depends on whether the tickets exist yet. Do not mix them.
10
+
11
+ - **Decomposition-time (greenfield — no tickets exist yet).** Use `task-decomposition` step 1.5: create one work unit per repo and group them under a **parent Story** (the cross-repo coordination container). Children are per-repo; the parent stays cross-repo. This is the right shape when you are creating the tickets from a PRD in the first place.
12
+ - **Work-time (a ticket already exists and an agent is about to implement it).** Use the procedure below: keep the original ticket, **narrow it to one repo**, spin off a **sibling** per additional repo, and link them with a dependency. Do **not** invent a new parent Story — re-homing an in-flight ticket's hierarchy is more disruptive than narrowing it. The siblings inherit the original's existing parent (Epic/Story/Project) if it has one.
13
+
14
+ ## Work-time split procedure
15
+
16
+ When an agent reads an existing leaf work unit at the pre-flight gate (before any code is written) and the work touches more than one repo, it must STOP and split before proceeding. This is an agent-performed fix, not a product question — like auto-transitioning status, auto-splitting a cross-repo work unit is explicitly allowed (S10 is `product_relevant: false`: a cross-repo work unit is a decomposition error the agent owns, not something to bounce to the reporter).
17
+
18
+ 1. **Detect the repos.** Parse the description, acceptance criteria, and technical approach for repo references, and confirm against the actual code surfaces the change requires. If the work fits in one repo, proceed normally — no split.
19
+ 2. **Pick the repo the original keeps.** Default: the original retains the **consumer / user-facing repo** (e.g. frontend), because that is usually the ticket a stakeholder is watching and the one whose acceptance criteria describe the user-visible outcome. Each **producer repo** (e.g. backend) becomes a new sibling.
20
+ 3. **Create one sibling per additional repo, cloning the original's metadata.** Carry over: summary (re-prefixed `[repo-name]`), the three audience sections, priority, labels/components, parent (Epic/Story/Project) if the original has one, target backend environment, sign-in requirements, and a Validation Journey scoped to that repo. Scope the acceptance criteria to that repo only.
21
+ 4. **Link by dependency.** The producer repo **blocks** the consumer repo (`is blocked by` on the consumer / `blocks` on the producer), so execution order is explicit: the producing sibling ships first. When there is no clear producer/consumer direction, use `relates to`.
22
+ 5. **Narrow the original.** Edit its summary prefix, its `Repository` section, and its acceptance criteria down to the retained repo only. Remove every cross-repo reference ("and the backend should also…").
23
+ 6. **Comment on the original.** Note the split and link each new sibling so the history is auditable.
24
+ 7. **Re-validate.** Run `tracker-verify` (which runs S10) against the original and each new sibling. Every one must now PASS single-repo scope. If any still fails, the split was incomplete — fix it before proceeding.
25
+ 8. **Proceed in dependency order.** Implement the producer sibling(s) first, then the consumer (the narrowed original), respecting the `blocks` links.
26
+
27
+ ### When to block instead of split
28
+
29
+ Auto-split only when the split is unambiguous. Fall back to the standard BLOCK + reassign-to-reporter path (see the pre-flight gate in `base-rules`) when:
30
+
31
+ - The repos touched cannot be determined confidently from the ticket and the code.
32
+ - Splitting would strand stakeholder context that only the reporter can re-scope (e.g. the acceptance criteria describe a single indivisible cross-repo behavior with no clean per-repo boundary).
33
+ - The metadata required to clone (parent, environment, credentials) is itself missing — block on the missing metadata first; do not propagate gaps into the siblings.
34
+
35
+ ## Vendor mechanics
36
+
37
+ The procedure is vendor-neutral; the create + link + edit mechanics differ:
38
+
39
+ - **JIRA** — create via `mcp__atlassian__createJiraIssue` (clone fields, set the same epic parent); link via `mcp__atlassian__createIssueLink` with `Blocks` / `is blocked by` (resolve names via `mcp__atlassian__getIssueLinkTypes`); narrow the original via `mcp__atlassian__editJiraIssue`; comment via `mcp__atlassian__addCommentToJiraIssue`. See `jira-write-ticket` Phase 6.
40
+ - **GitHub** — create the sibling issue with the same labels and parent sub-issue; encode the dependency in the body (`Blocked by #<n>` / `Blocks #<n>`) and via the sub-issue/parent graph where used; edit the original's body to narrow scope. See `github-write-issue` Phase 6.
41
+ - **Linear** — create via `mcp__linear-server__save_issue` (clone fields, set the same `projectId`); add a blocking relation via the `relations` field or a paired relation call; edit the original to narrow scope. See `linear-write-issue` Phase 6.
@@ -96,6 +96,19 @@ Every change requires one or more verification types. Classify the change first,
96
96
 
97
97
  ---
98
98
 
99
+ ## Per-Work-Unit Evidence Contract
100
+
101
+ Every **leaf work unit** — an individually implementable ticket with no child tickets (issue types Bug, Task, Sub-task, Improvement) — that changes runtime behavior must declare, at creation time, the exact evidence that proves it is done. Epics, Stories, and Spikes are coordination containers, not work units: their evidence is the rollup of their children, so this contract does not apply to them.
102
+
103
+ The declaration is not a separate field — it is the set of `[EVIDENCE: name]` markers in the work unit's **Validation Journey**. Those markers are the work unit's **evidence manifest**: an enumerated, named list of the artifacts a verifier must capture. The manifest binds both ends of the ticket lifecycle:
104
+
105
+ - **At creation** — the work unit cannot be written without a Validation Journey that names at least one `[EVIDENCE: name]` artifact (enforced by gate S14 in `tracker-validate` and the vendor `*-validate-*` skills). A behavior-changing unit should name both a success artifact and an error/edge artifact.
106
+ - **At completion** — the work unit cannot be marked complete, nor transitioned to its review/Done state, until every `[EVIDENCE: name]` marker in its manifest has a captured, non-empty artifact attached to the ticket (enforced by the Task Completion Rules and Definition of Done in `verification-lifecycle`, and by the evidence-manifest gate in `tracker-evidence`). A manifest with a missing or empty artifact blocks completion exactly like a failed verification.
107
+
108
+ The manifest is the single source of truth for "what evidence is required": authored once in the Validation Journey, enforced at write time, replayed during `tracker-journey`, and checked again before the ticket closes. There is no second list to keep in sync.
109
+
110
+ ---
111
+
99
112
  ## Local vs Remote Verification
100
113
 
101
114
  Verification happens at two stages in the workflow: