@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
|
-
|
|
69
|
-
|
|
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.
|
|
79
|
+
if (options.attachedTo) {
|
|
79
80
|
const attachmentsService = await createGraphQLAttachmentsService(rootOpts);
|
|
80
|
-
const issueId = await linearService.resolveIssueId(options.
|
|
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
|
|
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 (
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
:
|
|
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.
|
|
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",
|