@llodev/pm-tasks-linear 0.0.0-pr-48-70482cb

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LLDev Information Solutions
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ <!-- readme-selector:start -->
2
+ <p align="center">
3
+ <a href="https://github.com/llodev/skills/blob/main/skills/pm-tasks-linear/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
4
+ <a href="https://github.com/llodev/skills/blob/main/skills/pm-tasks-linear/docs/i18n/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
5
+ <a href="https://github.com/llodev/skills/blob/main/skills/pm-tasks-linear/docs/i18n/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
6
+ </p>
7
+ <!-- readme-selector:end -->
8
+
9
+ # @llodev/pm-tasks-linear
10
+
11
+ [![npm](https://img.shields.io/npm/v/@llodev/pm-tasks-linear?color=blue)](https://www.npmjs.com/package/@llodev/pm-tasks-linear)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
+
14
+ Linear adapter for the `@llodev/pm-tasks-*` family. Publishes generic cards as Linear issues and dispatches CRUD operations through the [Linear MCP](https://linear.app/docs/mcp). The **first** pm-tasks family adapter to implement `task.sprint.set` (via Linear cycles).
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @llodev/pm-tasks-linear @llodev/pm-tasks-core
20
+ # or
21
+ pnpm add @llodev/pm-tasks-linear @llodev/pm-tasks-core
22
+ ```
23
+
24
+ ## Setup the MCP
25
+
26
+ Linear uses the **Linear MCP** over Streamable-HTTP at `https://mcp.linear.app/mcp`. The MCP handles OAuth — the adapter never sees tokens.
27
+
28
+ For any MCP-capable agent (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
29
+
30
+ - **Claude Code**: `claude mcp add linear -s project -- npx -y @linear/mcp-server` (or follow [Linear's MCP setup guide](https://linear.app/docs/mcp)), then approve the OAuth flow in your browser.
31
+ - **Cursor / Windsurf / Cline / Roo Code**: add an MCP entry in that agent's settings pointing at `https://mcp.linear.app/mcp`.
32
+ - **Codex**: add an `[mcp_servers.linear]` entry in `~/.codex/config.toml`.
33
+
34
+ In Claude Code, verify with `claude mcp list` — `linear` should appear as authenticated.
35
+
36
+ ## Setup the config
37
+
38
+ The `init` script enumerates your Linear team, states, labels, members, and estimation settings, then writes a `.linear.json`. Run it from within an agent session that already has the Linear MCP connected, or standalone with a Linear API key.
39
+
40
+ **MCP-driven (recommended):**
41
+
42
+ ```bash
43
+ npx @llodev/pm-tasks-linear init
44
+ ```
45
+
46
+ Walk through the prompts. The script uses the Linear MCP to discover your team metadata — no credentials needed.
47
+
48
+ **GraphQL standalone (no MCP):**
49
+
50
+ Create a personal API key at [linear.app/settings/api](https://linear.app/settings/api), then:
51
+
52
+ ```bash
53
+ export LINEAR_API_KEY=lin_api_...
54
+ npx @llodev/pm-tasks-linear init
55
+ ```
56
+
57
+ Pick where the config should live:
58
+
59
+ - **local** → `./.linear.json` (recommended for project-scoped configs, can be committed).
60
+ - **global** → platform default:
61
+ - macOS / Linux (XDG): `$XDG_CONFIG_HOME/llodev/pm-tasks/linear.json`, falling back to `~/.config/llodev/pm-tasks/linear.json`.
62
+ - Windows: `%APPDATA%\llodev\pm-tasks\linear.json`.
63
+ - Override with `LLODEV_PM_TASKS_CONFIG_HOME=/your/path`.
64
+
65
+ The init prompt prints the absolute path it will write to.
66
+
67
+ Run workspace health checks anytime with:
68
+
69
+ ```bash
70
+ npx @llodev/pm-tasks-linear init --doctor
71
+ ```
72
+
73
+ > [!IMPORTANT]
74
+ > The API key is **only** used by `init` to discover your team metadata. The Linear MCP uses OAuth — never put the API key in `.linear.json`.
75
+
76
+ ## Use
77
+
78
+ | Prompt example | What the agent does |
79
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------- |
80
+ | `"publish this plan as Linear issues"` | Phase 5 publish — parent issue + sub-issues + labels in batch |
81
+ | `"check sub-issue LEO-43"` | Phase 6 CRUD — `checklist.check` (moves sub-issue to completed state) |
82
+ | `"close LEO-42"` | Phase 6 CRUD — `task.close` (state resolved by type `completed`) |
83
+ | `"set the estimate on LEO-42 to 5 points"` | Phase 6 CRUD — `task.estimate.set` (effort → points + label `est:`) |
84
+ | `"move LEO-42 to LEO-12"` | Phase 6 CRUD — `task.parent.set` (arbitrary depth) |
85
+ | `"assign cycle 3 to LEO-42"` | Phase 6 CRUD — `task.sprint.set` (resolve cycle by number) |
86
+ | `"[autonomous] create linear issues from plan @docs/plans/X.md"` | Phase 5b autonomous (requires `autonomous.enabled: true`) |
87
+
88
+ ## Linear-specific notes
89
+
90
+ **Sub-issues as checklists:** Linear has no native checklist. Checklist items from the generic card become **sub-issues** (`save_issue { parentId }`). `checklist.check` moves the sub-issue to a completed-type state — it does not edit description text.
91
+
92
+ **States by type:** The adapter resolves states by type string (`unstarted`, `started`, `completed`, `canceled`) — never by name. State names are locale-dependent and team-customizable.
93
+
94
+ **Single assignee:** Linear supports one assignee per issue. `task.assignee.add` is a set operation — it replaces the current assignee.
95
+
96
+ **Label replace-set:** Any label write overwrites the full label array. The adapter always reads current labels before writing (read-modify-write). Never write labels without reading first.
97
+
98
+ **Cycles = sprints:** `task.sprint.set` assigns an issue to a Linear cycle. Cycles are team-gated — enable them in team Settings before use. Returns `NOT_APPLICABLE` when cycles are disabled.
99
+
100
+ **save_issue upsert:** When `id` is absent, `save_issue` **creates** a new issue. Always pass `id` on updates — omitting it silently creates a duplicate.
101
+
102
+ ## Documentation
103
+
104
+ - [`SKILL.md`](SKILL.md) — full skill reference (routing, Linear model, all phases, CRUD verbs, result envelope)
105
+ - [`references/operations.md`](references/operations.md) — verb → MCP tool mapping, task-ref resolution, error codes
106
+ - [`references/estimation.md`](references/estimation.md) — estimation strategies, `est:<slug>` label idempotency
107
+ - [`references/autonomous.md`](references/autonomous.md) — Linear autonomous mode scope and allowlist
108
+ - [`anti-patterns/linear.md`](anti-patterns/linear.md) — NEVER rules for Linear
109
+ - [`schemas/config.json`](schemas/config.json) — `.linear.json` schema
110
+
111
+ ## License
112
+
113
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,298 @@
1
+ ---
2
+ name: pm-tasks-linear
3
+ description: >-
4
+ Linear adapter for the @llodev/pm-tasks-* family. Use when the user mentions
5
+ Linear, asks to "create Linear issue", "publish to Linear", "log to Linear",
6
+ "set cycle/sprint", "check sub-issue", or uses --publish-linear; OR for CRUD
7
+ on existing issues (check sub-issue, close, due-date, assign, comment,
8
+ estimate, parent, sprint); OR when invoked autonomously with [autonomous] /
9
+ --auto sentinel. Linear model: issue upsert via save_issue; sub-issues as
10
+ hierarchy AND checklist mechanism (parentId); states by type (unstarted/
11
+ started/completed/canceled); cycles = sprints (team-gated); single assignee
12
+ (set-not-add); labels are replace-set (est: idempotency read-modify-write).
13
+ MCP: Linear MCP (Streamable-HTTP, OAuth, mcp__linear__ prefix). Modes:
14
+ paste-ready, MCP publish, autonomous (write-through with allowlist),
15
+ plan-execution. Implements 10 verbs (task.create, task.move, checklist.check,
16
+ task.close, task.due-date.set, task.assignee.add, task.comment.add,
17
+ task.parent.set, task.estimate.set, task.sprint.set). FIRST family adapter to
18
+ implement task.sprint.set (via cycles). Requires @llodev/pm-tasks-core.
19
+ license: MIT
20
+ metadata:
21
+ version: 1.0.0
22
+ tags:
23
+ - agent-skill
24
+ - linear
25
+ - plan-to-tasks
26
+ - pm-tools
27
+ - linear-mcp
28
+ family: pm-tasks
29
+ role: adapter
30
+ tool: linear
31
+ compatibility:
32
+ agents:
33
+ - claude-code
34
+ - cursor
35
+ - codex
36
+ - windsurf
37
+ - cline
38
+ - roo-code
39
+ ---
40
+
41
+ # pm-tasks-linear
42
+
43
+ Linear adapter for the `@llodev/pm-tasks-*` family. Publishes generic cards as Linear issues and dispatches CRUD verbs through the Linear MCP (`mcp__linear__*` tool prefix).
44
+
45
+ ## Routing
46
+
47
+ | Trigger | Signal | Path |
48
+ | -------------- | --------------------------------------------------- | ---------------------------------------- |
49
+ | Paste-only | No MCP, no `.linear.json` | Phase 4 only — Markdown issue body |
50
+ | MCP publish | Linear MCP connected + `.linear.json` present | Phase 4 → 5 (create parent + sub-issues) |
51
+ | Autonomous | `[autonomous]` sentinel or `--auto` flag | Phase 5b (write-through, no preview) |
52
+ | CRUD ops | Verb on existing issue (`LEO-12`, UUID, or URL) | Phase 6 — jump directly |
53
+ | Plan-execution | Plan file path or `--plan-exec` with `.linear.json` | Phase 7 (discover + dispatch) |
54
+
55
+ ## Linear model
56
+
57
+ Linear issues have:
58
+
59
+ - **Title** (required; aim for ≤80 chars for board readability).
60
+ - **Description** (Markdown; MCP instructions require **literal newlines** — not `\n` escape sequences).
61
+ - **State** — resolved by **type string** (`backlog`, `unstarted`, `started`, `completed`, `canceled`, `duplicate`). States are team-scoped and stored in `.linear.json`. **NEVER match by state name** — names are locale-dependent and change with team customization.
62
+ - **Sub-issues** — `save_issue` with `parentId` = parent issue id. Arbitrary depth (unlike Jira's one-level Subtask). Sub-issues are both the **hierarchy mechanism** and the **checklist mechanism** (see Phase 4).
63
+ - **Cycles** — Linear's sprint primitive. Team-gated (`config.cycles.enabled`). Resolved by name/number/id via `list_cycles`. `task.sprint.set` returns `NOT_APPLICABLE` when cycles are disabled.
64
+ - **Single assignee** — `save_issue { assignee }`. Set-not-add — Linear supports only one assignee. Accepts id, name, email, or `"me"`.
65
+ - **Labels** — **replace-set**: any label write overwrites the full label array. The transport always reads current labels, strips `est:*`, merges, and writes the complete set (read-modify-write). Never write labels without reading first.
66
+ - **Identifiers** — `TEAMKEY-N` (e.g. `LEO-1`) or UUID are interchangeable on all MCP tools.
67
+
68
+ **MCP transport:** Linear MCP at Streamable-HTTP (`https://mcp.linear.app/mcp`). The MCP handles OAuth — the adapter never sees tokens. All tools use the prefix `mcp__linear__`.
69
+
70
+ **`save_issue` upsert:** 8 of 10 verbs collapse onto `save_issue`. When `id` is present → update. When `id` is absent → create. Omitting `id` silently creates a new issue — always pass `id` on updates.
71
+
72
+ ## Phase 4 — Linear formatting
73
+
74
+ Apply the generic card from core's [`../pm-tasks-core/references/generic-card.md`](../pm-tasks-core/references/generic-card.md). Then map to Linear:
75
+
76
+ - Title → issue `title`.
77
+ - Sections of the generic card → Markdown headings in `description` (literal newlines, per MCP instructions).
78
+ - Labels → resolve by name from `config.labels[]`; pass through raw strings otherwise.
79
+ - Due date → `dueDate` (YYYY-MM-DD; sliced from ISO 8601). Native Linear field.
80
+ - Assignee → `save_issue { assignee }` by id/name/email/`"me"`. Single-assignee set.
81
+ - Estimate → `save_issue { estimate }` (numeric points) when `estimation.enabled` and `linearTarget === "points"`.
82
+ - **Checklists → sub-issues (M3 resolution):** The core `task.create` contract carries no `items[]`. During Phase 5 MCP publish, the SKILL orchestrates child creation: for each checklist item in the generic card, call `save_issue` with `parentId` = the created parent issue's id. This is the doc-layer resolution — Linear has no native checklist, so checklist items become **sub-issues**. `checklist.check` later moves the sub-issue to a completed-type state via `save_issue { id: subIssueId, state: <completed-type-id> }`.
83
+
84
+ ## Phase 5 — MCP publish
85
+
86
+ **Prerequisites:** Linear MCP connected in your agent.
87
+
88
+ - **Claude Code**: `claude mcp add linear -s project -- npx -y @linear/mcp-server` (or follow [Linear's MCP setup guide](https://linear.app/docs/mcp)), then approve the OAuth flow.
89
+ - **Cursor / Windsurf / Cline / Roo Code**: add an MCP entry pointing at `https://mcp.linear.app/mcp`.
90
+ - **Codex**: add `[mcp_servers.linear]` in `~/.codex/config.toml`.
91
+
92
+ In Claude Code, verify with `claude mcp list` — `linear` should appear as authenticated.
93
+
94
+ Strict order: 5.1 read `.linear.json` → 5.2 resolve labels/members/states → 5.3 preview & approval → 5.4 publish → 5.5 error handling.
95
+
96
+ MCP publish sequence:
97
+
98
+ 1. **Config discovery** — `requireConfig` loads `.linear.json` (see § Config for lookup order). Extract `team.id`, `states[]`, `labels[]`, `members[]`.
99
+ 2. **Resolve** — match label names to ids from `config.labels[]`; resolve assignee from `config.members[]` or pass-through; resolve target state from `config.states[]` by type.
100
+ 3. **Preview & approval** — show title, description excerpt, labels, assignee, estimate.
101
+ 4. **Publish** — `save_issue` (no id) → parent issue. Then one `save_issue` per checklist item with `parentId` = created id.
102
+ 5. **Confirm** — display issue identifier (`LEO-N`) + URL and each sub-issue identifier.
103
+ 6. **Error handling** — on MCP error, surface the raw error; no partial-commit guarantees (sub-issue creates are individual calls).
104
+
105
+ ### Attribution (opt-in)
106
+
107
+ Before calling `save_issue`, read `config.attribution`. If `enabled === true`, append the `descriptionFooter` returned by `getAttribution()` to the end of `description`, and prefix comments with `commentPrefix`. In autonomous mode (`[autonomous]` sentinel), the `commentPrefix` automatically becomes the `autonomousCommentPrefix`. See [references/attribution.md in pm-tasks-core](../pm-tasks-core/references/attribution.md).
108
+
109
+ ## Phase 5b — Autonomous
110
+
111
+ Skip 5.3 preview & approval. Apply the autonomous-mode contract from [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md). Audit log entries per [`../pm-tasks-core/references/audit-log-format.md`](../pm-tasks-core/references/audit-log-format.md).
112
+
113
+ Linear-specific autonomous scope: `autonomous.scope.teams` must list the target team id from `.linear.json`. Issue state transitions must be to types reachable from the current workflow (`config.states[]` is the allowlist). See [`references/autonomous.md`](references/autonomous.md).
114
+
115
+ ### Continuous operation in multi-task loops
116
+
117
+ Mandatory when the controller executes a plan with multiple tasks autonomously. Mirror each task transition on the corresponding Linear issue in real time — NEVER batch state updates at end-of-loop. See [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md) § **Continuous operation across multi-task loops**.
118
+
119
+ Linear-specific verb mapping:
120
+
121
+ | Transition | Canonical verb + MCP call(s) |
122
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
123
+ | Task started | `task.move` → `save_issue { id, state }` (type `started`) |
124
+ | Task completed | `task.close` → `save_issue { id, state }` (type `completed`) |
125
+ | Sub-issue checked | `checklist.check` → `save_issue { id: subIssueId, state }` (type `completed`) (idempotent) |
126
+ | Task failed | `task.comment.add` → `save_comment { issueId, body }` with failure mode + `task.assignee.add` to reassign for human escalation. Do NOT call `task.move(_, "done")` or `task.close`. |
127
+
128
+ **Linear note:** `get_issue` returns current state but not the full changelog. Verify lifecycle in the Linear UI activity feed when auditing.
129
+
130
+ ## Phase 6 — CRUD operations (existing issues)
131
+
132
+ For verbs other than `task.create`, jump directly to the operation.
133
+
134
+ > **MANDATORY READ:** [`references/operations.md`](references/operations.md) — full verb → MCP tool mapping, `<task-ref>` resolution, error codes, and idempotency rules.
135
+
136
+ Verb → MCP tool mapping summary:
137
+
138
+ | Core verb | Linear MCP tool | Notes |
139
+ | ------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
140
+ | `task.create` | `save_issue` (no id) | `title`+`team` required; `project: config.defaultProjectId` when set; sub-issues via `parentId` per Phase 5 |
141
+ | `task.move` | `save_issue` (id + state) | Resolve state by type: `open`→`unstarted`, `wip`→`started`, `done`→`completed` |
142
+ | `checklist.check` | `save_issue` (sub-issue id + state) | `req.itemId` = sub-issue id; check = type `completed`; uncheck = type `unstarted` |
143
+ | `task.close` | `save_issue` (id + state) | Type `completed`; cancel = type `canceled` |
144
+ | `task.due-date.set` | `save_issue` (id + dueDate) | `YYYY-MM-DD` sliced from ISO 8601 |
145
+ | `task.assignee.add` | `save_issue` (id + assignee) | Single-assignee set-not-add; accepts id/name/email/`"me"` |
146
+ | `task.comment.add` | `save_comment` (issueId + body) | Markdown, literal newlines; apply attribution prefix if enabled |
147
+ | `task.parent.set` | `save_issue` (id + parentId) | Arbitrary depth; `null` parentId detaches |
148
+ | `task.estimate.set` | `get_issue` (labels) → `save_issue` (estimate + labels) | Read-modify-write label merge; see § Estimation below |
149
+ | `task.sprint.set` | `list_cycles` → `save_issue` (id + cycle) | Team-gated; see § Sprint below |
150
+
151
+ `<task-ref>` resolution: Linear permalink (`https://linear.app/team/issue/LEO-12`) → bare identifier (`LEO-12`) → UUID → alias from `.linear.json` `taskAliases[]`.
152
+
153
+ ### Estimation (task.estimate.set detail)
154
+
155
+ > **MANDATORY READ:** [`references/estimation.md`](references/estimation.md)
156
+
157
+ Estimation records **effort** — never a calendar deadline. The request carries `{ taskId, input: EstimateInput, config: EstimationConfig }`.
158
+
159
+ Flow:
160
+
161
+ 1. `normalizeEstimate(req.input, req.config)` (core helper, never throws) → `NormalizedEstimate`.
162
+ 2. Guard: if `config.estimation.linearTarget === "points"` and `n.points` is `null` → return `INVALID_REQUEST` with hint to pick a point-based strategy or set `linearTarget: "none"` and re-run `pm-tasks-linear init`.
163
+ 3. Guard: if `config.estimation.enabled === false` → return `NOT_APPLICABLE`.
164
+ 4. `get_issue({ id: taskId })` — read current labels (Linear `labels` is a replace-set).
165
+ 5. Strip all prior `est:*` labels; append `est:<slug>` where `slug = slugify(normalized.humanReadable)`.
166
+ 6. If `est:<slug>` label does not exist in `config.labels[]` → create via `create_issue_label { name, teamId }` (create-on-demand, M1).
167
+ 7. Build native field patch: `linearTarget === "points"` → `estimate: n.points`; `linearTarget === "none"` → no native field.
168
+ 8. `save_issue({ id: taskId, ...nativeFieldPatch, labels: mergedIds })` — ONE call.
169
+ 9. Return `{ ok: true, data: { normalized, fieldWritten } }`.
170
+
171
+ The `est:<slug>` label is the durable human-readable record. Labels are plain strings: robust, idempotent, searchable.
172
+
173
+ ### Sprint (task.sprint.set detail)
174
+
175
+ > **Note:** Linear is the FIRST pm-tasks family adapter to implement `task.sprint.set`.
176
+
177
+ Cycles are Linear's sprint primitive. They are **team-gated** — not all teams use cycles.
178
+
179
+ Flow:
180
+
181
+ 1. Guard: `config.cycles.enabled === false` → return `NOT_APPLICABLE` with `{ reason: "cycles_disabled" }`.
182
+ 2. `list_cycles({ teamId })` — fetch all cycles for the team.
183
+ 3. Match `req.sprintRef` against `cycle.id` (exact), `cycle.name` (exact), or `String(cycle.number)` (exact). If no match, pass `req.sprintRef` directly as `cycle` (may be a raw UUID).
184
+ 4. `save_issue({ id: req.taskId, cycle: cycleId })`.
185
+ 5. Return `{ ok: true, data: { previousSprintRef: null, newSprintRef: cycleId } }`.
186
+
187
+ If `config.cycles.enabled` is true but no cycles exist yet on the team, step 2 returns an empty list and step 4 will likely fail at the MCP layer. Surface the raw error.
188
+
189
+ ## Phase 7 — Plan-execution mode
190
+
191
+ When the calling agent passes a plan reference (a path to a markdown plan file, a plan slug, or an explicit list of expected issue titles), this skill loads `.linear.json` via `requireConfig` and uses `discoverPlanTasks` (from `@llodev/pm-tasks-core`) to triage which tasks in the plan already exist as Linear issues in scope. The skill returns `{ found, missing, ambiguous }` — the **calling agent** decides how to act on each bucket (create missing issues via Phase 4 + 5; disambiguate by picking the right ambiguous issue; proceed with existing).
192
+
193
+ The skill does **not** assume any particular implementation strategy. It does not drive the calling agent's task loop, does not depend on any specific orchestration framework. When the calling agent finishes a task and asks the skill to record progress, it invokes the standard verbs (`task.move`, `checklist.check`, `task.comment.add`, `task.close`) directly — the same path used by Phase 5b autonomous mode.
194
+
195
+ Full contract: [`skills/pm-tasks-core/references/plan-execution.md`](../pm-tasks-core/references/plan-execution.md). Linear note: use `list_issues` with team + title filters for scope queries.
196
+
197
+ ## Result envelope
198
+
199
+ Every verb returns the core contract shape (see [`../pm-tasks-core/references/contract.md`](../pm-tasks-core/references/contract.md) § Result envelope):
200
+
201
+ ```json
202
+ {
203
+ "ok": true,
204
+ "verb": "task.create",
205
+ "tool": "linear",
206
+ "ref": { "id": "LEO-42", "url": "https://linear.app/team/issue/LEO-42", "alias": null },
207
+ "details": {}
208
+ }
209
+ ```
210
+
211
+ Linear-specific `details` per verb:
212
+
213
+ | Verb | `details` fields |
214
+ | ------------------- | ---------------------------------------------------------------------- |
215
+ | `task.create` | `{ id: "LEO-42", url?: string }` |
216
+ | `task.move` | `{ previousListOrSectionId: null, newListOrSectionId: "<stateId>" }` |
217
+ | `checklist.check` | `{ previousState, newState }` (`"complete"` / `"incomplete"`) |
218
+ | `task.close` | `{ closed: true, movedToListOrSectionId: "<stateId>" }` |
219
+ | `task.due-date.set` | `{ previousDueAt: null, newDueAt: "YYYY-MM-DD" }` |
220
+ | `task.assignee.add` | `{ added: true, currentAssigneeIds: ["<userId>"] }` |
221
+ | `task.comment.add` | `{ commentId: "<id>", postedAt: "<ISO8601>" }` |
222
+ | `task.parent.set` | `{ previousParentId: null, newParentId: "<issueId or null>" }` |
223
+ | `task.estimate.set` | `{ normalized: NormalizedEstimate, fieldWritten: "estimate" or null }` |
224
+ | `task.sprint.set` | `{ previousSprintRef: null, newSprintRef: "<cycleId>" }` |
225
+
226
+ On failure: `{ ok: false, code: "<CODE>", details: { message, ... } }`. Stable error codes: `UNSUPPORTED_VERB`, `INVALID_REQUEST`, `NOT_APPLICABLE`, `NOT_FOUND`, `AUTH_ERROR`, `RATE_LIMITED`, `MCP_ERROR`. `NOT_APPLICABLE` is returned for `task.sprint.set` when cycles are disabled, and for `task.estimate.set` when estimation is disabled.
227
+
228
+ ## Anti-patterns
229
+
230
+ > **MANDATORY READ:** [`anti-patterns/linear.md`](anti-patterns/linear.md)
231
+
232
+ Key rules (do not violate):
233
+
234
+ - NEVER write labels without reading first (replace-set — overwrites everything).
235
+ - NEVER fake checklists with `- [ ]` in description — use sub-issues.
236
+ - NEVER hard-code state names — resolve by type.
237
+ - NEVER omit `id` when updating — silently creates a duplicate.
238
+ - NEVER assume cycles exist on a team.
239
+
240
+ ## Standalone fallback
241
+
242
+ If `@llodev/pm-tasks-core` is not installed: ask the user for minimum input (title + sub-issue names) and produce a paste-ready Linear issue body from this content alone. Quality is degraded — no scope/audience/fidelity inference. Print: _"Install `@llodev/pm-tasks-core` for the full flow."_
243
+
244
+ ## Config
245
+
246
+ Lookup order: `<git-root>/.linear.json` → `~/.config/llodev/pm-tasks/linear.json` → abort with init instructions. Schema: [`schemas/config.json`](schemas/config.json). Secrets NEVER in JSON — Linear MCP handles OAuth; `init` uses `LINEAR_API_KEY` (env only) for standalone GraphQL discovery.
247
+
248
+ Key fields written by `init`:
249
+
250
+ - `team` — `{ id, key, name }`.
251
+ - `states` — `[{ id, name, type }]` (move/close targets, scoped to team; `type` is the stable reference).
252
+ - `labels` — `[{ id, name }]` (workspace-level label registry).
253
+ - `members` — `[{ id, name, email, alias? }]`.
254
+ - `estimation` — `{ strategy, linearTarget: "points"|"none", enabled, scale? }`.
255
+ - `cycles` — `{ enabled }` (gate for `task.sprint.set`).
256
+ - `projects` — `[{ id, name }]` (OPTIONAL; workspace projects discovered at init).
257
+ - `defaultProjectId` — OPTIONAL string; id from `projects[]`. When set, `task.create` files the new issue into this project automatically.
258
+ - `locale` — chosen at init; used for narration.
259
+ - `attribution` — opt-in block; see core attribution reference.
260
+ - `autonomous` — `{ enabled, allow[], scope: { teams[], projects[] }, rateLimit, auditLog }`.
261
+
262
+ ## Init
263
+
264
+ ```
265
+ npx @llodev/pm-tasks-linear init
266
+ ```
267
+
268
+ See [`../pm-tasks-core/references/init-ux.md`](../pm-tasks-core/references/init-ux.md) for the shared flow. Two discovery paths:
269
+
270
+ **MCP-driven (default, recommended):** Requires the Linear MCP connected in your agent session.
271
+
272
+ - `list_teams` — select team (`key` + `name`).
273
+ - `list_issue_statuses { team }` — discover states (move/close targets) with their stable `type`.
274
+ - `list_issue_labels` — workspace-level label registry.
275
+ - `list_users` — member roster (id/name/email).
276
+ - `get_team { query }` — team settings (`cyclesEnabled`, `issueEstimationType`).
277
+ - `list_cycles { teamId }` — confirm cycles are available.
278
+ - `list_projects { team }` — workspace projects (stored in `config.projects[]`; optional default prompt).
279
+
280
+ **GraphQL standalone:** For environments where MCP is not available. Set `LINEAR_API_KEY` in env (personal API key — no Bearer prefix needed). The init script queries the Linear GraphQL API at `https://api.linear.app/graphql` directly for the same discovery (uses `{ projects { nodes { id name } } }` for projects).
281
+
282
+ ```bash
283
+ export LINEAR_API_KEY=lin_api_...
284
+ npx @llodev/pm-tasks-linear init
285
+ ```
286
+
287
+ > [!IMPORTANT]
288
+ > The API key is **only** used by `init` to discover team metadata. The MCP uses OAuth — never put the key in `.linear.json`.
289
+
290
+ Pass `--doctor` to run workspace health checks:
291
+
292
+ ```sh
293
+ npx @llodev/pm-tasks-linear init --doctor
294
+ ```
295
+
296
+ Doctor checks (C-LIN-*): config present and valid, team resolvable, states cover at least one `completed`-type, estimation/cycles flags coherent, autonomous scope sane, project ids consistent (`C-LIN-8`: `defaultProjectId` and `autonomous.scope.projects` ids must all appear in `projects[]`).
297
+
298
+ The init prompt prints the absolute path it will write to, so you always see exactly where the file goes. Walk through the prompts and pick local (`./.linear.json`, recommended, committable) or global (`~/.config/llodev/pm-tasks/linear.json`).
@@ -0,0 +1,25 @@
1
+ import { type Runtime } from "@llodev/pm-tasks-core/runtime";
2
+ import { type McpCaller } from "./transport-linear.js";
3
+ export type { McpCaller };
4
+ export interface CreateAdapterOptions {
5
+ /** Absolute or cwd-relative path to .linear.json config file. */
6
+ configPath: string;
7
+ /** Caller-supplied MCP dispatcher (proxies to `mcp__linear__*` tools at runtime). */
8
+ mcp: McpCaller;
9
+ /** Optional session id for audit-log correlation. Defaults to generateSession() when omitted. */
10
+ session?: string;
11
+ /** Optional locale hint (forwarded from .linear.json locale field for narration). */
12
+ language?: string;
13
+ }
14
+ /**
15
+ * Construct a ready-to-use Linear adapter Runtime. Reads + parses config from
16
+ * configPath, wires the Linear MCP transport from the caller-supplied McpCaller,
17
+ * and returns the same Runtime shape as createCoreRuntime.
18
+ *
19
+ * The double-parse (adapter reads config for transport; createCoreRuntime reads
20
+ * it again for the handler layer) is intentional — extending CreateRuntimeOptions
21
+ * to carry an already-parsed config is deferred (YAGNI). Config validation lives
22
+ * in `init` / `doctor` (Phase 3), not here.
23
+ */
24
+ export declare function createAdapter(opts: CreateAdapterOptions): Promise<Runtime>;
25
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,OAAO,EAAkB,MAAM,+BAA+B,CAAC;AAChG,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE9E,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,GAAG,EAAE,SAAS,CAAC;IACf,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAchF"}
@@ -0,0 +1,29 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createCoreRuntime } from "@llodev/pm-tasks-core/runtime";
3
+ import { createLinearTransport } from "./transport-linear.js";
4
+ /**
5
+ * Construct a ready-to-use Linear adapter Runtime. Reads + parses config from
6
+ * configPath, wires the Linear MCP transport from the caller-supplied McpCaller,
7
+ * and returns the same Runtime shape as createCoreRuntime.
8
+ *
9
+ * The double-parse (adapter reads config for transport; createCoreRuntime reads
10
+ * it again for the handler layer) is intentional — extending CreateRuntimeOptions
11
+ * to carry an already-parsed config is deferred (YAGNI). Config validation lives
12
+ * in `init` / `doctor` (Phase 3), not here.
13
+ */
14
+ export async function createAdapter(opts) {
15
+ const raw = await readFile(opts.configPath, "utf-8");
16
+ const config = JSON.parse(raw);
17
+ // Cast to Transport: taskEstimateSet is narrowed to LinearTaskEstimateSetRequest
18
+ // (uses linearTarget instead of jiraTarget); the runtime always calls it via
19
+ // the task-estimate-set handler which passes req.config as-is from the caller.
20
+ const transport = createLinearTransport({ mcp: opts.mcp, config });
21
+ return createCoreRuntime({
22
+ tool: "linear",
23
+ configPath: opts.configPath,
24
+ transport,
25
+ session: opts.session,
26
+ language: opts.language,
27
+ });
28
+ }
29
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAgC,MAAM,+BAA+B,CAAC;AAChG,OAAO,EAAE,qBAAqB,EAAkB,MAAM,uBAAuB,CAAC;AAe9E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA0B;IAC5D,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,iFAAiF;IACjF,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,SAAS,GAAG,qBAAqB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,CAAyB,CAAC;IAC3F,OAAO,iBAAiB,CAAC;QACvB,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS;QACT,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/bin/init.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // pm-tasks-linear init — thin CLI shim. Library surface lives in ../init-flow.ts.
3
+ import path from "node:path";
4
+ import { runInit } from "../init-flow.js";
5
+ async function run() {
6
+ const argv = process.argv.slice(2);
7
+ if (argv.includes("--doctor")) {
8
+ const { runDoctor } = await import("../doctor-cli.js");
9
+ await runDoctor({ tool: "linear", argv });
10
+ return;
11
+ }
12
+ const outputIdx = argv.indexOf("--output");
13
+ const outPath = outputIdx !== -1 && argv[outputIdx + 1] ? path.resolve(argv[outputIdx + 1]) : undefined;
14
+ await runInit({ outPath });
15
+ }
16
+ run().catch((e) => {
17
+ console.error("pm-tasks-linear init:", e.message);
18
+ process.exit(1);
19
+ });
20
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/bin/init.ts"],"names":[],"mappings":";AACA,kFAAkF;AAClF,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,KAAK,UAAU,GAAG;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACvD,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,OAAO,GACX,SAAS,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1F,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IAChB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type DoctorCheck } from "@llodev/pm-tasks-core/doctor";
2
+ export interface LinearProbe {
3
+ /** Call Linear MCP list_teams and return team list */
4
+ getTeam?: (teamId: string) => Promise<{
5
+ id: string;
6
+ key: string;
7
+ } | null>;
8
+ }
9
+ /** Create the linear-specific check array, optionally with a live probe for C-LIN-7. */
10
+ export declare function makeLinearChecks(probe?: LinearProbe): DoctorCheck[];
11
+ /** Pre-built checks without a probe (used when running standalone without MCP). */
12
+ export declare const ADAPTER_CHECKS: DoctorCheck[];
13
+ export interface RunDoctorOpts {
14
+ tool: string;
15
+ argv: string[];
16
+ }
17
+ export declare function runDoctor({ argv }: RunDoctorOpts): Promise<void>;
18
+ //# sourceMappingURL=doctor-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-cli.d.ts","sourceRoot":"","sources":["../src/doctor-cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,WAAW,EAAiC,MAAM,8BAA8B,CAAC;AAS/F,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CAC3E;AAuSD,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAEnE;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,EAAE,WAAW,EAAuB,CAAC;AAMhE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CtE"}