@enrichlayer/el-linear 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/claude-skills/linear-operations/SKILL.md +315 -0
  4. package/claude-skills/linear-operations/evals/evals.json +46 -0
  5. package/dist/commands/attachments.d.ts +2 -0
  6. package/dist/commands/attachments.js +57 -0
  7. package/dist/commands/batch.d.ts +2 -0
  8. package/dist/commands/batch.js +309 -0
  9. package/dist/commands/comments.d.ts +2 -0
  10. package/dist/commands/comments.js +272 -0
  11. package/dist/commands/config.d.ts +2 -0
  12. package/dist/commands/config.js +15 -0
  13. package/dist/commands/cycles.d.ts +2 -0
  14. package/dist/commands/cycles.js +63 -0
  15. package/dist/commands/documents.d.ts +2 -0
  16. package/dist/commands/documents.js +175 -0
  17. package/dist/commands/embeds.d.ts +2 -0
  18. package/dist/commands/embeds.js +65 -0
  19. package/dist/commands/gdoc.d.ts +2 -0
  20. package/dist/commands/gdoc.js +37 -0
  21. package/dist/commands/graphql.d.ts +2 -0
  22. package/dist/commands/graphql.js +70 -0
  23. package/dist/commands/init/aliases.d.ts +109 -0
  24. package/dist/commands/init/aliases.js +569 -0
  25. package/dist/commands/init/defaults.d.ts +25 -0
  26. package/dist/commands/init/defaults.js +112 -0
  27. package/dist/commands/init/index.d.ts +18 -0
  28. package/dist/commands/init/index.js +182 -0
  29. package/dist/commands/init/shared.d.ts +88 -0
  30. package/dist/commands/init/shared.js +164 -0
  31. package/dist/commands/init/token.d.ts +50 -0
  32. package/dist/commands/init/token.js +141 -0
  33. package/dist/commands/init/workspace.d.ts +20 -0
  34. package/dist/commands/init/workspace.js +80 -0
  35. package/dist/commands/issue-id.d.ts +28 -0
  36. package/dist/commands/issue-id.js +81 -0
  37. package/dist/commands/issues.d.ts +2 -0
  38. package/dist/commands/issues.js +1145 -0
  39. package/dist/commands/labels.d.ts +2 -0
  40. package/dist/commands/labels.js +100 -0
  41. package/dist/commands/project-milestones.d.ts +2 -0
  42. package/dist/commands/project-milestones.js +143 -0
  43. package/dist/commands/projects.d.ts +2 -0
  44. package/dist/commands/projects.js +336 -0
  45. package/dist/commands/read-shortcut.d.ts +6 -0
  46. package/dist/commands/read-shortcut.js +69 -0
  47. package/dist/commands/releases.d.ts +2 -0
  48. package/dist/commands/releases.js +142 -0
  49. package/dist/commands/search.d.ts +2 -0
  50. package/dist/commands/search.js +171 -0
  51. package/dist/commands/teams.d.ts +2 -0
  52. package/dist/commands/teams.js +19 -0
  53. package/dist/commands/templates.d.ts +2 -0
  54. package/dist/commands/templates.js +58 -0
  55. package/dist/commands/users.d.ts +2 -0
  56. package/dist/commands/users.js +17 -0
  57. package/dist/config/config.d.ts +43 -0
  58. package/dist/config/config.js +81 -0
  59. package/dist/config/issue-validation.d.ts +39 -0
  60. package/dist/config/issue-validation.js +264 -0
  61. package/dist/config/paths.d.ts +20 -0
  62. package/dist/config/paths.js +22 -0
  63. package/dist/config/resolver.d.ts +25 -0
  64. package/dist/config/resolver.js +183 -0
  65. package/dist/config/status-defaults.d.ts +13 -0
  66. package/dist/config/status-defaults.js +20 -0
  67. package/dist/config/term-enforcer.d.ts +31 -0
  68. package/dist/config/term-enforcer.js +69 -0
  69. package/dist/main.d.ts +2 -0
  70. package/dist/main.js +76 -0
  71. package/dist/queries/attachments.d.ts +3 -0
  72. package/dist/queries/attachments.js +35 -0
  73. package/dist/queries/comments.d.ts +3 -0
  74. package/dist/queries/comments.js +64 -0
  75. package/dist/queries/common.d.ts +2 -0
  76. package/dist/queries/common.js +109 -0
  77. package/dist/queries/cycles.d.ts +2 -0
  78. package/dist/queries/cycles.js +40 -0
  79. package/dist/queries/documents.d.ts +5 -0
  80. package/dist/queries/documents.js +67 -0
  81. package/dist/queries/introspect.d.ts +2 -0
  82. package/dist/queries/introspect.js +24 -0
  83. package/dist/queries/issues.d.ts +23 -0
  84. package/dist/queries/issues.js +380 -0
  85. package/dist/queries/labels.d.ts +4 -0
  86. package/dist/queries/labels.js +53 -0
  87. package/dist/queries/project-milestones.d.ts +6 -0
  88. package/dist/queries/project-milestones.js +125 -0
  89. package/dist/queries/projects.d.ts +7 -0
  90. package/dist/queries/projects.js +104 -0
  91. package/dist/queries/releases.d.ts +4 -0
  92. package/dist/queries/releases.js +85 -0
  93. package/dist/queries/search.d.ts +1 -0
  94. package/dist/queries/search.js +35 -0
  95. package/dist/queries/templates.d.ts +2 -0
  96. package/dist/queries/templates.js +30 -0
  97. package/dist/types/linear.d.ts +217 -0
  98. package/dist/types/linear.js +6 -0
  99. package/dist/utils/auth.d.ts +4 -0
  100. package/dist/utils/auth.js +23 -0
  101. package/dist/utils/auto-link-references.d.ts +47 -0
  102. package/dist/utils/auto-link-references.js +188 -0
  103. package/dist/utils/date-format.d.ts +4 -0
  104. package/dist/utils/date-format.js +8 -0
  105. package/dist/utils/download-uploads.d.ts +7 -0
  106. package/dist/utils/download-uploads.js +88 -0
  107. package/dist/utils/embed-parser.d.ts +8 -0
  108. package/dist/utils/embed-parser.js +54 -0
  109. package/dist/utils/error-messages.d.ts +4 -0
  110. package/dist/utils/error-messages.js +17 -0
  111. package/dist/utils/file-service.d.ts +13 -0
  112. package/dist/utils/file-service.js +239 -0
  113. package/dist/utils/gdoc-parser.d.ts +45 -0
  114. package/dist/utils/gdoc-parser.js +107 -0
  115. package/dist/utils/graphql-attachments-service.d.ts +12 -0
  116. package/dist/utils/graphql-attachments-service.js +46 -0
  117. package/dist/utils/graphql-documents-service.d.ts +18 -0
  118. package/dist/utils/graphql-documents-service.js +97 -0
  119. package/dist/utils/graphql-issues-service.d.ts +49 -0
  120. package/dist/utils/graphql-issues-service.js +925 -0
  121. package/dist/utils/graphql-service.d.ts +8 -0
  122. package/dist/utils/graphql-service.js +35 -0
  123. package/dist/utils/identifier-parser.d.ts +7 -0
  124. package/dist/utils/identifier-parser.js +20 -0
  125. package/dist/utils/issue-reference-extractor.d.ts +18 -0
  126. package/dist/utils/issue-reference-extractor.js +95 -0
  127. package/dist/utils/issue-reference-wrapper.d.ts +12 -0
  128. package/dist/utils/issue-reference-wrapper.js +91 -0
  129. package/dist/utils/linear-service.d.ts +26 -0
  130. package/dist/utils/linear-service.js +442 -0
  131. package/dist/utils/logger.d.ts +4 -0
  132. package/dist/utils/logger.js +8 -0
  133. package/dist/utils/markdown-prosemirror.d.ts +24 -0
  134. package/dist/utils/markdown-prosemirror.js +325 -0
  135. package/dist/utils/mention-resolver.d.ts +31 -0
  136. package/dist/utils/mention-resolver.js +234 -0
  137. package/dist/utils/output.d.ts +7 -0
  138. package/dist/utils/output.js +125 -0
  139. package/dist/utils/table-formatter.d.ts +4 -0
  140. package/dist/utils/table-formatter.js +249 -0
  141. package/dist/utils/usage.d.ts +2 -0
  142. package/dist/utils/usage.js +24 -0
  143. package/dist/utils/uuid.d.ts +2 -0
  144. package/dist/utils/uuid.js +8 -0
  145. package/dist/utils/validate-references.d.ts +10 -0
  146. package/dist/utils/validate-references.js +33 -0
  147. package/dist/utils/validators.d.ts +7 -0
  148. package/dist/utils/validators.js +71 -0
  149. package/dist/utils/workspace-url.d.ts +4 -0
  150. package/dist/utils/workspace-url.js +44 -0
  151. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Enrich Layer
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,219 @@
1
+ <p align="center">
2
+ <img src="public/logo-256.png" alt="el-linear logo" width="160" height="160" />
3
+ </p>
4
+
5
+ <h1 align="center">el-linear</h1>
6
+
7
+ A pragmatic CLI for [Linear.app](https://linear.app) — deterministic team /
8
+ label / member resolution, structured issue validation, configurable term
9
+ enforcement, and a GraphQL escape hatch for everything that isn't a
10
+ first-class command.
11
+
12
+ > **Note on naming.** This package was briefly published as
13
+ > `@enrichlayer/linctl` with binary `linctl`, but reverted to
14
+ > `@enrichlayer/el-linear` (binary `el-linear`) because of an npm name
15
+ > collision with [dorkitude/linctl](https://github.com/dorkitude/linctl). See
16
+ > [CHANGELOG.md](./CHANGELOG.md) for the migration recipe.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pnpm add -g @enrichlayer/el-linear
22
+ # or
23
+ npm install -g @enrichlayer/el-linear
24
+ ```
25
+
26
+ Requires Node.js ≥ 22.
27
+
28
+ ## Quickstart
29
+
30
+ ```bash
31
+ # Run the interactive setup wizard. Only the API token is required;
32
+ # every other step is skippable and revisitable later.
33
+ el-linear init
34
+
35
+ # Sanity-check
36
+ el-linear teams list
37
+
38
+ # Create your first issue
39
+ el-linear issues create "Investigate flaky deploy" \
40
+ --team ENG --assignee alice --project "Reliability" \
41
+ --description "..."
42
+ ```
43
+
44
+ `el-linear init` walks you through the API token, default team, member
45
+ aliases, and other defaults. Each step is also a stand-alone sub-command
46
+ (`el-linear init token`, `el-linear init aliases --import users.csv`, etc.) so
47
+ you can revisit individual sections later. Skip is the default at every
48
+ prompt — running the wizard twice with no input is a no-op.
49
+
50
+ If you'd rather skip the wizard entirely, the configuration schema is
51
+ fully documented in [docs/configuration.md](./docs/configuration.md) so
52
+ any LLM or script can write `~/.config/el-linear/config.json` directly.
53
+
54
+ Output is JSON by default. Pipe through `jq` for ad-hoc queries, or use
55
+ the built-in `--jq` / `--fields` / `--raw` flags.
56
+
57
+ ## Why el-linear
58
+
59
+ The Linear API + SDK are excellent. el-linear adds the layer above them that
60
+ every team ends up writing themselves:
61
+
62
+ | Concern | What el-linear gives you |
63
+ |---------|----------------------|
64
+ | **Name resolution** | Map team keys, member aliases, and label names to UUIDs from one config file. No fuzzy matching, no API roundtrips per call. |
65
+ | **Issue hygiene** | Required labels, required assignee, required project, type-label-to-verb conventions — all configurable. Warn or hard-fail. |
66
+ | **Term enforcement** | Catch misspellings of brand and project names in issue titles and descriptions ("EnrichLayer" → "Enrich Layer"). |
67
+ | **Status defaults** | "No project? → Triage. Has assignee+project? → Todo." Per-workspace, configurable. |
68
+ | **Auto-link & relate** | When you write `EMW-258` in a description, el-linear wraps it as a markdown link **and** creates the corresponding sidebar relation. Prose like "blocked by EMW-258" infers the relation type. |
69
+ | **`--claude` flag** | Tag issues delegated to [Claude Code](https://claude.ai/code) for autonomous work. The label is plain config; the flag is muscle memory. |
70
+ | **GraphQL escape hatch** | Anything not covered by built-in commands: `el-linear graphql '{ viewer { id } }'`. Schema introspection included. |
71
+ | **Bundled Claude skill** | The published tarball includes a `claude-skills/linear-operations/` directory you can symlink into your project's `.claude/skills/`. |
72
+
73
+ ## Authentication
74
+
75
+ The API token is resolved in this order:
76
+
77
+ 1. `--api-token <token>` flag.
78
+ 2. `LINEAR_API_TOKEN` environment variable.
79
+ 3. `~/.config/el-linear/token` file (recommended for human use).
80
+ 4. `~/.linear_api_token` file (legacy, still honored).
81
+
82
+ el-linear never logs the token.
83
+
84
+ ## Configuration
85
+
86
+ el-linear reads `~/.config/el-linear/config.json` on startup. All keys are
87
+ optional; defaults work for casual use.
88
+
89
+ ```json
90
+ {
91
+ "defaultTeam": "ENG",
92
+ "defaultLabels": ["claude"],
93
+ "members": {
94
+ "aliases": { "alice": "Alice Anderson" },
95
+ "uuids": { "Alice Anderson": "<uuid-from-linear>" }
96
+ },
97
+ "teams": { "ENG": "<uuid-from-linear>" },
98
+ "labels": {
99
+ "workspace": { "claude": "<uuid-from-linear>" },
100
+ "teams": { "ENG": { "feature": "<uuid-from-linear>" } }
101
+ },
102
+ "statusDefaults": {
103
+ "noProject": "Triage",
104
+ "withAssigneeAndProject": "Todo"
105
+ },
106
+ "terms": [
107
+ { "canonical": "Enrich Layer", "reject": ["EnrichLayer", "enrichlayer"] }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ A full reference with every key documented lives in [config.example.json](./config.example.json).
113
+
114
+ UUIDs come from the Linear UI (URL bars, settings pages) or via el-linear
115
+ itself: `el-linear teams list --raw | jq '.[] | {key, id}'`, etc.
116
+
117
+ ## Term enforcement (with brand-promotion examples)
118
+
119
+ The `terms` rules let you keep a list of canonical names and the misspellings
120
+ to reject. el-linear warns (or in `--strict` mode, throws) when an issue title
121
+ or description contains a rejected form.
122
+
123
+ ```json
124
+ {
125
+ "terms": [
126
+ { "canonical": "Enrich Layer", "reject": ["EnrichLayer", "enrichlayer", "Enrichlayer"] },
127
+ { "canonical": "Linear", "reject": ["linear.app", "Linear App"] },
128
+ { "canonical": "GitHub", "reject": ["Github", "GitHUB"] }
129
+ ]
130
+ }
131
+ ```
132
+
133
+ ```bash
134
+ $ el-linear issues create "Add EnrichLayer auth flow" --team ENG --description "..." --strict
135
+ Term enforcement failed:
136
+ - Found "EnrichLayer" — use "Enrich Layer" instead (1 occurrence)
137
+ ```
138
+
139
+ URLs and file paths are exempt — `enrichlayer.com` and `path/to/enrichlayer`
140
+ are allowed even though `enrichlayer` is rejected.
141
+
142
+ If you don't define any rules, term enforcement is a no-op.
143
+
144
+ ## The `--claude` delegation pattern
145
+
146
+ `el-linear issues create` accepts `--claude`, which applies the workspace label
147
+ configured at `config.labels.workspace.claude`. The label is the contract:
148
+ it tells [Claude Code](https://claude.ai/code) "this issue is delegated to
149
+ you for autonomous execution."
150
+
151
+ ```bash
152
+ el-linear issues create "Migrate auth middleware to new session store" \
153
+ --team ENG --assignee alice --project "Auth Refactor" \
154
+ --description "..." --claude
155
+ ```
156
+
157
+ Claude finds delegated work with `el-linear issues search "claude" --status "Todo"`.
158
+
159
+ ## Commands at a glance
160
+
161
+ ```bash
162
+ el-linear usage # full reference for all commands
163
+ el-linear <command> --help # detailed help for one command
164
+ ```
165
+
166
+ | Group | Common commands |
167
+ |-------|-----------------|
168
+ | Issues | `issues {list, search, create, read, update, delete, history, related, link-references}` |
169
+ | Comments | `comments {list, create, update}` |
170
+ | Labels | `labels {list, create, retire, restore}` |
171
+ | Projects | `projects {list, add-team, remove-team}` |
172
+ | Cycles | `cycles {list, read}` |
173
+ | Documents | `documents {list, read, create, update, delete}` |
174
+ | Releases | `releases {list, read, create, pipelines}` |
175
+ | Files | `embeds {upload, download}`, `attachments {list, create, delete}` |
176
+ | Search | `search <query>` (semantic, cross-resource) |
177
+ | Escape hatch | `graphql [query]` (with `--introspect`) |
178
+ | Config | `config show`, `users list`, `teams list`, `templates list` |
179
+
180
+ All `list` subcommands support `-l, --limit <n>`. All commands accept the
181
+ top-level filters: `--raw`, `--jq <expr>`, `--fields <list>`.
182
+
183
+ ## Use with Claude Code
184
+
185
+ el-linear ships a Claude Code skill at `claude-skills/linear-operations/SKILL.md`.
186
+ After installing the package, symlink it into your project:
187
+
188
+ ```bash
189
+ PKG=$(npm root -g)/@enrichlayer/el-linear
190
+ ln -s "$PKG/claude-skills/linear-operations" .claude/skills/linear-operations
191
+ ```
192
+
193
+ The skill teaches Claude Code el-linear's syntax, the duplicate/related issue
194
+ check, the label taxonomy, the auto-link flow, and the `--claude` delegation
195
+ pattern.
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ git clone https://github.com/enrichlayer/el-linear.git
201
+ cd el-linear
202
+ pnpm install
203
+
204
+ pnpm test # vitest
205
+ pnpm exec tsc --noEmit # typecheck
206
+ pnpm exec biome check src/
207
+ pnpm run build
208
+ node dist/main.js --version
209
+ ```
210
+
211
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
212
+
213
+ ## Built by
214
+
215
+ [Enrich Layer](https://enrichlayer.com) — data enrichment APIs.
216
+
217
+ ## License
218
+
219
+ [MIT](./LICENSE).
@@ -0,0 +1,315 @@
1
+ ---
2
+ name: linear-operations
3
+ description: "MUST be invoked before any el-linear CLI call. Covers el-linear syntax, label taxonomy, duplicate checking, issue creation conventions, and project management. Triggers on: \"create issue\", \"update issue\", \"search issues\", \"Linear\", \"el-linear\", \"label\", \"assign\". NOT for branch creation or full development workflows."
4
+ allowed-tools: Bash(el-linear:*), Bash(which:*), AskUserQuestion, Read
5
+ ---
6
+
7
+ # Linear Operations Skill
8
+
9
+ All Linear operations should go through the **`el-linear` CLI**. For command syntax, run `el-linear usage` (all commands) or `el-linear <command> --help`.
10
+
11
+ This skill covers **mandatory processes and non-obvious rules** — everything the CLI help doesn't tell you.
12
+
13
+ > **Team-specific overrides.** Many teams keep their own issue-creation guide, label taxonomy, or member alias map. If your project has a `CLAUDE.md` or sibling skill that supplements this one, treat its rules as authoritative on top of these defaults.
14
+
15
+ ---
16
+
17
+ ## Intent-Driven Issue Writing
18
+
19
+ Every issue should communicate **why** the work matters and **what** success looks like. This creates a track record of reasoning and gives the assignee room to find better solutions.
20
+
21
+ ### Principles
22
+
23
+ 1. **Lead with intent.** Start with why this work matters. The "Why we need this" section should explain real motivation (business problem, user pain, strategic context) — not restate the title.
24
+ 2. **Describe outcomes.** "Done when" criteria describe what success looks like. The assignee may find a more elegant path than the one you'd prescribe.
25
+ 3. **Respect expertise.** Provide context the assignee might lack (what a tool is, links to docs, business reasoning). Specific instructions are fine when you have relevant domain knowledge — but always pair them with the intent so the assignee understands the goal behind them.
26
+
27
+ ### Example
28
+
29
+ ```markdown
30
+ ## Set up a self-hosted CRM
31
+
32
+ The team needs a CRM to replace fragmented contact tracking
33
+ (spreadsheets + Linear + memory). Should be production-ready:
34
+ reliable, backed up, accessible.
35
+
36
+ ## Why we need this
37
+ As we scale outbound, we need customer relationships, pipeline,
38
+ and outreach tracked in one place.
39
+
40
+ ## Done when
41
+ - Team can access the CRM UI and create/edit contacts.
42
+ - Data survives a server restart.
43
+ - Accessible via a subdomain with TLS.
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Duplicate & Related Issues Check (MANDATORY)
49
+
50
+ **Search before creating. No exceptions.**
51
+
52
+ ```bash
53
+ el-linear issues search "keywords from proposed title" 2>&1
54
+ ```
55
+
56
+ 1. Extract 2–3 key terms from the proposed title (skip generic words).
57
+ 2. Review results — classify each match:
58
+ - Same component + same problem = **duplicate** → comment on existing issue, don't create.
59
+ - Same component + different problem = **related** → create new issue with `--related-to`.
60
+ - Same domain but different component = **related** → create new issue with `--related-to`.
61
+ - Unrelated = proceed without linking.
62
+ 3. If a potential duplicate is found: show the user the existing issue(s), ask whether to comment, mark duplicate, or proceed.
63
+ 4. If related issues are found, **always create the relation** when creating the new issue:
64
+
65
+ ```bash
66
+ el-linear issues create "Title" --team ENG --related-to "ENG-456,ENG-789" ... 2>&1
67
+ ```
68
+
69
+ ### Viewing existing relations
70
+
71
+ ```bash
72
+ el-linear issues related ENG-123 2>&1
73
+ ```
74
+
75
+ Returns all relations (related, blocks, blockedBy, duplicate) with direction, state, and assignee. Use this before creating follow-up work to understand the context around an issue.
76
+
77
+ ### Auto-linking issue references on create/update
78
+
79
+ `el-linear issues create`, `el-linear issues update`, `el-linear comments create`, and `el-linear comments update` run the same auto-linking flow on text being written:
80
+
81
+ 1. **Wrap as markdown links.** Bare identifiers (`ENG-100`, `DESIGN-22`) are rewritten to `[ENG-100](https://linear.app/<workspace>/issue/ENG-100/)`. Skipped inside fenced code blocks, inline backticks, existing markdown links, angle-bracket autolinks, and bare URLs.
82
+ 2. **Validate first.** Identifiers that don't resolve in the workspace (e.g. ISO codes like `ISO-1424`) are left as plain text — no link, no relation.
83
+ 3. **Create sidebar relations** on the parent issue. Default is `related`. Prose keywords upgrade the type:
84
+ - `blocked by X` / `depends on X` / `waiting on X` → `blocks` (X→source)
85
+ - `blocks X` / `prerequisite for X` → `blocks` (source→X)
86
+ - `duplicates X` / `duplicate of X` → `duplicate` (source→X)
87
+ - `duplicated by X` → `duplicate` (X→source)
88
+
89
+ The output includes an `autoLinked` field with `linked`, `skipped`, and `failed` arrays.
90
+
91
+ - A reference is **skipped** when any relation already exists.
92
+ - A reference is **failed** when the identifier doesn't resolve.
93
+ - Pass `--no-auto-link` on `create`/`update` to opt out of both wrapping and sidebar relation creation.
94
+
95
+ To backfill an existing issue:
96
+
97
+ ```bash
98
+ el-linear issues link-references ENG-123 # description only
99
+ el-linear issues link-references ENG-123 --include-comments # description + comments
100
+ el-linear issues link-references ENG-123 --dry-run # preview
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Pre-Work Check (Existing Issues)
106
+
107
+ When starting work on an existing issue (`el-linear issues read ENG-123`):
108
+
109
+ - [ ] **Assignee set** — if missing, ask user and update: `el-linear issues update ENG-123 --assignee <name>`.
110
+ - [ ] **Project set** — if missing, ask user and update: `el-linear issues update ENG-123 --project "<name>"`.
111
+ - [ ] **Status appropriate** — move to "In Progress" or your team's equivalent.
112
+
113
+ Don't start implementation work on an unassigned issue. The assignee is the person accountable.
114
+
115
+ ---
116
+
117
+ ## Pre-Creation Validation Checklist
118
+
119
+ Complete ALL items before creating any issue:
120
+
121
+ - [ ] **Duplicate & related check** — searched for existing issues, linked related ones (above).
122
+ - [ ] **Team** — ask user if unclear (`el-linear teams list`).
123
+ - [ ] **Assignee** — ask user if unclear (`el-linear users list --active`).
124
+ - [ ] **Project** — always ask user, never guess (`el-linear projects list`).
125
+ - [ ] **Labels** — exactly 1 type label + 1–2 domain labels (see Label Taxonomy below).
126
+ - [ ] **Title** — action verb matching the type label (see Title Verb Convention), sentence case, specific scope.
127
+ - [ ] **Description** — 2–4 sentences with context and intent, formatted with **bold** and `inline code`.
128
+ - [ ] **"Why we need this"** — genuine motivation, not a restatement of the title.
129
+
130
+ If any field is missing, **STOP and use AskUserQuestion**.
131
+
132
+ ---
133
+
134
+ ## The `--claude` Delegation Pattern
135
+
136
+ `el-linear issues create` accepts a `--claude` flag that applies the workspace-level "claude" label (configured at `config.labels.workspace.claude`). This is the canonical signal that an issue is **delegated to Claude Code** for autonomous execution.
137
+
138
+ ```bash
139
+ el-linear issues create "Migrate auth middleware to new session store" \
140
+ --team ENG --assignee alice --project "Auth Refactor" \
141
+ --description "..." --claude 2>&1
142
+ ```
143
+
144
+ When you see `--claude` in usage:
145
+
146
+ - **As a writer**: use it to mark issues you want Claude to pick up. The label is the contract; combine it with a clear "Done when" so Claude knows what success looks like.
147
+ - **As Claude**: if you find an issue with the `claude` label, you should treat it as in-scope work for autonomous progress. Search with `el-linear issues search "claude" --status "Todo"`.
148
+ - **As a reviewer**: an issue's `claude` label tells you to weigh whether the description has enough acceptance criteria, since the assignee can't ask follow-up questions in a back-and-forth.
149
+
150
+ The label is plain config — set it to anything you want, or skip it entirely. The flag is just sugar for `--labels claude`.
151
+
152
+ ---
153
+
154
+ ## User @Mentions
155
+
156
+ Reference team members by name in comments. el-linear resolves both explicit `@name` tokens and bare capitalized references to proper Linear mentions.
157
+
158
+ ```bash
159
+ el-linear comments create ENG-123 --body "cc @alice — Bob can you review?" 2>&1
160
+ # Both "@alice" and "Bob" become Linear mentions.
161
+ ```
162
+
163
+ - **Explicit `@name`** — always resolved. Config alias → display name → name (partial, case-insensitive) → API fallback.
164
+ - **Bare capitalized names** — auto-converted by default when they match a configured member. Config-only — no API fallback — so false positives are bounded to your team. Skipped inside inline code, code blocks, and link text. Self-references are skipped.
165
+ - **Opt out** per-invocation with `--no-auto-mention`.
166
+
167
+ Works in comments only (not descriptions).
168
+
169
+ ---
170
+
171
+ ## CLI Syntax Rules
172
+
173
+ Run `el-linear usage` for the full command reference. Non-obvious rules:
174
+
175
+ - **Always append `2>&1`** to capture errors.
176
+ - **Labels are comma-separated** — `--labels "feature,backend"` (not repeated flags).
177
+ - **Singular/plural interchangeable** — `issues`/`issue`, `labels`/`label`, etc.
178
+ - **Subcommand aliases** — `read`/`view`/`get`/`show`, `update`/`edit`/`set`.
179
+ - **`--jq` for GraphQL filtering** — never pipe through `jq` directly (zsh escaping breaks `!=`).
180
+ - **`--raw` flag** strips the `{ data, meta }` wrapper — emits just the array.
181
+
182
+ ### Output format
183
+
184
+ | Command type | Shape |
185
+ |--------------|-------|
186
+ | List | `{ "data": [...], "meta": { "count": N } }` |
187
+ | Single resource | Flat object |
188
+ | Error | `{ "error": "message" }` |
189
+
190
+ ---
191
+
192
+ ## Error Handling
193
+
194
+ 1. **Check for `"error"` key** before accessing fields like `identifier`.
195
+ 2. **Do NOT retry** failed commands — report the error.
196
+ 3. Common errors and fixes:
197
+ - "Label not found" → check `el-linear labels list --team X`.
198
+ - "Label is a group label" → use a child label, not the parent.
199
+ - "Project may not be associated with this issue's team" → fix with `el-linear projects add-team`.
200
+ - "User not found" → check `el-linear users list --active`.
201
+
202
+ ---
203
+
204
+ ## Label Taxonomy
205
+
206
+ ### Type Labels (Required: exactly 1)
207
+
208
+ The default el-linear validation expects one of these:
209
+
210
+ | Label | When |
211
+ |-------|------|
212
+ | `feature` | New functionality. |
213
+ | `bug` | Broken behavior. |
214
+ | `refactor` | Improving code without changing behavior. |
215
+ | `chore` | Maintenance, dependencies, tooling. |
216
+ | `spike` | Research or investigation. |
217
+
218
+ Customize the set per-config: `validation.typeLabels: ["feature", "bug", ...]`. Domain labels (`frontend`, `backend`, etc.) are not enforced — define your own.
219
+
220
+ ### Title Verb Convention
221
+
222
+ Title must start with a verb that matches the type label:
223
+
224
+ - `bug` → Fix, Resolve, Patch, Handle, Address, Correct
225
+ - `feature` → Add, Build, Create, Implement, Enable, Ship, Launch, Design, Wire, Integrate
226
+ - `chore` → Update, Remove, Clean, Migrate, Deploy, Rotate, Configure, Document, Review, Publish, Standardize, Upgrade
227
+ - `spike` → Research, Investigate, Explore, Evaluate, Audit, Benchmark
228
+ - `refactor` → Refactor, Restructure, Extract, Decouple, Simplify
229
+
230
+ ### Rules
231
+
232
+ - **Create missing labels liberally** — `el-linear labels create "my-label" --team ENG`.
233
+ - **`--claude` flag** adds the workspace-level "claude" label automatically.
234
+ - Ask the user to confirm labels if you're unsure about domain.
235
+
236
+ ---
237
+
238
+ ## Term Enforcement (`config.terms`)
239
+
240
+ el-linear can flag misspellings of brand or project names in titles and descriptions. Configure rules in `~/.config/el-linear/config.json`:
241
+
242
+ ```json
243
+ {
244
+ "terms": [
245
+ { "canonical": "Enrich Layer", "reject": ["EnrichLayer", "enrichlayer"] },
246
+ { "canonical": "Linear", "reject": ["linear.app", "Linear App"] },
247
+ { "canonical": "GitHub", "reject": ["Github", "github"] }
248
+ ]
249
+ }
250
+ ```
251
+
252
+ When el-linear finds a rejected token in an issue title or description, it warns (or in `--strict` mode, throws). The check tolerates URLs and file paths — `enrichlayer.com` is allowed even though `enrichlayer` is rejected.
253
+
254
+ If you don't configure any rules, term enforcement is a no-op.
255
+
256
+ ---
257
+
258
+ ## Project Management Gotchas
259
+
260
+ ### Content vs Description
261
+
262
+ Linear projects have two text fields — **both must be populated**:
263
+
264
+ - `description` — short summary (max 255 chars), shown in lists.
265
+ - `content` — full markdown body, shown in main panel.
266
+
267
+ If you only set one, the other shows blank.
268
+
269
+ ### Cross-Team Projects
270
+
271
+ Creating an issue on team X with a project from team Y → "Project not in same team" error. Fix:
272
+
273
+ ```bash
274
+ el-linear projects add-team "Project Name" ENG 2>&1
275
+ ```
276
+
277
+ **Never use raw `projectUpdate` with `teamIds`** — it replaces the entire team list. Always use `projects add-team` / `remove-team`.
278
+
279
+ ### Discovery Before Creation
280
+
281
+ Always check if a project exists before creating: `el-linear projects list --limit 50`.
282
+
283
+ ---
284
+
285
+ ## Git/GitLab/GitHub Integration
286
+
287
+ If your Linear workspace is connected to a code host, status transitions happen automatically:
288
+
289
+ - Creating a PR/MR → linked issue moves to "In Review".
290
+ - Merging a PR/MR → linked issue moves to "Done".
291
+
292
+ No manual status updates needed after PR/MR events.
293
+
294
+ ### Intermediate Deliverable Rule
295
+
296
+ **Any PR/MR whose branch contains an issue ID will auto-close that issue on merge.** This is dangerous for multi-phase issues where a PR delivers only part of the work.
297
+
298
+ **Rule:** if the PR doesn't complete ALL acceptance criteria of the parent issue, create a sub-issue and branch from that. The parent stays open.
299
+
300
+ ```text
301
+ Parent: ENG-100 "Review API design" ← stays open
302
+ └─ Sub: ENG-101 "Write API design doc" ← branch: feature/ENG-101-write-api-design-doc
303
+ PR merges → only ENG-101 closes
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Checklist Progress Tracking
309
+
310
+ When working on an issue with a checklist, check off items as you complete them.
311
+
312
+ 1. Read the issue: `el-linear issues read ENG-123`.
313
+ 2. Update the description with checked items: `el-linear issues update ENG-123 --description "..."`.
314
+ 3. Preserve the rest of the description — only change `- [ ]` to `- [x]`.
315
+ 4. If all items are done, update the status accordingly.
@@ -0,0 +1,46 @@
1
+ {
2
+ "skill_name": "linear-operations",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Create a Linear issue for fixing the login bug on the staging environment",
7
+ "expected_output": "The skill searches for duplicate issues first, selects appropriate labels from the taxonomy, creates an issue with a description containing 'Why we need this' section, and uses el-linear create command",
8
+ "expectations": [
9
+ "Searches for existing issues to avoid duplicates before creating",
10
+ "Selects labels from the known label taxonomy (e.g., bug)",
11
+ "Description includes a 'Why we need this' section",
12
+ "Uses el-linear CLI to create the issue, not raw curl"
13
+ ]
14
+ },
15
+ {
16
+ "id": 2,
17
+ "prompt": "Search Linear for auth-related issues",
18
+ "expected_output": "The skill uses el-linear search to find issues matching auth keywords",
19
+ "expectations": [
20
+ "Uses el-linear search command with appropriate query",
21
+ "Does not attempt to use curl against api.linear.app",
22
+ "Returns formatted results with issue IDs and titles"
23
+ ]
24
+ },
25
+ {
26
+ "id": 3,
27
+ "prompt": "Update DEV-123 to In Progress",
28
+ "expected_output": "The skill uses el-linear update to change the issue status",
29
+ "expectations": [
30
+ "Uses el-linear update command with the issue identifier DEV-123",
31
+ "Sets the status to In Progress",
32
+ "Does not create a new issue"
33
+ ]
34
+ },
35
+ {
36
+ "id": 4,
37
+ "prompt": "Create a git branch for DEV-456",
38
+ "expected_output": "This should NOT trigger linear-operations. Branch creation from Linear issues is handled by git-branch-from-linear skill.",
39
+ "expectations": [
40
+ "Does NOT trigger linear-operations skill",
41
+ "Routes to git-branch-from-linear instead",
42
+ "No el-linear create or update commands are issued"
43
+ ]
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupAttachmentsCommands(program: Command): void;
@@ -0,0 +1,57 @@
1
+ import { getApiToken } from "../utils/auth.js";
2
+ import { FileService } from "../utils/file-service.js";
3
+ import { createGraphQLAttachmentsService } from "../utils/graphql-attachments-service.js";
4
+ import { createLinearService } from "../utils/linear-service.js";
5
+ import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
6
+ export function setupAttachmentsCommands(program) {
7
+ const attachments = program
8
+ .command("attachments")
9
+ .description("Manage file attachments on issues.");
10
+ attachments.action(() => attachments.help());
11
+ attachments
12
+ .command("create <issueId>")
13
+ .description("Upload a file and attach it to an issue.")
14
+ .requiredOption("--file <path>", "path to file to upload")
15
+ .option("--title <title>", "attachment title (defaults to filename)")
16
+ .action(handleAsyncCommand(async (issueId, options, command) => {
17
+ const rootOpts = command.parent.parent.opts();
18
+ const apiToken = getApiToken(rootOpts);
19
+ const linearService = createLinearService(rootOpts);
20
+ const resolvedIssueId = await linearService.resolveIssueId(issueId);
21
+ const fileService = new FileService(apiToken);
22
+ const uploadResult = await fileService.uploadFile(options.file);
23
+ if (!uploadResult.success) {
24
+ throw new Error(uploadResult.error);
25
+ }
26
+ const attachmentsService = createGraphQLAttachmentsService(rootOpts);
27
+ const attachment = await attachmentsService.createAttachment({
28
+ issueId: resolvedIssueId,
29
+ url: uploadResult.assetUrl,
30
+ title: options.title || uploadResult.filename,
31
+ });
32
+ outputSuccess(attachment);
33
+ }));
34
+ attachments
35
+ .command("list <issueId>")
36
+ .description("List attachments on an issue.")
37
+ .option("-l, --limit <number>", "maximum number of attachments", "50")
38
+ .action(handleAsyncCommand(async (issueId, options, command) => {
39
+ const rootOpts = command.parent.parent.opts();
40
+ const linearService = createLinearService(rootOpts);
41
+ const resolvedIssueId = await linearService.resolveIssueId(issueId);
42
+ const attachmentsService = createGraphQLAttachmentsService(rootOpts);
43
+ const allAttachments = await attachmentsService.listAttachments(resolvedIssueId);
44
+ const limit = Number.parseInt(options.limit, 10);
45
+ const data = allAttachments.slice(0, limit);
46
+ outputSuccess({ data, meta: { count: data.length } });
47
+ }));
48
+ attachments
49
+ .command("delete <attachmentId>")
50
+ .description("Delete an attachment.")
51
+ .action(handleAsyncCommand(async (attachmentId, _options, command) => {
52
+ const rootOpts = command.parent.parent.opts();
53
+ const attachmentsService = createGraphQLAttachmentsService(rootOpts);
54
+ await attachmentsService.deleteAttachment(attachmentId);
55
+ outputSuccess({ success: true, message: "Attachment deleted" });
56
+ }));
57
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupBatchCommands(program: Command): void;