@enrichlayer/el-linear 1.38.1 → 1.39.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.
@@ -371,7 +371,7 @@ Complete ALL items before creating any issue:
371
371
  - [ ] **Assignee** — ask user if unclear (`el-linear users list --active`).
372
372
  - [ ] **Project** — always ask user, never guess (`el-linear projects list`).
373
373
  - [ ] **Labels** — exactly 1 type label + 1–2 domain labels (see Label Taxonomy below).
374
- - [ ] **Title** — action verb matching the type label (see Title Verb Convention), sentence case, specific scope.
374
+ - [ ] **Title** — action verb matching the type label (see Title Verb Convention), sentence case, specific scope, **plain-language and jargon-free** (see Title Readability).
375
375
  - [ ] **Description** — 2–4 sentences with context and intent, formatted with **bold** and `inline code`.
376
376
  - [ ] **"Why we need this"** — genuine motivation, not a restatement of the title.
377
377
 
@@ -497,6 +497,27 @@ Title must start with a verb that matches the type label:
497
497
  - `spike` → Research, Investigate, Explore, Evaluate, Audit, Benchmark
498
498
  - `refactor` → Refactor, Restructure, Extract, Decouple, Simplify
499
499
 
500
+ ### Title Readability — plain language, jargon in the body
501
+
502
+ **The title says what problem is being solved, in words a non-author can read.** The title is the shared surface — a teammate scanning the board, a manager triaging priority, a future picker deciding what to pick up. If it only parses for the person who wrote it that week, that whole audience is locked out. This applies to **MR/PR titles too** — they front the same board.
503
+
504
+ **Keep the mechanism detail — function names, env-var constants, symbol soup — in the description, not the title.** Moving it into the body doesn't lose precision; it puts precision where the reader who opens the issue actually wants it. The title carries the *problem*; the body carries the *how*.
505
+
506
+ - **Lead with the problem or outcome**, not the internal symbol at the center of it.
507
+ - **Move code identifiers, env-var constants, and file/function names into the body**, under a `## …` heading where they read as helpful context.
508
+ - **Proper nouns that ARE the clearest name stay.** The name of a tool, service, or protocol a teammate would recognize (a CLI name, `Vault`, `CI`, `OAuth`) is not jargon — don't paraphrase it into vagueness. The test is "would a teammate recognize this?", not "does it contain a lowercase token?".
509
+ - **Don't overcorrect into mush.** "Fix the thing that was broken" is worse than a jargon title — specificity still matters, just express it in problem terms.
510
+
511
+ Before → after:
512
+
513
+ | ❌ Jargon title | ✅ Plain-language title |
514
+ |---|---|
515
+ | `parseTokenBucket drops refill when lastRefillTs is unset` | Fix rate limiter losing its refill allowance after an idle period |
516
+ | `AUTH_SESSION_TTL mismatch logs out users early in refreshSession` | Fix users getting logged out before their session length expires |
517
+ | `Extract validateEntry into shared pkg (3 hand-rolled copies)` | Extract the duplicated entry validator into a shared package |
518
+
519
+ The mechanism (`parseTokenBucket`, `AUTH_SESSION_TTL`, `refreshSession`, the three copies) still gets stated — in the **description**, where it reads as context instead of a barrier.
520
+
500
521
  ### Rules
501
522
 
502
523
  - **Create missing labels liberally** — `el-linear labels create "my-label" --team ENG`.
@@ -65,8 +65,9 @@ async function handleCreateDocument(options, command) {
65
65
  outputSuccess(document);
66
66
  }
67
67
  async function handleListDocuments(options, command) {
68
- if (options.project && options.issue) {
69
- throw new Error("Cannot use --project and --issue together. Choose one filter.");
68
+ const filters = [options.project, options.issue, options.attachedTo].filter(Boolean);
69
+ if (filters.length > 1) {
70
+ throw new Error("Cannot combine --project, --issue, and --attached-to. Choose one filter.");
70
71
  }
71
72
  const rootOpts = getRootOpts(command);
72
73
  const documentsService = await createGraphQLDocumentsService(rootOpts);
@@ -75,9 +76,9 @@ async function handleListDocuments(options, command) {
75
76
  if (Number.isNaN(limit) || limit < 1) {
76
77
  throw new Error(`Invalid limit "${options.limit}": must be a positive number`);
77
78
  }
78
- if (options.issue) {
79
+ if (options.attachedTo) {
79
80
  const attachmentsService = await createGraphQLAttachmentsService(rootOpts);
80
- const issueId = await linearService.resolveIssueId(options.issue);
81
+ const issueId = await linearService.resolveIssueId(options.attachedTo);
81
82
  const attachments = await attachmentsService.listAttachments(issueId);
82
83
  const documentSlugIds = [
83
84
  ...new Set(attachments
@@ -96,8 +97,13 @@ async function handleListDocuments(options, command) {
96
97
  if (options.project) {
97
98
  projectId = await linearService.resolveProjectId(options.project);
98
99
  }
100
+ let issueId;
101
+ if (options.issue) {
102
+ issueId = await linearService.resolveIssueId(options.issue);
103
+ }
99
104
  const docs = await documentsService.listDocuments({
100
105
  projectId,
106
+ issueId,
101
107
  first: limit,
102
108
  });
103
109
  outputSuccess({ data: docs, meta: { count: docs.length } });
@@ -119,8 +125,8 @@ export function setupDocumentsCommands(program) {
119
125
  .option("--team <team>", "team key or name")
120
126
  .option("--icon <icon>", "document icon")
121
127
  .option("--color <color>", "icon color")
122
- .option("--issue <issue>", "link document to issue (e.g., ABC-123)")
123
- .option("--attach-to <issue>", "also attach document to issue (e.g., ABC-123)")
128
+ .option("--issue <issue>", "link document directly to issue (e.g., ABC-123)")
129
+ .option("--attach-to <issue>", "also create a URL attachment on issue (e.g., ABC-123)")
124
130
  .action(handleAsyncCommand(handleCreateDocument));
125
131
  documents
126
132
  .command("update <documentId>")
@@ -164,7 +170,8 @@ export function setupDocumentsCommands(program) {
164
170
  .command("list")
165
171
  .description("List documents")
166
172
  .option("--project <project>", "filter by project name or ID")
167
- .option("--issue <issue>", "filter by issue (shows documents attached to the issue)")
173
+ .option("--issue <issue>", "filter by direct issue link (set by documents create --issue)")
174
+ .option("--attached-to <issue>", "filter by URL attachments (set by documents create --attach-to)")
168
175
  .option("-l, --limit <limit>", "maximum number of documents", "50")
169
176
  .action(handleAsyncCommand(handleListDocuments));
170
177
  documents
@@ -9,6 +9,7 @@ declare class GraphQLDocumentsService {
9
9
  getDocument(id: string): Promise<LinearDocument>;
10
10
  listDocuments(options?: {
11
11
  projectId?: string;
12
+ issueId?: string;
12
13
  first?: number;
13
14
  }): Promise<LinearDocument[]>;
14
15
  deleteDocument(id: string): Promise<boolean>;
@@ -55,9 +55,13 @@ class GraphQLDocumentsService {
55
55
  return transformDocument(result.document);
56
56
  }
57
57
  async listDocuments(options) {
58
- const filter = options?.projectId
59
- ? { project: { id: { eq: options.projectId } } }
60
- : undefined;
58
+ let filter;
59
+ if (options?.projectId) {
60
+ filter = { project: { id: { eq: options.projectId } } };
61
+ }
62
+ else if (options?.issueId) {
63
+ filter = { issue: { id: { eq: options.issueId } } };
64
+ }
61
65
  const result = await this.graphqlService.rawRequest(LIST_DOCUMENTS_QUERY, {
62
66
  first: options?.first ?? 50,
63
67
  filter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enrichlayer/el-linear",
3
- "version": "1.38.1",
3
+ "version": "1.39.0",
4
4
  "description": "A pragmatic CLI for Linear.app — deterministic team/label/member resolution, structured issue validation, configurable term enforcement, and a GraphQL escape hatch.",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",