@enrichlayer/el-linear 1.44.0 → 1.44.1

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.
@@ -185,6 +185,14 @@ Every issue should communicate **why** the work matters and **what** success loo
185
185
  ### Example
186
186
 
187
187
  ```markdown
188
+ ## Intake decision
189
+ - Needed: Yes — contact tracking is split across spreadsheets, Linear, and memory.
190
+ - Worth doing: Yes — outbound scale makes the fragmentation costly now, not later.
191
+ - Existing work: searched "CRM", "contact tracking" with --include-closed; no duplicate.
192
+ - Owner: Nico (operations tooling).
193
+ - Placement: OPS / Sales infrastructure; self-hosted on the Hetzner box.
194
+ - Decision: PROCEED
195
+
188
196
  ## Set up a self-hosted CRM
189
197
 
190
198
  The team needs a CRM to replace fragmented contact tracking
@@ -203,6 +211,26 @@ and outreach tracked in one place.
203
211
 
204
212
  ---
205
213
 
214
+ ## Intake Decision Block (MANDATORY, blocking)
215
+
216
+ **`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:
217
+
218
+ ```markdown
219
+ ## Intake decision
220
+ - Needed: Yes — <why this is needed>
221
+ - Worth doing: Yes — <why the value exceeds the cost>
222
+ - Existing work: <duplicate/search result and evidence>
223
+ - Owner: <canonical owner or source of truth>
224
+ - Placement: <team/project/repository/document path>
225
+ - Decision: PROCEED
226
+ ```
227
+
228
+ 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.
229
+
230
+ 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.
231
+
232
+ 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.
233
+
206
234
  ## Duplicate & Related Issues Check (MANDATORY)
207
235
 
208
236
  **Search before creating. No exceptions.**
@@ -374,6 +402,7 @@ Don't start implementation work on an unassigned issue — the assignee is the p
374
402
 
375
403
  Complete ALL items before creating any issue:
376
404
 
405
+ - [ ] **Intake decision** — description opens with the `## Intake decision` block (above). Blocking.
377
406
  - [ ] **Duplicate & related check** — searched for existing issues, linked related ones (above).
378
407
  - [ ] **Team** — ask user if unclear (`el-linear teams list`).
379
408
  - [ ] **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
- const AFFIRMATIVE_WITH_REASON = /^yes\s*(?:[-—:;,]|because)\s*(\S.{2,})$/i;
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 content is there, but it is not an explicit "Yes — <reason>" judgment.`;
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.0",
3
+ "version": "1.44.1",
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": "^87.0.0",
55
+ "@linear/sdk": "^88.0.0",
56
56
  "commander": "^15.0.0",
57
57
  "picocolors": "^1.1.1"
58
58
  },