@codyswann/lisa 2.21.1 → 2.23.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.
- package/package.json +3 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/agents/confluence-prd-intake.md +11 -9
- package/plugins/lisa/agents/github-agent.md +18 -10
- package/plugins/lisa/agents/github-build-intake.md +10 -8
- package/plugins/lisa/agents/github-prd-intake.md +11 -9
- package/plugins/lisa/agents/jira-agent.md +12 -8
- package/plugins/lisa/agents/jira-build-intake.md +9 -7
- package/plugins/lisa/agents/learnings-synthesizer.md +1 -1
- package/plugins/lisa/agents/linear-agent.md +15 -9
- package/plugins/lisa/agents/linear-build-intake.md +13 -11
- package/plugins/lisa/agents/linear-prd-intake.md +11 -9
- package/plugins/lisa/agents/notion-prd-intake.md +11 -9
- package/plugins/lisa/agents/pr-mining-specialist.md +1 -1
- package/plugins/lisa/agents/tracker-mining-specialist.md +1 -1
- package/plugins/lisa/commands/setup/atlassian.md +7 -0
- package/plugins/lisa/commands/setup/confluence.md +7 -0
- package/plugins/lisa/commands/setup/jira.md +7 -0
- package/plugins/lisa/commands/setup/notion.md +7 -0
- package/plugins/lisa/hooks/enforce-team-first.sh +14 -6
- package/plugins/lisa/rules/base-rules.md +3 -3
- package/plugins/lisa/rules/config-resolution.md +242 -24
- package/plugins/lisa/rules/intent-routing.md +4 -4
- package/plugins/lisa/rules/repo-scope-split.md +41 -0
- package/plugins/lisa/rules/verification.md +13 -0
- package/plugins/lisa/skills/atlassian-access/SKILL.md +260 -0
- package/plugins/lisa/skills/confluence-prd-intake/SKILL.md +167 -82
- package/plugins/lisa/skills/confluence-to-tracker/SKILL.md +39 -26
- package/plugins/lisa/skills/debrief/SKILL.md +4 -5
- package/plugins/lisa/skills/github-add-journey/SKILL.md +1 -0
- package/plugins/lisa/skills/github-build-intake/SKILL.md +104 -40
- package/plugins/lisa/skills/github-evidence/SKILL.md +22 -5
- package/plugins/lisa/skills/github-prd-intake/SKILL.md +87 -51
- package/plugins/lisa/skills/github-to-tracker/SKILL.md +2 -2
- package/plugins/lisa/skills/github-validate-issue/SKILL.md +11 -1
- package/plugins/lisa/skills/implement/SKILL.md +5 -6
- package/plugins/lisa/skills/intake/SKILL.md +5 -6
- package/plugins/lisa/skills/jira-add-journey/SKILL.md +1 -0
- package/plugins/lisa/skills/jira-build-intake/SKILL.md +110 -45
- package/plugins/lisa/skills/jira-create/SKILL.md +5 -3
- package/plugins/lisa/skills/jira-evidence/SKILL.md +19 -2
- package/plugins/lisa/skills/jira-journey/SKILL.md +3 -1
- package/plugins/lisa/skills/jira-read-ticket/SKILL.md +10 -8
- package/plugins/lisa/skills/jira-sync/SKILL.md +11 -5
- package/plugins/lisa/skills/jira-validate-ticket/SKILL.md +22 -10
- package/plugins/lisa/skills/jira-verify/SKILL.md +5 -3
- package/plugins/lisa/skills/jira-write-ticket/SKILL.md +16 -14
- package/plugins/lisa/skills/linear-add-journey/SKILL.md +1 -0
- package/plugins/lisa/skills/linear-build-intake/SKILL.md +90 -32
- package/plugins/lisa/skills/linear-evidence/SKILL.md +22 -5
- package/plugins/lisa/skills/linear-prd-intake/SKILL.md +92 -57
- package/plugins/lisa/skills/linear-validate-issue/SKILL.md +10 -0
- package/plugins/lisa/skills/monitor/SKILL.md +4 -5
- package/plugins/lisa/skills/notion-access/SKILL.md +193 -0
- package/plugins/lisa/skills/notion-prd-intake/SKILL.md +105 -46
- package/plugins/lisa/skills/notion-to-tracker/SKILL.md +7 -5
- package/plugins/lisa/skills/plan/SKILL.md +4 -5
- package/plugins/lisa/skills/research/SKILL.md +4 -5
- package/plugins/lisa/skills/setup-atlassian/SKILL.md +316 -0
- package/plugins/lisa/skills/setup-confluence/SKILL.md +245 -0
- package/plugins/lisa/skills/setup-jira/SKILL.md +198 -0
- package/plugins/lisa/skills/setup-notion/SKILL.md +283 -0
- package/plugins/lisa/skills/task-decomposition/SKILL.md +2 -0
- package/plugins/lisa/skills/ticket-triage/SKILL.md +4 -1
- package/plugins/lisa/skills/tracker-evidence/SKILL.md +1 -0
- package/plugins/lisa/skills/verification-lifecycle/SKILL.md +2 -0
- package/plugins/lisa/skills/verify/SKILL.md +4 -5
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/skills/ops-browser-uat/SKILL.md +1 -1
- package/plugins/lisa-expo/skills/ops-check-logs/SKILL.md +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/skills/nestjs-graphql/references/project-patterns.md +48 -0
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/src/base/agents/confluence-prd-intake.md +11 -9
- package/plugins/src/base/agents/github-agent.md +18 -10
- package/plugins/src/base/agents/github-build-intake.md +10 -8
- package/plugins/src/base/agents/github-prd-intake.md +11 -9
- package/plugins/src/base/agents/jira-agent.md +12 -8
- package/plugins/src/base/agents/jira-build-intake.md +9 -7
- package/plugins/src/base/agents/learnings-synthesizer.md +1 -1
- package/plugins/src/base/agents/linear-agent.md +15 -9
- package/plugins/src/base/agents/linear-build-intake.md +13 -11
- package/plugins/src/base/agents/linear-prd-intake.md +11 -9
- package/plugins/src/base/agents/notion-prd-intake.md +11 -9
- package/plugins/src/base/agents/pr-mining-specialist.md +1 -1
- package/plugins/src/base/agents/tracker-mining-specialist.md +1 -1
- package/plugins/src/base/commands/setup/atlassian.md +7 -0
- package/plugins/src/base/commands/setup/confluence.md +7 -0
- package/plugins/src/base/commands/setup/jira.md +7 -0
- package/plugins/src/base/commands/setup/notion.md +7 -0
- package/plugins/src/base/hooks/enforce-team-first.sh +14 -6
- package/plugins/src/base/rules/base-rules.md +3 -3
- package/plugins/src/base/rules/config-resolution.md +242 -24
- package/plugins/src/base/rules/intent-routing.md +4 -4
- package/plugins/src/base/rules/repo-scope-split.md +41 -0
- package/plugins/src/base/rules/verification.md +13 -0
- package/plugins/src/base/skills/atlassian-access/SKILL.md +260 -0
- package/plugins/src/base/skills/confluence-prd-intake/SKILL.md +167 -82
- package/plugins/src/base/skills/confluence-to-tracker/SKILL.md +39 -26
- package/plugins/src/base/skills/debrief/SKILL.md +4 -5
- package/plugins/src/base/skills/github-add-journey/SKILL.md +1 -0
- package/plugins/src/base/skills/github-build-intake/SKILL.md +104 -40
- package/plugins/src/base/skills/github-evidence/SKILL.md +22 -5
- package/plugins/src/base/skills/github-prd-intake/SKILL.md +87 -51
- package/plugins/src/base/skills/github-to-tracker/SKILL.md +2 -2
- package/plugins/src/base/skills/github-validate-issue/SKILL.md +11 -1
- package/plugins/src/base/skills/implement/SKILL.md +5 -6
- package/plugins/src/base/skills/intake/SKILL.md +5 -6
- package/plugins/src/base/skills/jira-add-journey/SKILL.md +1 -0
- package/plugins/src/base/skills/jira-build-intake/SKILL.md +110 -45
- package/plugins/src/base/skills/jira-create/SKILL.md +5 -3
- package/plugins/src/base/skills/jira-evidence/SKILL.md +19 -2
- package/plugins/src/base/skills/jira-journey/SKILL.md +3 -1
- package/plugins/src/base/skills/jira-read-ticket/SKILL.md +10 -8
- package/plugins/src/base/skills/jira-sync/SKILL.md +11 -5
- package/plugins/src/base/skills/jira-validate-ticket/SKILL.md +22 -10
- package/plugins/src/base/skills/jira-verify/SKILL.md +5 -3
- package/plugins/src/base/skills/jira-write-ticket/SKILL.md +16 -14
- package/plugins/src/base/skills/linear-add-journey/SKILL.md +1 -0
- package/plugins/src/base/skills/linear-build-intake/SKILL.md +90 -32
- package/plugins/src/base/skills/linear-evidence/SKILL.md +22 -5
- package/plugins/src/base/skills/linear-prd-intake/SKILL.md +92 -57
- package/plugins/src/base/skills/linear-validate-issue/SKILL.md +10 -0
- package/plugins/src/base/skills/monitor/SKILL.md +4 -5
- package/plugins/src/base/skills/notion-access/SKILL.md +193 -0
- package/plugins/src/base/skills/notion-prd-intake/SKILL.md +105 -46
- package/plugins/src/base/skills/notion-to-tracker/SKILL.md +7 -5
- package/plugins/src/base/skills/plan/SKILL.md +4 -5
- package/plugins/src/base/skills/research/SKILL.md +4 -5
- package/plugins/src/base/skills/setup-atlassian/SKILL.md +316 -0
- package/plugins/src/base/skills/setup-confluence/SKILL.md +245 -0
- package/plugins/src/base/skills/setup-jira/SKILL.md +198 -0
- package/plugins/src/base/skills/setup-notion/SKILL.md +283 -0
- package/plugins/src/base/skills/task-decomposition/SKILL.md +2 -0
- package/plugins/src/base/skills/ticket-triage/SKILL.md +4 -1
- package/plugins/src/base/skills/tracker-evidence/SKILL.md +1 -0
- package/plugins/src/base/skills/verification-lifecycle/SKILL.md +2 -0
- package/plugins/src/base/skills/verify/SKILL.md +4 -5
- package/plugins/src/expo/skills/ops-browser-uat/SKILL.md +1 -1
- package/plugins/src/expo/skills/ops-check-logs/SKILL.md +1 -1
- package/plugins/src/nestjs/skills/nestjs-graphql/references/project-patterns.md +48 -0
- package/scripts/check-plugins-sync.sh +45 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: linear-prd-intake
|
|
3
|
-
description: "Scans a Linear workspace (or a specific team) for projects
|
|
3
|
+
description: "Scans a Linear workspace (or a specific team) for projects carrying the configured `ready` PRD label and runs each one through the dry-run validation pipeline. Projects that pass every gate get tickets written and the label flipped to the configured `ticketed` label; projects that fail get clarifying-question comments (on a sentinel feedback issue under the project) and the label flipped to the configured `blocked` label. Linear counterpart of `lisa:notion-prd-intake` and `lisa:confluence-prd-intake` — the workflow is identical; only the source-of-truth tools differ. Composes existing skills (linear-to-tracker, tracker-validate, tracker-source-artifacts, product-walkthrough)."
|
|
4
4
|
allowed-tools: ["Skill", "Bash", "mcp__linear-server__list_projects", "mcp__linear-server__get_project", "mcp__linear-server__save_project", "mcp__linear-server__list_project_labels", "mcp__linear-server__list_issues", "mcp__linear-server__get_issue", "mcp__linear-server__save_issue", "mcp__linear-server__list_comments", "mcp__linear-server__save_comment", "mcp__linear-server__list_issue_labels", "mcp__linear-server__create_issue_label", "mcp__linear-server__list_documents", "mcp__linear-server__get_document", "mcp__linear-server__list_teams"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,52 +8,78 @@ allowed-tools: ["Skill", "Bash", "mcp__linear-server__list_projects", "mcp__line
|
|
|
8
8
|
|
|
9
9
|
`$ARGUMENTS` is one of:
|
|
10
10
|
|
|
11
|
-
- A Linear **workspace** URL — scans every project in the workspace whose labels include `
|
|
12
|
-
- A Linear **team** URL or team key — scans every project on the team whose labels include `
|
|
11
|
+
- A Linear **workspace** URL — scans every project in the workspace whose labels include the configured `ready` label. Example: `https://linear.app/acme`.
|
|
12
|
+
- A Linear **team** URL or team key — scans every project on the team whose labels include the configured `ready` label. Example: `https://linear.app/acme/team/ENG/projects` or bare `ENG`.
|
|
13
13
|
- The literal token `linear` — equivalent to "the default Linear workspace"; only valid if `linear.workspace` is configured in `.lisa.config.json`.
|
|
14
14
|
|
|
15
|
-
Run one intake cycle against that scope. Each project with the `
|
|
15
|
+
Run one intake cycle against that scope. Each project with the `ready` label is claimed, validated, and routed to either the `blocked` label (with clarifying comments on a sentinel feedback issue) or the `ticketed` label (with destination tickets created).
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
## Workflow resolution
|
|
18
|
+
|
|
19
|
+
PRD label names are read from `.lisa.config.json` `linear.labels.prd.*`, falling back to defaults documented in the `config-resolution` rule. Bash pattern:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Read role with default fallback. Local overrides global per-key.
|
|
23
|
+
read_role() {
|
|
24
|
+
local role="$1" default="$2"
|
|
25
|
+
local local_v global_v
|
|
26
|
+
local_v=$(jq -r ".linear.labels.prd.${role} // empty" .lisa.config.local.json 2>/dev/null)
|
|
27
|
+
global_v=$(jq -r ".linear.labels.prd.${role} // empty" .lisa.config.json 2>/dev/null)
|
|
28
|
+
echo "${local_v:-${global_v:-$default}}"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
READY=$(read_role ready "prd-ready")
|
|
32
|
+
IN_REVIEW=$(read_role in_review "prd-in-review")
|
|
33
|
+
BLOCKED=$(read_role blocked "prd-blocked")
|
|
34
|
+
TICKETED=$(read_role ticketed "prd-ticketed")
|
|
35
|
+
SHIPPED=$(read_role shipped "prd-shipped")
|
|
36
|
+
SENTINEL=$(read_role sentinel "prd-intake-feedback")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In prose below, the role names refer to the resolved labels: e.g. "the `ready` label" means whatever `linear.labels.prd.ready` resolves to (default: `prd-ready`).
|
|
40
|
+
|
|
41
|
+
This skill is the Linear counterpart of `lisa:notion-prd-intake` and `lisa:confluence-prd-intake`. The phases, gates, comment templates, and rules are identical — the only differences are (1) the lifecycle is encoded as **project labels** instead of a status property, (2) the fetch / update tools are Linear MCP, and (3) clarifying-question comments land on a sentinel feedback Issue under the project (because Linear's MCP does not expose project-level comments). Keep all three skills behaviorally aligned: when changing intake logic, change them together.
|
|
18
42
|
|
|
19
43
|
## Confirmation policy
|
|
20
44
|
|
|
21
|
-
Do NOT ask the caller whether to proceed. Once invoked with a workspace/team scope, run the cycle to completion — claim, validate, branch to `
|
|
45
|
+
Do NOT ask the caller whether to proceed. Once invoked with a workspace/team scope, run the cycle to completion — claim, validate, branch to `$BLOCKED` or `$TICKETED`, write the summary. The caller (a human or a cron) has already authorized the run by invoking the skill; re-prompting defeats the purpose of a background batch.
|
|
22
46
|
|
|
23
47
|
Specifically forbidden:
|
|
24
48
|
|
|
25
49
|
- Previewing projected scope (epic count, story count, write count) and asking whether to continue.
|
|
26
50
|
- Offering A/B/C-style choices like "proceed / skip / dry-run only" — the documented behavior IS the default.
|
|
27
|
-
- Pausing because a PRD looks large, has many open questions, or is likely to end in
|
|
51
|
+
- Pausing because a PRD looks large, has many open questions, or is likely to end in `$BLOCKED`. The `blocked` label is a valid terminal state of this lifecycle, not a failure mode — routing a PRD there with gate-failure comments is exactly how this skill communicates "the PRD needs more work before it can be ticketed." That outcome is success.
|
|
28
52
|
- Pausing because the dry-run validation looks expensive. The cost of one cycle is bounded; the cost of stalling a scheduled cron waiting on a human is unbounded.
|
|
29
53
|
|
|
30
54
|
The only legitimate reasons to stop early:
|
|
31
55
|
|
|
32
56
|
- Missing scope argument or required configuration (`linear.workspace` in `.lisa.config.json`, `E2E_BASE_URL`, etc.). Surface the missing key(s) and exit this cycle — never invent values.
|
|
33
|
-
- Workspace/team unreachable, or the labelling convention not yet adopted (no projects carry any of `
|
|
34
|
-
- Empty
|
|
57
|
+
- Workspace/team unreachable, or the labelling convention not yet adopted (no projects carry any of `$READY` / `$IN_REVIEW` / `$BLOCKED` / `$TICKETED`). Surface and exit.
|
|
58
|
+
- Empty ready set. Exit cleanly with `"No Linear projects labelled $READY. Nothing to do."`
|
|
35
59
|
|
|
36
60
|
## Lifecycle assumed
|
|
37
61
|
|
|
38
62
|
The Linear PRD lifecycle is encoded as **project labels** (we deliberately do NOT key off Linear's native project state, since project state is product's day-to-day signal and we don't want to fight it). Exactly one of these labels is expected on a project at any time:
|
|
39
63
|
|
|
40
64
|
```text
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
draft → ready → in_review → blocked | ticketed → shipped
|
|
66
|
+
(product) (us) (us) (product)
|
|
43
67
|
```
|
|
44
68
|
|
|
69
|
+
(Defaults: `prd-draft` / `prd-ready` / `prd-in-review` / `prd-blocked` / `prd-ticketed` / `prd-shipped`.)
|
|
70
|
+
|
|
45
71
|
This skill ONLY transitions:
|
|
46
72
|
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
73
|
+
- `$READY` → `$IN_REVIEW` (claim)
|
|
74
|
+
- `$IN_REVIEW` → `$BLOCKED` (gate failures or coverage gaps)
|
|
75
|
+
- `$IN_REVIEW` → `$TICKETED` (success)
|
|
76
|
+
- `$TICKETED` → `$BLOCKED` (post-write coverage gaps from Phase 3e)
|
|
51
77
|
|
|
52
|
-
It never adds, removes, or touches `
|
|
78
|
+
It never adds, removes, or touches the `draft` or `shipped` labels. Those labels are owned by product.
|
|
53
79
|
|
|
54
80
|
A "transition" means: remove the old lifecycle label and add the new one in a single `save_project` call (passing the full new label set in the `labels` array). The skill MUST verify that exactly one lifecycle label exists on the project after the update — having two simultaneously breaks idempotency.
|
|
55
81
|
|
|
56
|
-
If the project does not yet use
|
|
82
|
+
If the project does not yet use these labels, this skill cannot run. Adopting the convention is a one-time setup the project owner does (see "Adoption" at the bottom of this file).
|
|
57
83
|
|
|
58
84
|
## Phases
|
|
59
85
|
|
|
@@ -67,32 +93,32 @@ If the project does not yet use `prd-*` labels, this skill cannot run. Adopting
|
|
|
67
93
|
2. Verify the scope is reachable:
|
|
68
94
|
- For a workspace: call `mcp__linear-server__list_teams` and confirm at least one team is returned (non-empty workspaces are readable; empty results indicate auth or workspace-mismatch).
|
|
69
95
|
- For a team: call `mcp__linear-server__list_teams({query: <KEY>})` and confirm the team resolves.
|
|
70
|
-
3. Resolve the project-label IDs for
|
|
96
|
+
3. Resolve the project-label IDs for `$READY`, `$IN_REVIEW`, `$BLOCKED`, `$TICKETED` via `mcp__linear-server__list_project_labels`. Cache them — every transition uses these IDs. If any of the four are missing, surface a label-convention error and exit (see "Adoption").
|
|
71
97
|
|
|
72
|
-
### Phase 2 — Find
|
|
98
|
+
### Phase 2 — Find ready PRDs
|
|
73
99
|
|
|
74
|
-
Call `mcp__linear-server__list_projects({label: "
|
|
100
|
+
Call `mcp__linear-server__list_projects({label: "$READY", ...scope-filter})`:
|
|
75
101
|
|
|
76
|
-
- For a workspace scope: pass `label: "
|
|
77
|
-
- For a team scope: pass `label: "
|
|
102
|
+
- For a workspace scope: pass `label: "$READY"` only.
|
|
103
|
+
- For a team scope: pass `label: "$READY"` AND `team: "<KEY>"`.
|
|
78
104
|
|
|
79
|
-
The query returns the list of candidate projects with IDs, names, and label sets. For each candidate, confirm that exactly one lifecycle label is present (the API filter is
|
|
105
|
+
The query returns the list of candidate projects with IDs, names, and label sets. For each candidate, confirm that exactly one lifecycle label is present (the API filter is `$READY`, but a project could have ended up with two labels by hand — that's a misconfiguration, not a normal queue entry).
|
|
80
106
|
|
|
81
107
|
If the result set is empty, run a secondary query to distinguish between a genuinely empty queue and a workspace/team that has not yet adopted the label convention:
|
|
82
108
|
|
|
83
|
-
- Secondary query: `list_projects({...scope-filter})` with no label filter, then in-process check whether any returned project carries any of `
|
|
109
|
+
- Secondary query: `list_projects({...scope-filter})` with no label filter, then in-process check whether any returned project carries any of `$READY` / `$IN_REVIEW` / `$BLOCKED` / `$TICKETED`.
|
|
84
110
|
|
|
85
|
-
If the secondary query shows zero projects carrying any
|
|
111
|
+
If the secondary query shows zero projects carrying any PRD lifecycle label → the convention has not been adopted. Surface a misconfiguration message: `"No Linear projects in this scope carry PRD lifecycle labels. If this is a new project, apply the $READY label to projects that are ready for ticketing (see Adoption section)."` Exit with an error — this is a setup issue, not a normal idle cycle.
|
|
86
112
|
|
|
87
|
-
If the secondary query shows projects with other
|
|
113
|
+
If the secondary query shows projects with other PRD lifecycle labels but none with `$READY` → the queue is genuinely empty (all PRDs are already in `in_review`, `blocked`, `ticketed`, or `shipped`). Exit cleanly with `"No Linear projects labelled $READY. Nothing to do."`
|
|
88
114
|
|
|
89
|
-
### Phase 3 — Process each
|
|
115
|
+
### Phase 3 — Process each ready PRD
|
|
90
116
|
|
|
91
117
|
For each project (process serially to keep label transitions auditable):
|
|
92
118
|
|
|
93
119
|
#### 3a. Claim
|
|
94
120
|
|
|
95
|
-
Transition labels via `mcp__linear-server__save_project({id, labels})`: pass the full new label set with `
|
|
121
|
+
Transition labels via `mcp__linear-server__save_project({id, labels})`: pass the full new label set with `$READY` removed and `$IN_REVIEW` added. This is the idempotency lock — a re-entrant cycle running concurrently won't see this project because its query filters on `label: "$READY"`.
|
|
96
122
|
|
|
97
123
|
If the update fails (permission error, race condition), log it and skip this project. Do not proceed to validation on a project you didn't successfully claim.
|
|
98
124
|
|
|
@@ -114,8 +140,8 @@ This call also indirectly invokes `lisa:tracker-source-artifacts` (artifact extr
|
|
|
114
140
|
|
|
115
141
|
1. Re-invoke `lisa:linear-to-tracker` with `dry_run: false` to actually write the tickets. This re-runs Phases 1-5 and runs the preservation gate (Phase 5.5).
|
|
116
142
|
2. Capture the created ticket keys from the skill's output.
|
|
117
|
-
3. Ensure the project has a sentinel feedback issue (see "Sentinel feedback issue" below for the helper). Post a comment on it via `mcp__linear-server__save_comment` listing the created tickets (epic, stories, sub-tasks) with their JIRA URLs. Lead with: `"Ticketed by Claude. Created N JIRA issues — see below. Add the
|
|
118
|
-
4. Transition labels: remove
|
|
143
|
+
3. Ensure the project has a sentinel feedback issue (see "Sentinel feedback issue" below for the helper). Post a comment on it via `mcp__linear-server__save_comment` listing the created tickets (epic, stories, sub-tasks) with their JIRA URLs. Lead with: `"Ticketed by Claude. Created N JIRA issues — see below. Add the $SHIPPED label to the Linear project after the work is delivered."`
|
|
144
|
+
4. Transition labels: remove `$IN_REVIEW`, add `$TICKETED` via `save_project`.
|
|
119
145
|
5. **Run Phase 3e (coverage audit)** before considering this PRD done.
|
|
120
146
|
|
|
121
147
|
**If `FAIL`** (one or more planned tickets failed one or more gates):
|
|
@@ -144,7 +170,7 @@ Each comment body MUST contain these four parts, in this order, no exceptions:
|
|
|
144
170
|
|
|
145
171
|
**Recommendation:** <validator's `recommendation` field, verbatim — must contain 1–3 concrete options, never a generic "please clarify">
|
|
146
172
|
|
|
147
|
-
**Action:** Update this section in the PRD, then replace the `
|
|
173
|
+
**Action:** Update this section in the PRD, then replace the `$BLOCKED` label with `$READY` on the Linear project and Claude will re-run intake.
|
|
148
174
|
```
|
|
149
175
|
|
|
150
176
|
If multiple failures share an anchor, render each as its own `**What's unclear:** ... **Recommendation:** ...` block within the same comment, separated by horizontal lines (`---`). Keep the single `[Category badge]` heading at the top using the most-severe / most-blocking category from the group.
|
|
@@ -175,13 +201,13 @@ Use these exact badge labels — they are the validator's category values transl
|
|
|
175
201
|
|
|
176
202
|
##### 3c.6 Label transition
|
|
177
203
|
|
|
178
|
-
After all comments are posted (anchored groups + the optional sentinel-issue summary), transition labels: remove
|
|
204
|
+
After all comments are posted (anchored groups + the optional sentinel-issue summary), transition labels: remove `$IN_REVIEW`, add `$BLOCKED` via `save_project`. Do NOT write any destination tickets.
|
|
179
205
|
|
|
180
206
|
#### 3d. Continue
|
|
181
207
|
|
|
182
|
-
Move to the next
|
|
208
|
+
Move to the next ready PRD. One PRD failing does not affect others.
|
|
183
209
|
|
|
184
|
-
#### 3e. Coverage audit (mandatory after
|
|
210
|
+
#### 3e. Coverage audit (mandatory after $TICKETED)
|
|
185
211
|
|
|
186
212
|
Per-ticket gates prove each ticket is well-formed; they do NOT prove the *set* of created tickets covers the *whole* PRD. Silent drops happen — invoke the `lisa:prd-ticket-coverage` skill to catch them.
|
|
187
213
|
|
|
@@ -190,16 +216,16 @@ Per-ticket gates prove each ticket is well-formed; they do NOT prove the *set* o
|
|
|
190
216
|
|
|
191
217
|
| Verdict | Action |
|
|
192
218
|
|---------|--------|
|
|
193
|
-
| `COMPLETE` | Done. Leave label as
|
|
194
|
-
| `COMPLETE_WITH_SCOPE_CREEP` | Post an advisory comment on the sentinel feedback issue naming the scope-creep tickets (so product can decide whether to close them as out-of-scope). Leave label as
|
|
195
|
-
| `GAPS_FOUND` | The created ticket set is incomplete. (a) For each gap, post a comment using the same product-facing template as Phase 3c.3 — anchored on the relevant sub-issue when `prd_anchor` is non-null, on the sentinel feedback issue otherwise; category badge from the gap's `category` field; `What's unclear` and `Recommendation` from the audit report's `what` and `recommendation` fields. Apply the same forbidden-language rules from Phase 3c.5. (b) Post one summary comment on the sentinel feedback issue listing the tickets that *were* successfully created (so product knows what to keep vs. what to extend). (c) Transition labels from `
|
|
196
|
-
| `NO_TICKETS_FOUND` | Should not happen if step 2 succeeded. If it does, log it as an Error in the cycle summary and leave label as `
|
|
219
|
+
| `COMPLETE` | Done. Leave label as `$TICKETED`. Move to next PRD. |
|
|
220
|
+
| `COMPLETE_WITH_SCOPE_CREEP` | Post an advisory comment on the sentinel feedback issue naming the scope-creep tickets (so product can decide whether to close them as out-of-scope). Leave label as `$TICKETED`. |
|
|
221
|
+
| `GAPS_FOUND` | The created ticket set is incomplete. (a) For each gap, post a comment using the same product-facing template as Phase 3c.3 — anchored on the relevant sub-issue when `prd_anchor` is non-null, on the sentinel feedback issue otherwise; category badge from the gap's `category` field; `What's unclear` and `Recommendation` from the audit report's `what` and `recommendation` fields. Apply the same forbidden-language rules from Phase 3c.5. (b) Post one summary comment on the sentinel feedback issue listing the tickets that *were* successfully created (so product knows what to keep vs. what to extend). (c) Transition labels from `$TICKETED` back to `$BLOCKED` via `save_project`. |
|
|
222
|
+
| `NO_TICKETS_FOUND` | Should not happen if step 2 succeeded. If it does, log it as an Error in the cycle summary and leave label as `$TICKETED` with a comment flagging the audit failure for human review. |
|
|
197
223
|
|
|
198
224
|
3. The created tickets remain in the destination tracker regardless of the verdict — they are valid in their own right. The audit only tells us whether *more* are needed.
|
|
199
225
|
|
|
200
226
|
### Phase 4 — Summary report
|
|
201
227
|
|
|
202
|
-
After processing every
|
|
228
|
+
After processing every ready PRD, emit a summary:
|
|
203
229
|
|
|
204
230
|
```text
|
|
205
231
|
## linear-prd-intake summary
|
|
@@ -209,18 +235,18 @@ Cycle started: <ISO timestamp>
|
|
|
209
235
|
Cycle completed: <ISO timestamp>
|
|
210
236
|
|
|
211
237
|
PRDs processed: <n>
|
|
212
|
-
-
|
|
238
|
+
- $TICKETED: <n>
|
|
213
239
|
- <project name> → <epic-key> + <story-count> stories + <subtask-count> sub-tasks (coverage: COMPLETE | COMPLETE_WITH_SCOPE_CREEP)
|
|
214
|
-
-
|
|
240
|
+
- $BLOCKED: <n>
|
|
215
241
|
- <project name> → <gate-failure-count> gate failures (pre-write) OR <gap-count> coverage gaps (post-write)
|
|
216
242
|
- Errors (claim failed, etc): <n>
|
|
217
243
|
- <project name> — <reason>
|
|
218
244
|
|
|
219
|
-
Total
|
|
245
|
+
Total destination tickets created: <n>
|
|
220
246
|
Coverage audit summary: <n> COMPLETE / <n> COMPLETE_WITH_SCOPE_CREEP / <n> GAPS_FOUND
|
|
221
247
|
```
|
|
222
248
|
|
|
223
|
-
Print to the agent's output. Do not write this summary to Linear or
|
|
249
|
+
Print to the agent's output. Do not write this summary to Linear or the destination tracker — it's an operational record for the human.
|
|
224
250
|
|
|
225
251
|
## Sentinel feedback issue
|
|
226
252
|
|
|
@@ -229,52 +255,61 @@ Linear's MCP does not expose project-level comments. To preserve the comment-bas
|
|
|
229
255
|
The sentinel issue is identified by:
|
|
230
256
|
|
|
231
257
|
- A stable title: `"PRD intake: clarifying questions"`
|
|
232
|
-
- A stable label: `
|
|
258
|
+
- A stable label: `$SENTINEL` (issue-level label, distinct from the project-level PRD lifecycle labels)
|
|
233
259
|
- Membership in the project being processed
|
|
234
260
|
|
|
235
261
|
Helper behavior — call this **before** posting any clarifying-question comment in Phase 3c or 3e:
|
|
236
262
|
|
|
237
|
-
1. Search for an existing feedback issue: `list_issues({project: <id>, label: "
|
|
238
|
-
2. If none exists: ensure the `
|
|
263
|
+
1. Search for an existing feedback issue: `list_issues({project: <id>, label: "$SENTINEL"})`. If multiple match (shouldn't happen, but defensive), use the oldest by `createdAt`.
|
|
264
|
+
2. If none exists: ensure the `$SENTINEL` label exists on the project's team via `list_issue_labels` then `create_issue_label` if needed; then create the sentinel via `save_issue({team: <team-id>, project: <id>, title: "PRD intake: clarifying questions", description: "Auto-created by lisa:linear-prd-intake. This issue collects clarifying-question comments that don't anchor to a specific sub-issue. Do not close manually — it is reused across intake cycles.", labels: ["$SENTINEL"]})`. Capture the new issue identifier.
|
|
239
265
|
3. Return the issue identifier to the caller for use in `save_comment({issueId: <id>, body: ...})`.
|
|
240
266
|
|
|
241
267
|
Idempotency: the helper finds-or-creates. Re-runs of the cycle reuse the same sentinel issue. Comments accumulate; product reads top-down to see the latest cycle's findings. Do not delete or repurpose old comments — history is the audit trail.
|
|
242
268
|
|
|
243
269
|
## Idempotency & safety
|
|
244
270
|
|
|
245
|
-
- **Single-cycle scope**: this skill processes the
|
|
246
|
-
- **No writes outside the lifecycle**: this skill only ever writes to the destination tracker via `lisa:linear-to-tracker` (which delegates to `lisa:tracker-write`), only ever changes Linear project labels among
|
|
247
|
-
- **Claim-first ordering**: the label flip to `
|
|
248
|
-
- **Failure isolation**: an exception processing one project must not stop the cycle. Catch, record under "Errors" in the summary, continue to the next project. The project that errored is left labelled `
|
|
271
|
+
- **Single-cycle scope**: this skill processes the ready set as it exists at the start of Phase 2. New `$READY` projects added mid-cycle are picked up next run.
|
|
272
|
+
- **No writes outside the lifecycle**: this skill only ever writes to the destination tracker via `lisa:linear-to-tracker` (which delegates to `lisa:tracker-write`), only ever changes Linear project labels among `$IN_REVIEW`, `$BLOCKED`, `$TICKETED`, only ever creates/comments on the sentinel feedback issue (never any other Linear issue). It never edits project descriptions, never edits Linear documents, never touches the `draft` or `shipped` labels, never archives or deletes projects.
|
|
273
|
+
- **Claim-first ordering**: the label flip to `$IN_REVIEW` happens BEFORE validation runs, so a re-entrant call won't double-process.
|
|
274
|
+
- **Failure isolation**: an exception processing one project must not stop the cycle. Catch, record under "Errors" in the summary, continue to the next project. The project that errored is left labelled `$IN_REVIEW` — the human investigates from there.
|
|
249
275
|
- **Single-label invariant**: after every transition, verify exactly one lifecycle label is present on the project. If two are present (rare race), surface as an Error and skip — do NOT auto-resolve, the human decides.
|
|
250
276
|
|
|
251
277
|
## Configuration
|
|
252
278
|
|
|
253
279
|
Same configuration as `lisa:linear-to-tracker`. See that skill for the full table. Key items:
|
|
254
280
|
|
|
255
|
-
- **From `.lisa.config.json`**: `linear.workspace` (required for Linear MCP). When the destination tracker is `linear`, also `linear.teamKey`.
|
|
281
|
+
- **From `.lisa.config.json`**: `linear.workspace` (required for Linear MCP). When the destination tracker is `linear`, also `linear.teamKey`. Lifecycle label vocabulary lives under `linear.labels.prd.*` (all optional; defaults documented above).
|
|
256
282
|
- **From environment variables**: `E2E_BASE_URL`, `E2E_TEST_PHONE`, `E2E_TEST_OTP`, `E2E_TEST_ORG`, `E2E_GRAPHQL_URL` (operational E2E test config).
|
|
257
283
|
|
|
258
284
|
Destination tracker config (jira / github / linear) is consumed by `lisa:tracker-write` internally — this skill does NOT read it. If any required value is missing, surface the missing key(s) and exit this cycle — never invent values.
|
|
259
285
|
|
|
286
|
+
| Field | Default | Purpose |
|
|
287
|
+
|-------|---------|---------|
|
|
288
|
+
| `.lisa.config.json` `linear.labels.prd.ready` | `prd-ready` | Project label signalling "PRD ready for ticketing" |
|
|
289
|
+
| `.lisa.config.json` `linear.labels.prd.in_review` | `prd-in-review` | Project label set on claim |
|
|
290
|
+
| `.lisa.config.json` `linear.labels.prd.blocked` | `prd-blocked` | Project label set on validation failure |
|
|
291
|
+
| `.lisa.config.json` `linear.labels.prd.ticketed` | `prd-ticketed` | Project label set on success |
|
|
292
|
+
| `.lisa.config.json` `linear.labels.prd.shipped` | `prd-shipped` | Project label product sets after delivery |
|
|
293
|
+
| `.lisa.config.json` `linear.labels.prd.sentinel` | `prd-intake-feedback` | Issue-level label marking the sentinel feedback issue |
|
|
294
|
+
|
|
260
295
|
## Rules
|
|
261
296
|
|
|
262
297
|
- Never write to the destination tracker outside of `lisa:linear-to-tracker` → `lisa:tracker-write`. The validator's verdict gates progress; bypassing it produces broken tickets.
|
|
263
|
-
- Never add or remove a label this skill doesn't own (
|
|
298
|
+
- Never add or remove a label this skill doesn't own (`$IN_REVIEW`, `$BLOCKED`, `$TICKETED`). Product owns the `draft`, `ready`, and `shipped` labels. The issue-level `$SENTINEL` label is owned by this skill but is not a lifecycle label.
|
|
264
299
|
- Never edit a project's description or any attached Linear document. Communication with product happens only through comments on sub-issues or on the sentinel feedback issue.
|
|
265
300
|
- Never post a single dump of all gate failures on one comment. One comment per `prd_anchor` group on the relevant sub-issue (or one comment on the sentinel feedback issue for unanchored failures only). Comments must be sub-issue-anchored where possible, categorized, plain-language, and contain a concrete recommendation.
|
|
266
301
|
- Never include a gate ID, internal skill name, or engineering shorthand in a comment body.
|
|
267
302
|
- Never run more than one intake cycle concurrently against the same scope. This skill assumes serial execution.
|
|
268
303
|
- Never close, archive, or otherwise modify the sentinel feedback issue except to post comments on it. Its longevity is the audit trail.
|
|
269
|
-
- If `lisa:linear-to-tracker` returns errors, treat them as gate failures: comment +
|
|
304
|
+
- If `lisa:linear-to-tracker` returns errors, treat them as gate failures: comment + `$BLOCKED`. Don't silently fail.
|
|
270
305
|
|
|
271
306
|
## Adoption (one-time per project)
|
|
272
307
|
|
|
273
|
-
Before this skill can run against a Linear workspace or team, the team must adopt the
|
|
308
|
+
Before this skill can run against a Linear workspace or team, the team must adopt the PRD lifecycle project-label convention (defaults shown; override via `linear.labels.prd.*` if you want different names):
|
|
274
309
|
|
|
275
|
-
1. Apply `prd-ready` to projects that are ready for ticketing (replaces the Notion `Status = Ready` flip and the Confluence `prd-ready` page label).
|
|
276
|
-
2. Reserve `prd-in-review`, `prd-blocked`, `prd-ticketed` for this skill — humans should not set them manually except to recover from an error.
|
|
277
|
-
3. (Optional but recommended) Add `prd-draft` for in-progress PRDs and
|
|
310
|
+
1. Apply the `ready` label (default: `prd-ready`) to projects that are ready for ticketing (replaces the Notion `Status = Ready` flip and the Confluence `prd-ready` page label).
|
|
311
|
+
2. Reserve `in_review`, `blocked`, `ticketed` (defaults: `prd-in-review`, `prd-blocked`, `prd-ticketed`) for this skill — humans should not set them manually except to recover from an error.
|
|
312
|
+
3. (Optional but recommended) Add the `draft` and `shipped` labels (defaults: `prd-draft`, `prd-shipped`) for in-progress PRDs and delivered work respectively, so the full lifecycle is visible at a glance.
|
|
278
313
|
4. The labels must exist as **project labels** in Linear (`list_project_labels` should return them). Issue-level labels with the same names won't work; Linear keeps the two label kinds separate.
|
|
279
314
|
|
|
280
315
|
If the workspace hasn't adopted these labels, the first run exits with a label-convention error (not the idle empty-set message) — this distinguishes a setup issue from a genuinely empty queue so operators know to apply the convention rather than assuming the queue is drained. See Phase 2 for how the skill detects this case.
|
|
@@ -86,6 +86,7 @@ Each gate is tagged with a fixed `category` and a `product_relevant` boolean. Ca
|
|
|
86
86
|
| S11 Validation Journey | `acceptance-criteria` | true |
|
|
87
87
|
| S12 Source Precedence | `design-ux` | true |
|
|
88
88
|
| S13 Relationship Search | `dependency` | true |
|
|
89
|
+
| S14 Evidence manifest binding (leaf work units) | `acceptance-criteria` | true |
|
|
89
90
|
| F1 Issue type valid in team | `structural` | false |
|
|
90
91
|
| F2 Project parent exists and is in same team | `structural` | false |
|
|
91
92
|
| F3 Linked items exist | `structural` | false |
|
|
@@ -191,6 +192,14 @@ The item must EITHER have at least one entry in `relations`, OR the description
|
|
|
191
192
|
|
|
192
193
|
An item with zero relations and no documented search: FAIL.
|
|
193
194
|
|
|
195
|
+
#### S14 — Evidence manifest binding (leaf work units)
|
|
196
|
+
|
|
197
|
+
When `issue_type ∈ {Bug, Task, Sub-task, Improvement}` AND `runtime_behavior_change = true`, the `## Validation Journey` must declare at least one `[EVIDENCE: name]` marker. Each marker name must be kebab-case and unique within the item. These markers are the work unit's **evidence manifest** — the exact, enumerated set of artifacts that must be captured and attached before the item may be closed (see the "Per-Work-Unit Evidence Contract" section of the `verification` rule, the Definition of Done in `verification-lifecycle`, and the evidence-manifest gate in `tracker-evidence`).
|
|
198
|
+
|
|
199
|
+
FAIL when the Validation Journey is present but declares zero `[EVIDENCE: name]` markers, or when any marker name is empty, duplicated, or not kebab-case. A behavior-changing work unit SHOULD declare both a success marker and an error/edge marker; a journey with only one marker passes but the remediation should recommend adding the error/edge case.
|
|
200
|
+
|
|
201
|
+
This gate depends on S11. It is `N/A` for Project / Story / Spike (coordination containers, not work units) and for leaf units with `runtime_behavior_change = false` (doc-only / config-only / type-only). If S11 fails because the Validation Journey is absent, S14 also FAILs (there is no manifest to bind) with remediation pointing back to `lisa:linear-add-journey`.
|
|
202
|
+
|
|
194
203
|
### Feasibility Gates (require Linear lookups; skip in dry-run if requested)
|
|
195
204
|
|
|
196
205
|
#### F1 — Issue type valid in team
|
|
@@ -242,6 +251,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
|
|
|
242
251
|
- [PASS|FAIL|N/A] S11 Validation Journey — <one-line reason>
|
|
243
252
|
- [PASS|FAIL|N/A] S12 Source Precedence — <one-line reason>
|
|
244
253
|
- [PASS|FAIL|N/A] S13 Relationship Search — <one-line reason>
|
|
254
|
+
- [PASS|FAIL|N/A] S14 Evidence manifest binding — <one-line reason>
|
|
245
255
|
|
|
246
256
|
### Feasibility Gates (omit when --spec-only)
|
|
247
257
|
- [PASS|FAIL|N/A] F1 Issue type valid in team — <one-line reason>
|
|
@@ -10,14 +10,13 @@ Spot-check application health in the named environment (`dev` / `staging` / `pro
|
|
|
10
10
|
|
|
11
11
|
## Orchestration: agent team
|
|
12
12
|
|
|
13
|
-
If you are NOT already operating inside an agent team (no prior
|
|
13
|
+
If you are NOT already operating inside an agent team (no prior successful team-creation tool call in this session, not spawned into a team context), the very first thing you do is establish team orchestration.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
2. `TeamCreate` — actually create the team.
|
|
15
|
+
Use `TeamCreate` if available. In Claude, if `TeamCreate` has not been loaded yet, first use `ToolSearch` with `query: "select:TeamCreate"` to load its schema. If `TeamCreate` is not available, use the current runtime's tool-discovery mechanism (for Codex, `tool_search`) to discover available multi-agent/team tools, then call the appropriate team creation tool. If no team creation tool is available, explicitly state that team orchestration is unavailable in this runtime, continue as the lead agent, and preserve the workflow's review, verification, and task-tracking obligations locally.
|
|
17
16
|
|
|
18
|
-
Until
|
|
17
|
+
Until the team is established or the no-team fallback has been declared, do NOT call any of: `Agent`, `TaskCreate`, `Skill`, MCP tools (Atlassian / Linear / GitHub / Notion / Sentry), `Read`, `Write`, `Edit`, `Bash`, `Grep`, `Glob`. Hitting health endpoints, pulling logs, querying Sentry — all of those are tasks for the team you are about to create, not for the lead session before orchestration exists.
|
|
19
18
|
|
|
20
|
-
If you ARE already inside an agent team (e.g., a teammate invoked this skill via the Skill tool), do NOT
|
|
19
|
+
If you ARE already inside an agent team (e.g., a teammate invoked this skill via the Skill tool), do NOT create a second team — many harnesses reject double-creates. Continue within the existing team. The team lead created the team; teammates inherit it.
|
|
21
20
|
|
|
22
21
|
## Flow
|
|
23
22
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: notion-access
|
|
3
|
+
description: "Vendor-neutral access layer for Notion. Every notion-* skill MUST delegate through this skill rather than invoking the Notion REST API or any Notion MCP directly. Resolves a substrate per operation in this order: (1) Notion MCP if authenticated and the configured prdDatabaseId is fetchable through it (identity-match), (2) curl + Bearer auth + internal-integration token. Verifies the active connection matches `.lisa.config.json` before every operation — substrates authenticated as a different Notion workspace are skipped, not used."
|
|
4
|
+
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Notion Access: $ARGUMENTS
|
|
8
|
+
|
|
9
|
+
Single chokepoint for all Notion operations. Routes each op to a substrate, enforces connection match, returns structured result. Caller skills (`notion-*`) MUST go through this — they MUST NOT call the Notion REST API or any `mcp__*notion*` tools directly.
|
|
10
|
+
|
|
11
|
+
## Invocation contract
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
operation: read-page id: <uuid>
|
|
15
|
+
operation: write-page payload: {...} # update page properties
|
|
16
|
+
operation: archive-page id: <uuid>
|
|
17
|
+
operation: query-database id: <uuid> filter: {...} sort: {...}
|
|
18
|
+
operation: read-database id: <uuid>
|
|
19
|
+
operation: append-blocks page_id: <uuid> children: [...]
|
|
20
|
+
operation: search query: "..." [filter: { object: "page" }]
|
|
21
|
+
operation: list-users
|
|
22
|
+
operation: get-self
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The skill returns either the structured operation result (JSON) or an error message prefixed with `Error:` and a remediation hint.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### Step 1 — Substrate selection (per operation)
|
|
30
|
+
|
|
31
|
+
Read config:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
WORKSPACE=$(jq -r '.notion.workspaceId // empty' .lisa.config.json)
|
|
35
|
+
DB_ID=$(jq -r '.notion.prdDatabaseId // empty' .lisa.config.json)
|
|
36
|
+
[ -z "$WORKSPACE" ] && { echo "Error: notion.workspaceId not set. Run /lisa:setup:notion." >&2; exit 1; }
|
|
37
|
+
[ -z "$DB_ID" ] && { echo "Error: notion.prdDatabaseId not set. Run /lisa:setup:notion." >&2; exit 1; }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different workspace are skipped, not used.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
substrate=""
|
|
44
|
+
|
|
45
|
+
# Tier 1: Notion MCP (identity-matched by fetching the configured PRD database)
|
|
46
|
+
# Pseudo-code; actual call is the MCP tool invocation.
|
|
47
|
+
# Try to fetch DB_ID through the MCP. Success → MCP is authed to the right workspace.
|
|
48
|
+
# 404 / object_not_found → MCP is authed elsewhere (or unauthenticated). Skip.
|
|
49
|
+
if mcp_notion_can_fetch_database "$DB_ID"; then
|
|
50
|
+
substrate="mcp"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Tier 2: curl + API token
|
|
54
|
+
read_notion_token() {
|
|
55
|
+
local workspace="$1"
|
|
56
|
+
[ -n "$NOTION_API_TOKEN" ] && { echo "$NOTION_API_TOKEN"; return; }
|
|
57
|
+
local slug=$(echo "$workspace" | tr '[:upper:]-' '[:lower:]_')
|
|
58
|
+
local varname="NOTION_API_TOKEN_${slug}"
|
|
59
|
+
[ -n "${!varname}" ] && { echo "${!varname}"; return; }
|
|
60
|
+
case "$(uname -s)" in
|
|
61
|
+
Darwin) security find-generic-password -s lisa-notion -a "$workspace" -w 2>/dev/null ;;
|
|
62
|
+
Linux) command -v secret-tool >/dev/null && \
|
|
63
|
+
secret-tool lookup service lisa-notion account "$workspace" 2>/dev/null ;;
|
|
64
|
+
MINGW*|MSYS*|CYGWIN*) cmdkey /list:"lisa-notion-${workspace}" 2>/dev/null | grep Password | awk '{print $NF}' ;;
|
|
65
|
+
esac
|
|
66
|
+
}
|
|
67
|
+
TOKEN=$(read_notion_token "$WORKSPACE")
|
|
68
|
+
if [ -n "$TOKEN" ]; then
|
|
69
|
+
# Verify token belongs to the configured workspace.
|
|
70
|
+
me=$(curl -s -H "Authorization: Bearer $TOKEN" -H "Notion-Version: 2022-06-28" \
|
|
71
|
+
"https://api.notion.com/v1/users/me")
|
|
72
|
+
me_workspace=$(echo "$me" | jq -r '.bot.workspace_name // .bot.workspace_id // empty')
|
|
73
|
+
if [ -n "$me_workspace" ] && [ "$me_workspace" = "$WORKSPACE" ]; then
|
|
74
|
+
: ${substrate:=curl}
|
|
75
|
+
elif [ -n "$me_workspace" ]; then
|
|
76
|
+
echo "Warning: Notion token belongs to workspace '$me_workspace' but config declares '$WORKSPACE'. Skipping curl tier." >&2
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Fail loudly with actionable remediation if nothing works.
|
|
81
|
+
if [ -z "$substrate" ]; then
|
|
82
|
+
# Detect plugin enablement state for the suggestion.
|
|
83
|
+
plugin_enabled_global=$(jq -r '.enabledPlugins["notion@claude-plugins-official"] // false' ~/.claude/settings.json 2>/dev/null || echo "false")
|
|
84
|
+
plugin_enabled_project=$(jq -r '.enabledPlugins["notion@claude-plugins-official"] // false' .claude/settings.json 2>/dev/null || echo "false")
|
|
85
|
+
plugin_enabled_local=$(jq -r '.enabledPlugins["notion@claude-plugins-official"] // false' .claude/settings.local.json 2>/dev/null || echo "false")
|
|
86
|
+
|
|
87
|
+
cat >&2 <<EOF
|
|
88
|
+
Error: no Notion access substrate available for workspace '$WORKSPACE'.
|
|
89
|
+
|
|
90
|
+
Attempted:
|
|
91
|
+
MCP — $([ "$plugin_enabled_global" = "true" ] || [ "$plugin_enabled_project" = "true" ] || [ "$plugin_enabled_local" = "true" ] && echo "plugin enabled but not authenticated or cannot fetch configured prdDatabaseId" || echo "plugin not enabled in any settings.json scope")
|
|
92
|
+
curl — no NOTION_API_TOKEN found for $WORKSPACE (env, slug-suffixed env, or keychain) OR token belongs to a different workspace
|
|
93
|
+
|
|
94
|
+
Remediation paths (pick one):
|
|
95
|
+
|
|
96
|
+
1. Install the Notion MCP plugin (local scope — per-developer, gitignored).
|
|
97
|
+
This is the simplest path for single-workspace developers.
|
|
98
|
+
|
|
99
|
+
Run in your terminal:
|
|
100
|
+
|
|
101
|
+
jq '.enabledPlugins["notion@claude-plugins-official"] = true' \\
|
|
102
|
+
.claude/settings.local.json 2>/dev/null > /tmp/s && \\
|
|
103
|
+
mv /tmp/s .claude/settings.local.json || \\
|
|
104
|
+
echo '{"enabledPlugins":{"notion@claude-plugins-official":true}}' > .claude/settings.local.json
|
|
105
|
+
|
|
106
|
+
Then restart Claude Code (or run /restart-mcp) to load the plugin, and
|
|
107
|
+
invoke 'mcp__plugin_notion_notion__authenticate' to complete OAuth.
|
|
108
|
+
Also share the configured prdDatabaseId with the integration via
|
|
109
|
+
the page's '•••' menu → Connections.
|
|
110
|
+
|
|
111
|
+
2. Provision an internal-integration API token (headless / CI / multi-workspace).
|
|
112
|
+
|
|
113
|
+
Run /lisa:setup:notion — guided flow with clipboard-piped keychain store.
|
|
114
|
+
|
|
115
|
+
EOF
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 2 — Connection-match assertion
|
|
121
|
+
|
|
122
|
+
The substrate selection in Step 1 already verifies identity. This step is the explicit re-assertion before any operation runs — defensive in case substrate state changed since selection. For the curl tier, re-validate token-to-workspace pairing if more than a few minutes elapsed.
|
|
123
|
+
|
|
124
|
+
The workspace identifier stored in config is whatever stable string the user picked at setup time — typically `bot.workspace_name` (human-readable) for simplicity. If the workspace has been renamed in Notion, `setup-notion` re-detects and re-stores; the access skill surfaces the mismatch instead of silently authing as the wrong workspace.
|
|
125
|
+
|
|
126
|
+
### Step 3 — Operation dispatch
|
|
127
|
+
|
|
128
|
+
When `$substrate=mcp`, route through Notion MCP tools. When `$substrate=curl`, hit the Notion REST API directly. All curl calls use `https://api.notion.com/v1/<path>`, `Notion-Version: 2022-06-28`, `Authorization: Bearer $TOKEN`.
|
|
129
|
+
|
|
130
|
+
Substrate columns: try the column matching `$substrate` first. If that column is `—` for the requested operation (no adapter), fall through to the other substrate if it's also available. If neither has an adapter, the operation is unsupported.
|
|
131
|
+
|
|
132
|
+
| Operation | MCP adapter | curl adapter |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| **Pages** | | |
|
|
135
|
+
| `read-page id:<I>` | `mcp__claude_ai_Notion__notion-fetch` | `GET /v1/pages/<I>` |
|
|
136
|
+
| `write-page payload:<P>` | `mcp__claude_ai_Notion__notion-update-page` | `PATCH /v1/pages/<I>` body `{ "properties": {...}, "archived": true/false }` |
|
|
137
|
+
| `archive-page id:<I>` | `mcp__claude_ai_Notion__notion-update-page` (with `archived: true`) | `PATCH /v1/pages/<I>` body `{ "archived": true }` |
|
|
138
|
+
| `append-blocks page_id:<P> children:<arr>` | (no direct equivalent) | `PATCH /v1/blocks/<P>/children` body `{ "children": <arr> }` |
|
|
139
|
+
| **Databases** | | |
|
|
140
|
+
| `read-database id:<I>` | `mcp__claude_ai_Notion__notion-fetch` | `GET /v1/databases/<I>` |
|
|
141
|
+
| `query-database id:<I> filter:<F> sort:<S>` | `mcp__claude_ai_Notion__notion-search` (with collection scope) | `POST /v1/databases/<I>/query` body `{ "filter": <F>, "sorts": <S>, "page_size": <N> }` |
|
|
142
|
+
| **Comments** | | |
|
|
143
|
+
| `list-comments block_id:<I>` | (MCP lacks a generic list-comments tool) | `GET /v1/comments?block_id=<I>` |
|
|
144
|
+
| `create-comment page_id:<I> rich_text:<arr>` | `mcp__claude_ai_Notion__notion-create-comment` (page-level) | `POST /v1/comments` body `{ "parent": { "page_id": "<I>" }, "rich_text": <arr> }` |
|
|
145
|
+
| `create-comment-on-block block_id:<I> rich_text:<arr>` | `mcp__claude_ai_Notion__notion-create-comment` (with block anchor) | `POST /v1/comments` body `{ "parent": { "block_id": "<I>" }, "rich_text": <arr> }` |
|
|
146
|
+
| **Search & users** | | |
|
|
147
|
+
| `search query:<Q> [filter:<F>]` | `mcp__claude_ai_Notion__notion-search` | `POST /v1/search` body `{ "query": "<Q>", "filter": <F or null> }` |
|
|
148
|
+
| `list-users` | — | `GET /v1/users` |
|
|
149
|
+
| `get-self` | — | `GET /v1/users/me` |
|
|
150
|
+
|
|
151
|
+
Operations not in this table are unsupported — add an adapter row before invoking. Adapters MUST return parsed JSON; never raw HTTP responses.
|
|
152
|
+
|
|
153
|
+
### Step 4 — Return result
|
|
154
|
+
|
|
155
|
+
Wrap the JSON response in a `<result>` block for caller parsing. On HTTP non-2xx, prefix the error message with `Error:` and surface the HTTP status code plus Notion's response body verbatim.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
exec_op() {
|
|
159
|
+
local method="$1" path="$2" body="${3:-}"
|
|
160
|
+
local args=( -s -X "$method"
|
|
161
|
+
-H "Authorization: Bearer $TOKEN"
|
|
162
|
+
-H "Notion-Version: 2022-06-28" )
|
|
163
|
+
[ -n "$body" ] && args+=( -H "Content-Type: application/json" --data-binary "$body" )
|
|
164
|
+
local code=$(curl "${args[@]}" -o /tmp/notion-resp -w "%{http_code}" \
|
|
165
|
+
"https://api.notion.com/v1${path}")
|
|
166
|
+
if [ "${code:0:1}" != "2" ]; then
|
|
167
|
+
echo "Error: Notion API $method $path returned HTTP $code" >&2
|
|
168
|
+
cat /tmp/notion-resp >&2
|
|
169
|
+
return 1
|
|
170
|
+
fi
|
|
171
|
+
cat /tmp/notion-resp
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Invariants
|
|
176
|
+
|
|
177
|
+
- Caller skills never call `curl https://api.notion.com/...` or any `mcp__*notion*` tool directly. They invoke this skill via the Skill tool with an operation name and arguments.
|
|
178
|
+
- Substrate is selected per skill invocation following the tier ladder. The first tier that's available AND identity-matches `notion.workspaceId` wins.
|
|
179
|
+
- The connection-match check is mandatory at every tier. Skipping it (because "the user obviously meant this workspace") is forbidden — silent cross-workspace operations are exactly the multi-account hazard this design exists to prevent.
|
|
180
|
+
- API tokens never mutate. If the configured workspace's token is wrong or missing, fail loudly and tell the user to run `/lisa:setup:notion`.
|
|
181
|
+
- `Notion-Version` is pinned to `2022-06-28` — the version every existing notion-* skill targets. Bumping it is a coordinated change across the access skill and all callers.
|
|
182
|
+
|
|
183
|
+
## Headless behavior
|
|
184
|
+
|
|
185
|
+
In a headless / non-interactive context (no TTY, `CI=true`, or `-p` mode), the MCP tier is unavailable (its OAuth flow needs a browser). The ladder collapses to curl + `NOTION_API_TOKEN`. Same skill code runs identically; only the substrate changes.
|
|
186
|
+
|
|
187
|
+
## Per-page sharing prerequisite
|
|
188
|
+
|
|
189
|
+
Notion integrations only see pages that have been **explicitly shared** with them. If `read-page` or `query-database` returns a 404 or `object_not_found` error and the configured workspace is correct, the cause is almost always that the page/database wasn't shared with the integration. Surface this in the error message:
|
|
190
|
+
|
|
191
|
+
> Page <id> not visible to the integration. Open the page in Notion → "..." menu → Connections → add the lisa integration.
|
|
192
|
+
|
|
193
|
+
Do not paper over with a retry. Sharing is a one-time human action per database (or per page if the user prefers page-level sharing); failures here mean the user needs to act.
|