用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/CodySwannGT/lisa --skill lisa-setup-linear命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | lisa-setup-linear |
| description | Configure Linear as the… |
| allowed-tools | ["Bash","Read","Write","Edit","Skill","AskUserQuestion","mcp__linear-server__authenticate","mcp__linear-server__complete_authentication"] |
Make Linear a tracker, a PRD source, or both for this project. After this skill, .lisa.config.json contains linear.workspace (+ linear.teamKey when Linear is the tracker), the team carries the lifecycle states and label namespaces lisa needs, and (optionally) tracker / source point at Linear.
The two lifecycles run on different primitives, and conflating them is the most common setup error:
linear.workflow.*. Not labels. See "Why Linear uses states, not labels" in config-resolution, and Step 3a below. lisa-linear-build-intake reads only these.prd-*), because a PRD is a Linear Project.Project labels and issue labels are distinct namespaces in Linear and are NOT interchangeable — creating an issue label named prd-ready will not work for the PRD flow. The one issue label this skill creates is the sentinel feedback marker, which belongs to the PRD flow despite being an issue label (Linear's MCP has no project-level comments — see linear-prd-intake).
A status:* issue-label namespace is no longer scaffolded or read. It was the pre-state-model build lane; see "Migrating a project that predates the state model" below for what to do with a config that still carries it.
Ask two things via AskUserQuestion.
Access path:
How should lisa talk to Linear for this project?
- MCP-only (simplest) — authenticate the Linear MCP once via browser OAuth; lisa uses it for every operation. Best for single-workspace developers on a personal laptop.
- MCP + API key (recommended for teams) — MCP for interactive dev, a personal API key in keychain for headless / CI. Continue through key-store steps.
- API-key-only (headless / CI) — store a personal API key in the OS keychain; lisa uses curl against the Linear GraphQL API. Best for pipelines / containers.
Role (multiSelect):
What should lisa use Linear for?
- Destination tracker — lisa writes Epics→Projects, Stories→Issues, Sub-tasks→Sub-issues; the build queue runs off native workflow states (
linear.workflow), not labels. Setstracker: "linear". (Requires a team key.)- PRD source — humans flag Linear projects with
prd-ready;/lisa:intakescans and ticketes them off theprd-*project-label namespace. Setssource: "linear".
The role answer drives Step 3 (states for the tracker lane, prd-* project labels for the PRD lane) and whether teamKey is required (tracker → yes).
Verify the Linear MCP is authenticated to the right workspace by listing teams:
lisa-linear-access operation: list-teams({})
If it errors / returns nothing, run mcp__linear-server__authenticate and have the user complete OAuth in the browser, then mcp__linear-server__complete_authentication, then re-list. A non-empty team list confirms the MCP is authed to a readable workspace.
Linear personal API keys are created at Linear → Settings → Security & access → Personal API keys (or https://linear.app/<workspace>/settings/account/security). Store the key in the OS keychain keyed by the workspace slug, using the clipboard-pipe pattern (key never enters chat), mirroring setup-notion:
case "$(uname -s)" in
Darwin)
cat <<EOF
1. Copy the Linear API key (starts with 'lin_api_').
2. Run this single line (leading space keeps it out of zsh history):
security delete-generic-password -s lisa-linear -a "$WORKSPACE" 2>/dev/null; TOK="\$(pbpaste)"; security add-generic-password -U -s lisa-linear -a "$WORKSPACE" -w "\$TOK"; unset TOK
EOF
;;
Linux)
cat <<EOF
secret-tool clear service lisa-linear account "$WORKSPACE" 2>/dev/null; printf '%s' "\$(wl-paste 2>/dev/null || xclip -selection clipboard -o 2>/dev/null || xsel --clipboard --output 2>/dev/null)" | secret-tool store --label="Lisa Linear ($WORKSPACE)" service lisa-linear account "$WORKSPACE"
(no clipboard tool? the command reads stdin — paste, then Ctrl-D. Or env-var fallback: export LINEAR_API_KEY_$(echo "$WORKSPACE" | tr '[:upper:]-' '[:lower:]_')="<key>")
EOF
;;
MINGW*|MSYS*|CYGWIN*)
cat <<EOF
PowerShell: \$tok = Get-Clipboard; cmdkey /generic:"lisa-linear-$WORKSPACE" /user:"$WORKSPACE" /pass:"\$tok"; Remove-Variable tok
EOF
;;
esac
Never accept the key via this skill's chat or stdin. After the user confirms storage, retrieve via the lookup ladder (env → workspace-suffixed env → keychain) and validate against the GraphQL API:
read_linear_key() { # $1=workspace slug
local ws="$1"
[ -n "$LINEAR_API_KEY" ] && { echo "$LINEAR_API_KEY"; return; }
local slug; slug=$(echo "$ws" | tr '[:upper:]-' '[:lower:]_')
local varname="LINEAR_API_KEY_${slug}"
[ -n "${!varname}" ] && { echo "${!varname}"; return; }
# Preferred path: the single secrets chokepoint. It owns the one-store rule
# and the surface ladder, so anything it can answer must not be read out of an
# OS keychain here — a second reader is how the same credential ends up living
# in two places and drifting.
#
# The CANDIDATE LADDER below must stay identical to `linear-access`. What may
# differ is only what happens after it: `linear-access` has nowhere else to
# go and fails loudly, whereas this skill falls through to the legacy keychain
# rung. Stating the invariant as "the ladder" rather than "this function" is
# deliberate — the previous wording said to keep the whole thing identical,
# which is not achievable, and a rule that cannot be followed is a rule that
# gets ignored. That is exactly how this copy kept the two-rung ladder while
# `linear-access` grew to seven, leaving `/lisa:setup:linear` unable to reach
candidates=()
[ -n ];
candidates+=()
[ -n ];
candidates+=()
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
resolver
tried=()
resolver ;
tried+=()
[ -f ];
via_lisa
via_lisa=$(node get LINEAR_API_KEY 2>/dev/null) \
&& [ -n ] && { ; ; }
from_keychain=
Darwin) from_keychain=$(security find-generic-password -s lisa-linear -a -w 2>/dev/null) ;;
Linux) -v secret-tool >/dev/null && from_keychain=$(secret-tool lookup service lisa-linear account 2>/dev/null) ;;
MINGW*|MSYS*|CYGWIN*)
from_keychain=$(LISA_CRED_TARGET= powershell.exe -NoProfile -NonInteractive -Command 2>/dev/null | -d ) ;;
[ -n ] && { ; ; }
>&2
>&2
>&2
>&2
1
}
KEY=$(read_linear_key )
[ -z ] && { >&2; 1; }
VIEWER=$(curl -s -X POST https://api.linear.app/graphql \
-H -H \
-d )
! | jq -e >/dev/null 2>&1;
>&2
1
--workspace=<slug>. Otherwise derive from the validated identity — the GraphQL organization.urlKey (API path) or the team list's workspace (MCP path). Confirm with the user; this slug is the keychain account key and the multi-workspace disambiguator.--team=<KEY>. Otherwise enumerate teams via lisa-linear-access operation: list-teams({}) (or the GraphQL teams query) and present them via AskUserQuestion (label = team key, description = team name) for the user to pick the team that owns lisa's destination Issues. If Linear is source-only, teamKey is optional — skip unless the user wants to pin a team scope.Read role → label with the default-fallback ladder the intake skills use, so scaffolded labels match exactly what they query.
read_role() { # $1=namespace (build|prd) $2=role $3=default
local ns="$1" role="$2" default="$3" local_v global_v
local_v=$(jq -r ".linear.labels.${ns}.${role} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r ".linear.labels.${ns}.${role} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
The build lane resolves to native workflow states, not labels — see "Why Linear uses states, not labels" in config-resolution. Read role → state name with the same ladder, against linear.workflow:
read_state() { # $1=role path (e.g. ready, done.dev) $2=default
local role="$1" default="$2" local_v global_v
local_v=$(jq -r ".linear.workflow.${role} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r ".linear.workflow.${role} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
Enumerate the team's states with lisa-linear-access operation: list-workflow-states (each carries id, name, type, position). For each role, resolve in this order — the same cascade lisa-setup-jira uses, with one extra rung Linear affords that JIRA does not:
ready, additionally refuse to resolve onto the team's DEFAULT state (Todo on a stock team): that inverts the gate from "a human flipped this" to "nobody has touched this" and makes every untouched backlog item claimable. Offer to create a dedicated state instead.type (ready → unstarted, claimed/review → started, blocked → started or unstarted, terminal done → completed) → present the team's state list via AskUserQuestion and let the user pick which state means this role. Record the choice as a config override in Step 4.lisa-linear-access operation: create-workflow-state (name, type, position, colour), showing the exact name and type first. Linear's API permits this where JIRA's workflow editing is admin-gated — which is why this rung exists here and not there.| Role | Default state | type | Ships with a stock team? |
|---|---|---|---|
ready | Ready | unstarted | no — must be created or mapped |
claimed | In Progress | started | yes |
review | In Review | started | yes |
blocked | Blocked | unstarted | no — must be created or mapped |
done.dev | On Dev | started | no — must be created or mapped |
done.staging | On Stg | started | no — must be created or mapped |
done.production | Done | completed | yes |
The env rungs are deliberately started, not completed. On Dev and On Stg mean "merged and deployed that far" — work that is emphatically not finished. Typing them completed would make Linear treat them as closed: they would leave the active board, stop counting in cycles, and re-create the exact premature-closure problem this model exists to fix. Only done.production is completed.
Position them between In Review and Done so the board reads left-to-right in real lifecycle order. A team that orders its board differently can pass its own position.
Turn off the team's merge → Done git automation. Linear's per-team git automations (Settings → Team → Workflow, or the gitAutomationStates API) auto-complete an Issue on merge to any branch. With this model that automation is an unwanted second writer: it jumps an Issue straight to Done at a dev merge, skipping On Dev / On Stg and asserting production-done. Lisa itself moves the state at each rung, so the automation is redundant as well as wrong. Detect it and offer to delete it; leave start and review alone — those assert non-terminal states and are harmless.
Probe with lisa-linear-access operation: list-project-labels. Create missing ones via lisa-linear-access operation: create-project-label. This probe-then-create is find-or-create per label: a label already present is reused untouched, so re-running never duplicates prd-*. These are a separate label kind from issue labels — creating an issue label of the same name will NOT work for the PRD flow.
prd-verified is the terminal lifecycle state after prd-shipped (the verified role from config-resolution, #591): /lisa:verify-prd transitions a Linear PRD project into it once the shipped product has been empirically verified against the PRD. Scaffold it through the same find-or-create path as every other prd-* row.
| Role | Default | Kind |
|---|---|---|
draft | prd-draft | project label |
ready | prd-ready | project label |
in_review | prd-in-review | project label |
blocked | prd-blocked | project label |
ticketed | prd-ticketed | project label |
shipped | prd-shipped | project label |
verified | prd-verified | project label |
sentinel | prd-intake-feedback | issue label (marks the sentinel feedback issue — create via create_issue_label) |
If the team already uses a differently-named label for a role, do not create a duplicate — present the existing labels via AskUserQuestion, map the role to the existing label, and record the mapping as a config override (Step 4).
.lisa.config.jsonlinear.workspace (and linear.teamKey when tracker) are project-wide → committed. Write only label keys that differ from defaults.
touch .lisa.config.json
[ -s .lisa.config.json ] || echo '{}' > .lisa.config.json
jq --arg ws "$WORKSPACE" \
'.linear = ((.linear // {}) | .workspace = $ws)' \
.lisa.config.json > .lisa.config.json.tmp && mv .lisa.config.json.tmp .lisa.config.json
# teamKey only when Linear is the tracker (or the user pinned a team scope).
if [ -n "$TEAM_KEY" ]; then
jq --arg tk "$TEAM_KEY" '.linear.teamKey = $tk' \
.lisa.config.json > .lisa.config.json.tmp && mv .lisa.config.json.tmp .lisa.config.json
fi
# Conditionally write label overrides (markers + PRD lane only — the build lane
# is states now, and lives under .linear.workflow below).
if [ -n "$LABEL_OVERRIDES_JSON" ] && [ "$LABEL_OVERRIDES_JSON" != "{}" ]; then
jq --argjson o "$LABEL_OVERRIDES_JSON" \
'.linear.labels = ((.linear.labels // {}) * $o)' \
.lisa.config.json > .lisa.config.json.tmp && mv .lisa.config.json.tmp .lisa.config.json
fi
# Workflow-state overrides: only roles whose resolved state name differs from
# the default, INCLUDING any the user mapped onto an existing state in 3a.
if [ -n "$WORKFLOW_OVERRIDES_JSON" ] && [ "$WORKFLOW_OVERRIDES_JSON" != "{}" ]; then
jq --argjson w "$WORKFLOW_OVERRIDES_JSON" \
\
.lisa.config.json > .lisa.config.json.tmp && .lisa.config.json.tmp .lisa.config.json
Migrating a project that predates the state model. A config carrying
linear.labels.build.{ready,claimed,review,blocked,done} was written against the
old label-driven lane. Those keys are inert now — nothing reads them — but
leaving them in place reads as configuration and will mislead the next person.
Migrate in one pass, and do it before the first intake cycle runs, or that cycle
sees an empty queue:
linear.workflow.status:* label, set its
workflow state to the role that label encoded. Do this before deleting
anything — the labels are the only record of where each Issue sits.build.{ready,claimed,review,blocked,done} from linear.labels, keeping
build.human_needed and the whole prd map.status:* labels themselves in the workspace, unapplied, until the
first intake cycle after the migration has run green. They are the rollback.No secrets in config — the API key stays in keychain / LINEAR_API_KEY, the MCP session in its own store.
tracker / sourceFor each role selected in Step 0, offer the matching top-level flag (skip if already pointing at Linear).
If tracker selected and .tracker ≠ "linear": ask "Set top-level tracker: \"linear\" so vendor-neutral skills write Issues here?" → jq '.tracker = "linear"'.
If source selected and .source ≠ "linear": ask "Set top-level source: \"linear\" so /lisa:intake (no args) scans this workspace for prd-ready projects?" → jq '.source = "linear"'.
Both are project-wide — never set without explicit confirmation.
jq -e '.linear.workspace' .lisa.config.json >/dev/null
# If tracker: also require teamKey.
[ "$(jq -r '.tracker // empty' .lisa.config.json)" = "linear" ] && jq -e '.linear.teamKey' .lisa.config.json >/dev/null
Confirm what was scaffolded is present: list-workflow-states for every build role when Linear is the tracker, list_project_labels for prd-* (including the terminal prd-verified) and list_issue_labels for the sentinel when Linear is the PRD source. Do NOT expect a status:* namespace — it is not part of this model. Report success with the resolved workspace, team key (if any), which namespaces were scaffolded (created vs. already existed), any non-default overrides, and whether tracker / source were set. Direct the user to /lisa:intake to test.
linear section's fields rather than appending — jq merge throughout.tracker / source if they already point at Linear. The keychain store in Step 1 is the user's manual action — they re-run the same security / secret-tool / cmdkey command..lisa.config.json. It stays in keychain or LINEAR_API_KEY.tracker / source without explicit confirmation — they're project-wide switches.