@enrichlayer/el-linear 1.29.0 → 1.29.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.
@@ -18,6 +18,7 @@
18
18
  * connection has no top-level state filter).
19
19
  */
20
20
  import { buildIssueTreeQuery, DEFAULT_TREE_DEPTH, MAX_TREE_DEPTH, } from "../../queries/issue-tree.js";
21
+ import { TERMINAL_STATE_TYPES } from "../../types/linear.js";
21
22
  import { formatTree } from "../../utils/format-tree.js";
22
23
  import { createIssuesService } from "../../utils/issues-service-bootstrap.js";
23
24
  import { handleAsyncCommand, outputSuccess } from "../../utils/output.js";
@@ -74,10 +75,11 @@ function parseDepth(raw) {
74
75
  return n;
75
76
  }
76
77
  /**
77
- * Walk the tree depth-first and drop any node whose `state.type` is
78
- * `completed` or `canceled`. Pure function — does not mutate the input.
79
- * A pruned child takes its entire subtree with it (consistent with how
80
- * `issues list --no-include-closed` excludes closed work entirely).
78
+ * Walk the tree depth-first and drop any node whose `state.type` is terminal
79
+ * (`completed` / `canceled` / `duplicate` — `TERMINAL_STATE_TYPES`, DEV-4879).
80
+ * Pure function — does not mutate the input. A pruned child takes its entire
81
+ * subtree with it (consistent with how `issues list --no-include-closed`
82
+ * excludes closed work entirely — same shared constant).
81
83
  *
82
84
  * Assumes Linear's parent → children graph stays single-parent (a tree,
83
85
  * not a DAG). If Linear ever ships multi-parent issues, this recursion
@@ -97,6 +99,5 @@ function pruneTerminalStates(root) {
97
99
  };
98
100
  }
99
101
  function isTerminalState(node) {
100
- const t = node.state?.type;
101
- return t === "completed" || t === "canceled";
102
+ return TERMINAL_STATE_TYPES.includes(node.state?.type ?? "");
102
103
  }
@@ -35,6 +35,17 @@ interface StateRef {
35
35
  */
36
36
  type?: string;
37
37
  }
38
+ /**
39
+ * Workflow-state types treated as terminal / closed for default filtering.
40
+ * `issues list` / `issues search` exclude these unless `--include-closed` (or
41
+ * an explicit `--status`) is passed (DEV-4478).
42
+ *
43
+ * `duplicate` is included (DEV-4879) because this workspace parks resolved
44
+ * duplicates in a `duplicate`-typed state — they carry a `duplicate-of`
45
+ * relation and are semantically closed, so they must not leak into the default
46
+ * open-issues view the way `completed` / `canceled` don't.
47
+ */
48
+ export declare const TERMINAL_STATE_TYPES: readonly string[];
38
49
  interface ProjectRef {
39
50
  id: string;
40
51
  name: string;
@@ -3,4 +3,18 @@
3
3
  * These represent the public API surface — GraphQL responses are
4
4
  * transformed into these shapes at the service boundary.
5
5
  */
6
- export {};
6
+ /**
7
+ * Workflow-state types treated as terminal / closed for default filtering.
8
+ * `issues list` / `issues search` exclude these unless `--include-closed` (or
9
+ * an explicit `--status`) is passed (DEV-4478).
10
+ *
11
+ * `duplicate` is included (DEV-4879) because this workspace parks resolved
12
+ * duplicates in a `duplicate`-typed state — they carry a `duplicate-of`
13
+ * relation and are semantically closed, so they must not leak into the default
14
+ * open-issues view the way `completed` / `canceled` don't.
15
+ */
16
+ export const TERMINAL_STATE_TYPES = [
17
+ "completed",
18
+ "canceled",
19
+ "duplicate",
20
+ ];
@@ -10,6 +10,7 @@
10
10
  * Pure rendering — does not fetch or filter. The depth bound and the
11
11
  * terminal-state exclusion are enforced by the caller before this runs.
12
12
  */
13
+ import { TERMINAL_STATE_TYPES } from "../types/linear.js";
13
14
  const BRANCH_TEE = "├── ";
14
15
  const BRANCH_END = "└── ";
15
16
  const VERTICAL = "│ ";
@@ -40,7 +41,7 @@ function formatStateSuffix(state) {
40
41
  // Only annotate terminal-typed states; the renderer is otherwise
41
42
  // state-name-agnostic so workspace-custom workflow states (e.g. "In
42
43
  // Review") don't get a noisy suffix.
43
- if (state.type === "completed" || state.type === "canceled") {
44
+ if (TERMINAL_STATE_TYPES.includes(state.type)) {
44
45
  return ` [${state.name}]`;
45
46
  }
46
47
  return "";
@@ -42,7 +42,8 @@ export interface SearchIssueArgs {
42
42
  /** Issue states to include (e.g. `["Todo", "In Progress"]`). */
43
43
  status?: string[];
44
44
  /**
45
- * Exclude terminal states (workflow type `completed` or `canceled`).
45
+ * Exclude terminal states (workflow type `completed`, `canceled`, or
46
+ * `duplicate` — see `TERMINAL_STATE_TYPES`).
46
47
  * Default `false` for back-compat at the service layer; the CLI's
47
48
  * `issues list` / `issues search` flip this to `true` by default
48
49
  * (DEV-4478) and accept `--include-closed` to opt back in. When
@@ -2,6 +2,7 @@ import { isRegistryConfigured, resolveViaRegistry, } from "../config/registry-re
2
2
  import { resolveUserDisplayName } from "../config/resolver.js";
3
3
  import { ARCHIVE_ISSUE_MUTATION, BATCH_GET_ISSUES_QUERY, BATCH_RESOLVE_FOR_CREATE_QUERY, BATCH_RESOLVE_FOR_SEARCH_QUERY, BATCH_RESOLVE_FOR_UPDATE_QUERY, buildResolveLabelsByNameQuery, CREATE_ISSUE_MUTATION, DELETE_ISSUE_MUTATION, FILTERED_SEARCH_ISSUES_QUERY, GET_ISSUE_BY_ID_QUERY, GET_ISSUE_BY_IDENTIFIER_QUERY, GET_ISSUE_CLAIM_CONTEXT_QUERY, GET_ISSUE_START_CONTEXT_QUERY, GET_ISSUE_TEAM_QUERY, GET_ISSUES_QUERY, SEARCH_ISSUES_QUERY, TEAM_STARTED_STATUSES_QUERY, UPDATE_ISSUE_MUTATION, } from "../queries/issues.js";
4
4
  import { CREATE_LABEL_MUTATION } from "../queries/labels.js";
5
+ import { TERMINAL_STATE_TYPES } from "../types/linear.js";
5
6
  import { toISOStringOrNow } from "./date-format.js";
6
7
  import { extractEmbeds } from "./embed-parser.js";
7
8
  import { multipleMatchesError, notFoundError } from "./error-messages.js";
@@ -981,7 +982,7 @@ export class GraphQLIssuesService {
981
982
  else if (filters.excludeTerminalStates) {
982
983
  // Implicit terminal-state exclusion (DEV-4478). Explicit `status`
983
984
  // always wins — the user already named the states they want.
984
- filtered = filtered.filter((issue) => issue.state?.type !== "completed" && issue.state?.type !== "canceled");
985
+ filtered = filtered.filter((issue) => !TERMINAL_STATE_TYPES.includes(issue.state?.type ?? ""));
985
986
  }
986
987
  if (filters.labelNames && filters.labelNames.length > 0) {
987
988
  const lowerNames = filters.labelNames.map((n) => n.toLowerCase());
@@ -1024,7 +1025,7 @@ export class GraphQLIssuesService {
1024
1025
  else if (filters.excludeTerminalStates) {
1025
1026
  // Implicit terminal-state exclusion (DEV-4478). Explicit `status`
1026
1027
  // always wins — the user already named the states they want.
1027
- filter.state = { type: { nin: ["completed", "canceled"] } };
1028
+ filter.state = { type: { nin: [...TERMINAL_STATE_TYPES] } };
1028
1029
  }
1029
1030
  if (filters.labelNames && filters.labelNames.length > 0) {
1030
1031
  if (filters.labelNames.length === 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enrichlayer/el-linear",
3
- "version": "1.29.0",
3
+ "version": "1.29.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",