@enrichlayer/el-linear 1.44.0 → 1.44.2
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.
|
@@ -99,6 +99,38 @@ el-linear issues read DEV-123 --format json 2>&1 | python3 -c "import json,sys;
|
|
|
99
99
|
|
|
100
100
|
`--body` is mutually exclusive with `--field` / `--sections` / `--with` (those extract named parts or extend the JSON envelope; `--body` is the whole thing as text).
|
|
101
101
|
|
|
102
|
+
#### Citing an issue's stated rationale: `--body` or `--field`, never `--format summary`
|
|
103
|
+
|
|
104
|
+
**`--format summary` truncates the description.** That is correct for scanning a board and wrong the moment you quote, cite, or reason from what an issue *says*. A research pass once read an issue with `--format summary`, saw a truncated description, and published the opposite of the design position that issue states outright — the fix was one command with a different flag, run twenty minutes too late.
|
|
105
|
+
|
|
106
|
+
So: the moment a claim is about an issue's **stated rationale** — what a decision claimed at the time, why an approach was chosen, what a spec requires — read `--body` (or `--field <section>` for one section) and quote from that.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# ✅ Reasoning from what the issue actually says
|
|
110
|
+
el-linear issues read DEV-123 --body 2>&1
|
|
111
|
+
el-linear issues read DEV-123 --field "Why we need this" 2>&1
|
|
112
|
+
|
|
113
|
+
# ❌ Citing a summary — the description you are quoting may be cut off
|
|
114
|
+
el-linear issues read DEV-123 --format summary 2>&1
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The scope is narrow and worth stating precisely: an issue body is a weak source for *system behavior* — it records what someone intended, not what the code does — but it is the **authoritative** artifact for what a decision claimed at the time. Use it for the second, not the first.
|
|
118
|
+
|
|
119
|
+
#### An umbrella's status is not delivery evidence
|
|
120
|
+
|
|
121
|
+
A parent or umbrella issue's own status says nothing reliable about whether the work shipped. Read its children and slices, then read the artifact.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
el-linear issues related DEV-100 --format summary 2>&1
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Neither direction is safe on its own:
|
|
128
|
+
|
|
129
|
+
- **Canceled does not mean abandoned.** An umbrella is routinely closed as bookkeeping after its slices land — one was reported as "abandoned" in a research document while all six of its children were Done and the code was in production.
|
|
130
|
+
- **Done children do not prove delivery either.** A child can be a duplicate, a rename, or an administrative closure.
|
|
131
|
+
|
|
132
|
+
The tracker is a lead. The **merged MR or the deployed code** is the evidence — go look at it before writing "shipped" or "abandoned".
|
|
133
|
+
|
|
102
134
|
### Comment reads and full comment bodies
|
|
103
135
|
|
|
104
136
|
When you need a specific comment, or the full text of a long comment, use the
|
|
@@ -185,6 +217,14 @@ Every issue should communicate **why** the work matters and **what** success loo
|
|
|
185
217
|
### Example
|
|
186
218
|
|
|
187
219
|
```markdown
|
|
220
|
+
## Intake decision
|
|
221
|
+
- Needed: Yes — contact tracking is split across spreadsheets, Linear, and memory.
|
|
222
|
+
- Worth doing: Yes — outbound scale makes the fragmentation costly now, not later.
|
|
223
|
+
- Existing work: searched "CRM", "contact tracking" with --include-closed; no duplicate.
|
|
224
|
+
- Owner: Nico (operations tooling).
|
|
225
|
+
- Placement: OPS / Sales infrastructure; self-hosted on the Hetzner box.
|
|
226
|
+
- Decision: PROCEED
|
|
227
|
+
|
|
188
228
|
## Set up a self-hosted CRM
|
|
189
229
|
|
|
190
230
|
The team needs a CRM to replace fragmented contact tracking
|
|
@@ -203,6 +243,26 @@ and outreach tracked in one place.
|
|
|
203
243
|
|
|
204
244
|
---
|
|
205
245
|
|
|
246
|
+
## Intake Decision Block (MANDATORY, blocking)
|
|
247
|
+
|
|
248
|
+
**`el-linear issues create` refuses any description without a `## Intake decision` section.** It must open the description, and the six lines must appear in this order:
|
|
249
|
+
|
|
250
|
+
```markdown
|
|
251
|
+
## Intake decision
|
|
252
|
+
- Needed: Yes — <why this is needed>
|
|
253
|
+
- Worth doing: Yes — <why the value exceeds the cost>
|
|
254
|
+
- Existing work: <duplicate/search result and evidence>
|
|
255
|
+
- Owner: <canonical owner or source of truth>
|
|
256
|
+
- Placement: <team/project/repository/document path>
|
|
257
|
+
- Decision: PROCEED
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Write it **before** composing the rest of the body. The gate runs before the create POST, so omitting it fails the call outright — nothing is written, and a finished issue body then has to be reassembled around a section you were never told to include.
|
|
261
|
+
|
|
262
|
+
The point is that the decision is *recorded*, not merely reached: `Existing work` cites the search you actually ran, and `Owner`/`Placement` name a concrete destination rather than a plausible one. It is the same discipline the two gates below enforce mechanically, applied to the judgment they cannot check.
|
|
263
|
+
|
|
264
|
+
Escape hatch: `--allow-missing-intake-decision`, which is recorded. It exists for an **accountable human** who has approved an exceptional create — not for an agent that would rather not write the block. `--skip-validation` also bypasses it but disables every other field check too, so prefer the narrow flag.
|
|
265
|
+
|
|
206
266
|
## Duplicate & Related Issues Check (MANDATORY)
|
|
207
267
|
|
|
208
268
|
**Search before creating. No exceptions.**
|
|
@@ -374,6 +434,7 @@ Don't start implementation work on an unassigned issue — the assignee is the p
|
|
|
374
434
|
|
|
375
435
|
Complete ALL items before creating any issue:
|
|
376
436
|
|
|
437
|
+
- [ ] **Intake decision** — description opens with the `## Intake decision` block (above). Blocking.
|
|
377
438
|
- [ ] **Duplicate & related check** — searched for existing issues, linked related ones (above).
|
|
378
439
|
- [ ] **Team** — ask user if unclear (`el-linear teams list`).
|
|
379
440
|
- [ ] **Assignee** — ask user if unclear (`el-linear users list --active`).
|
|
@@ -63,7 +63,11 @@ const WRAPPED_IN_EMPHASIS = /^([*_]{1,3})([^*_].*?)\1$/;
|
|
|
63
63
|
const INLINE_EMPHASIS = /(^|[^\p{L}\p{N}])([*_]{1,3})(?=\S)(.+?\S)\2(?=$|[^\p{L}\p{N}])/gu;
|
|
64
64
|
const PLACEHOLDER = /^(?:tbd|todo|unknown|n\/?a|none|unsure|not decided|-)\.?$/i;
|
|
65
65
|
const NON_SPECIFIC = /^(?:yes|no)$/i;
|
|
66
|
-
|
|
66
|
+
// The gate exists to force an explicit verdict AND a reason. Which punctuation
|
|
67
|
+
// joins the two carries no meaning, so every ordinary separator is accepted —
|
|
68
|
+
// including `.`, which reads most naturally when the reason is a full sentence.
|
|
69
|
+
// A bare verdict still fails: the trailing group requires real reason text.
|
|
70
|
+
const AFFIRMATIVE_WITH_REASON = /^yes\s*(?:[-–—.:;,]|because)\s*(\S.{2,})$/i;
|
|
67
71
|
function labelOf(key) {
|
|
68
72
|
return FIELD_DEFINITIONS.find((field) => field.key === key)?.label ?? key;
|
|
69
73
|
}
|
|
@@ -252,7 +256,7 @@ function describeFieldProblem(field, problem, value) {
|
|
|
252
256
|
case "non-specific":
|
|
253
257
|
return `The intake field "${field}" reads "${quote(value)}", which is too short or too generic to be a specific answer.`;
|
|
254
258
|
case "no-judgment":
|
|
255
|
-
return `The intake field "${field}" reads "${quote(value)}" — the
|
|
259
|
+
return `The intake field "${field}" reads "${quote(value)}" — the verdict is there, but no reason follows it. Write both together, e.g. "Yes — <reason>" or "Yes. <reason>"; any ordinary separator works.`;
|
|
256
260
|
case "placeholder-reason":
|
|
257
261
|
return `The intake field "${field}" says Yes but gives the placeholder reason "${quote(value)}"; record the real reason.`;
|
|
258
262
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enrichlayer/el-linear",
|
|
3
|
-
"version": "1.44.
|
|
3
|
+
"version": "1.44.2",
|
|
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",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@inquirer/prompts": "^8.4.2",
|
|
55
|
-
"@linear/sdk": "^
|
|
55
|
+
"@linear/sdk": "^88.0.0",
|
|
56
56
|
"commander": "^15.0.0",
|
|
57
57
|
"picocolors": "^1.1.1"
|
|
58
58
|
},
|