linmux 0.1.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -0
  3. package/bin/run.js +4 -0
  4. package/dist/commands/comment/create.js +94 -0
  5. package/dist/commands/comment/delete.js +74 -0
  6. package/dist/commands/comment/list.js +84 -0
  7. package/dist/commands/comment/update.js +80 -0
  8. package/dist/commands/cycle/current.js +78 -0
  9. package/dist/commands/cycle/list.js +84 -0
  10. package/dist/commands/cycle/move.js +91 -0
  11. package/dist/commands/describe.js +65 -0
  12. package/dist/commands/graphql/index.js +92 -0
  13. package/dist/commands/install-skill.js +54 -0
  14. package/dist/commands/issue/archive.js +75 -0
  15. package/dist/commands/issue/create.js +115 -0
  16. package/dist/commands/issue/get.js +84 -0
  17. package/dist/commands/issue/list.js +93 -0
  18. package/dist/commands/issue/purge.js +81 -0
  19. package/dist/commands/issue/search.js +109 -0
  20. package/dist/commands/issue/transition.js +91 -0
  21. package/dist/commands/issue/trash.js +75 -0
  22. package/dist/commands/issue/update.js +126 -0
  23. package/dist/commands/label/create.js +91 -0
  24. package/dist/commands/label/list.js +76 -0
  25. package/dist/commands/list-tools.js +47 -0
  26. package/dist/commands/me.js +71 -0
  27. package/dist/commands/project/create.js +101 -0
  28. package/dist/commands/project/get.js +83 -0
  29. package/dist/commands/project/list.js +75 -0
  30. package/dist/commands/project/update-status.js +99 -0
  31. package/dist/commands/project/update.js +99 -0
  32. package/dist/commands/raw/batch.js +85 -0
  33. package/dist/commands/raw/index.js +72 -0
  34. package/dist/commands/schema.js +69 -0
  35. package/dist/commands/state/list.js +77 -0
  36. package/dist/commands/team/get.js +73 -0
  37. package/dist/commands/team/list.js +73 -0
  38. package/dist/commands/whoami.js +71 -0
  39. package/dist/commands/workspace/add.js +97 -0
  40. package/dist/commands/workspace/list.js +47 -0
  41. package/dist/commands/workspace/remove.js +63 -0
  42. package/dist/commands/workspace/replace-token.js +89 -0
  43. package/dist/commands/workspace/use.js +54 -0
  44. package/dist/core/client/factory.js +28 -0
  45. package/dist/core/client/index.js +2 -0
  46. package/dist/core/config/index.js +4 -0
  47. package/dist/core/config/paths.js +30 -0
  48. package/dist/core/config/schema.js +36 -0
  49. package/dist/core/config/store.js +149 -0
  50. package/dist/core/errors/error.js +142 -0
  51. package/dist/core/errors/exit-codes.js +70 -0
  52. package/dist/core/output/envelope.js +53 -0
  53. package/dist/core/output/format.js +42 -0
  54. package/dist/core/output/index.js +3 -0
  55. package/dist/core/pagination/flags.js +29 -0
  56. package/dist/core/pagination/index.js +2 -0
  57. package/dist/core/projection/presets.js +116 -0
  58. package/dist/core/projection/project.js +282 -0
  59. package/dist/core/redact/redact.js +45 -0
  60. package/dist/core/resolvers/cycle.js +60 -0
  61. package/dist/core/resolvers/index.js +7 -0
  62. package/dist/core/resolvers/label.js +54 -0
  63. package/dist/core/resolvers/project-status.js +42 -0
  64. package/dist/core/resolvers/project.js +43 -0
  65. package/dist/core/resolvers/state.js +46 -0
  66. package/dist/core/resolvers/team.js +50 -0
  67. package/dist/core/transport/fetch-interceptor.js +109 -0
  68. package/dist/core/transport/index.js +3 -0
  69. package/dist/core/transport/rate-limit.js +167 -0
  70. package/dist/core/workspace/resolver.js +70 -0
  71. package/dist/core/workspace/write-guard.js +43 -0
  72. package/dist/generated/graphql.js +89428 -0
  73. package/dist/generated/operations.js +3013 -0
  74. package/dist/lib/comment-create-runtime.js +96 -0
  75. package/dist/lib/comment-delete-runtime.js +46 -0
  76. package/dist/lib/comment-list-runtime.js +182 -0
  77. package/dist/lib/comment-update-runtime.js +93 -0
  78. package/dist/lib/cycle-current-runtime.js +90 -0
  79. package/dist/lib/cycle-list-runtime.js +151 -0
  80. package/dist/lib/cycle-move-runtime.js +142 -0
  81. package/dist/lib/describe-runtime.js +180 -0
  82. package/dist/lib/filter-heuristics.js +59 -0
  83. package/dist/lib/graphql-runtime.js +202 -0
  84. package/dist/lib/include-fragments.js +73 -0
  85. package/dist/lib/install-skill-runtime.js +228 -0
  86. package/dist/lib/introspection-registry.js +488 -0
  87. package/dist/lib/issue-archive-runtime.js +89 -0
  88. package/dist/lib/issue-create-runtime.js +175 -0
  89. package/dist/lib/issue-get-runtime.js +153 -0
  90. package/dist/lib/issue-list-runtime.js +164 -0
  91. package/dist/lib/issue-purge-runtime.js +89 -0
  92. package/dist/lib/issue-search-runtime.js +114 -0
  93. package/dist/lib/issue-transition-runtime.js +131 -0
  94. package/dist/lib/issue-trash-runtime.js +84 -0
  95. package/dist/lib/issue-update-runtime.js +164 -0
  96. package/dist/lib/label-create-runtime.js +113 -0
  97. package/dist/lib/label-list-runtime.js +97 -0
  98. package/dist/lib/levenshtein.js +42 -0
  99. package/dist/lib/list-tools-runtime.js +38 -0
  100. package/dist/lib/me-runtime.js +55 -0
  101. package/dist/lib/project-create-runtime.js +103 -0
  102. package/dist/lib/project-get-runtime.js +134 -0
  103. package/dist/lib/project-list-runtime.js +84 -0
  104. package/dist/lib/project-update-runtime.js +110 -0
  105. package/dist/lib/project-update-status-runtime.js +91 -0
  106. package/dist/lib/raw-batch-runtime.js +229 -0
  107. package/dist/lib/raw-runtime.js +171 -0
  108. package/dist/lib/schema-loader.js +41 -0
  109. package/dist/lib/schema-runtime.js +65 -0
  110. package/dist/lib/state-list-runtime.js +93 -0
  111. package/dist/lib/team-get-runtime.js +55 -0
  112. package/dist/lib/team-list-runtime.js +52 -0
  113. package/dist/lib/workspace-runtime.js +112 -0
  114. package/dist/operations/_registry.zod.js +5337 -0
  115. package/oclif.manifest.json +3631 -0
  116. package/package.json +99 -0
  117. package/schema.graphql +30772 -0
  118. package/skills/linmux/SKILL.md +186 -0
@@ -0,0 +1,89 @@
1
+ import { LinearAgentError } from "../core/errors/error.js";
2
+ import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
3
+ import { withRateLimitRetry } from "../core/transport/rate-limit.js";
4
+ import "../core/transport/index.js";
5
+ import { createLinearClient } from "../core/client/factory.js";
6
+ import "../core/client/index.js";
7
+ import { loadConfig } from "../core/config/store.js";
8
+ import "../core/config/index.js";
9
+ import { resolveWorkspace } from "../core/workspace/resolver.js";
10
+ import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
11
+ import { ISSUE_IDENTIFIER_RE, UUID_RE } from "./filter-heuristics.js";
12
+ //#region src/lib/issue-archive-runtime.ts
13
+ async function issueArchiveRuntime(input) {
14
+ const config = (input.loadConfigOverride ?? loadConfig)();
15
+ const envForResolver = {};
16
+ if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
17
+ if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
18
+ const resolved = resolveWorkspace({
19
+ flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
20
+ env: envForResolver,
21
+ config
22
+ });
23
+ requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
24
+ const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
25
+ return withFetchInterception(async () => {
26
+ const ref = input.args.identifier;
27
+ const { id, identifier } = await resolveIssueRef(client, ref, input.retryOptsOverride);
28
+ const payload = await withRateLimitRetry(() => client.archiveIssue(id), input.retryOptsOverride);
29
+ if (!payload.success) throw LinearAgentError.linear.apiError({
30
+ message: "archiveIssue returned success=false",
31
+ details: { lastSyncId: payload.lastSyncId }
32
+ });
33
+ const data = {
34
+ id,
35
+ archived: true
36
+ };
37
+ if (identifier !== void 0) data.identifier = identifier;
38
+ const complexity = getLastComplexity();
39
+ return {
40
+ data,
41
+ meta: {
42
+ workspace: resolved.name,
43
+ workspaceSource: resolved.source,
44
+ ...complexity !== void 0 ? { complexity } : {}
45
+ }
46
+ };
47
+ });
48
+ }
49
+ /**
50
+ * Resolve a user-typed issue ref (ENG-123 or UUID) to a UUID + identifier.
51
+ * UUID path is a no-op (no SDK round-trip); identifier path uses the
52
+ * team-key + number filter. Inlined per the plan note: each runtime in this
53
+ * plan keeps its own resolution helper for cross-plan independence.
54
+ */
55
+ async function resolveIssueRef(client, ref, retryOpts) {
56
+ if (UUID_RE.test(ref)) return { id: ref };
57
+ const m = ref.match(ISSUE_IDENTIFIER_RE);
58
+ if (!m) throw new LinearAgentError({
59
+ code: "ISSUE_NOT_FOUND",
60
+ message: `invalid issue ref: ${ref}`,
61
+ details: { ref }
62
+ });
63
+ const teamKey = m[1].toUpperCase();
64
+ const number = Number(m[2]);
65
+ const issue = (await withRateLimitRetry(() => client.issues({
66
+ filter: {
67
+ team: { key: { eq: teamKey } },
68
+ number: { eq: number }
69
+ },
70
+ first: 1
71
+ }), retryOpts)).nodes[0];
72
+ if (!issue) throw new LinearAgentError({
73
+ code: "ISSUE_NOT_FOUND",
74
+ message: `issue not found: ${ref}`,
75
+ details: { ref }
76
+ });
77
+ const idRaw = issue.id;
78
+ if (typeof idRaw !== "string") throw new LinearAgentError({
79
+ code: "ISSUE_NOT_FOUND",
80
+ message: `issue not found: ${ref}`,
81
+ details: { ref }
82
+ });
83
+ const idRes = { id: idRaw };
84
+ const idn = issue.identifier;
85
+ if (typeof idn === "string") idRes.identifier = idn;
86
+ return idRes;
87
+ }
88
+ //#endregion
89
+ export { issueArchiveRuntime };
@@ -0,0 +1,175 @@
1
+ import { LinearAgentError } from "../core/errors/error.js";
2
+ import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
3
+ import { withRateLimitRetry } from "../core/transport/rate-limit.js";
4
+ import "../core/transport/index.js";
5
+ import { createLinearClient } from "../core/client/factory.js";
6
+ import "../core/client/index.js";
7
+ import { loadConfig } from "../core/config/store.js";
8
+ import "../core/config/index.js";
9
+ import { parseFields, project } from "../core/projection/project.js";
10
+ import { resolveWorkspace } from "../core/workspace/resolver.js";
11
+ import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
12
+ import { EMAIL_RE, ISSUE_IDENTIFIER_RE, UUID_RE } from "./filter-heuristics.js";
13
+ import { resolveCycleId } from "../core/resolvers/cycle.js";
14
+ import { resolveLabelIds } from "../core/resolvers/label.js";
15
+ import { resolveProjectId } from "../core/resolvers/project.js";
16
+ import { resolveStateNameToId } from "../core/resolvers/state.js";
17
+ import { resolveTeamId } from "../core/resolvers/team.js";
18
+ import "../core/resolvers/index.js";
19
+ //#region src/lib/issue-create-runtime.ts
20
+ async function issueCreateRuntime(input) {
21
+ const config = (input.loadConfigOverride ?? loadConfig)();
22
+ const envForResolver = {};
23
+ if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
24
+ if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
25
+ const resolved = resolveWorkspace({
26
+ flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
27
+ env: envForResolver,
28
+ config
29
+ });
30
+ requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
31
+ if (input.flags.title === void 0 || input.flags.title === "") throw LinearAgentError.usage("--title is required");
32
+ if (input.flags.team === void 0 || input.flags.team === "") throw LinearAgentError.usage("--team is required");
33
+ const title = input.flags.title;
34
+ const teamRef = input.flags.team;
35
+ const fields = parseFields(input.flags.fields ?? "defaults", "issue");
36
+ const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
37
+ return withFetchInterception(async () => {
38
+ const workspaceKey = resolved.name ?? "_api-key-env_";
39
+ const teamId = await resolveTeamId(client, workspaceKey, teamRef, input.retryOptsOverride);
40
+ const flags = input.flags;
41
+ const [stateId, labelIds, projectId, cycleId, parentId, assigneeId] = await Promise.all([
42
+ flags.state !== void 0 ? resolveStateNameToId(client, workspaceKey, teamId, flags.state, input.retryOptsOverride) : Promise.resolve(void 0),
43
+ flags.labels !== void 0 && flags.labels !== "" ? resolveLabelIds(client, workspaceKey, teamId, flags.labels.split(",").map((s) => s.trim()).filter(Boolean), input.retryOptsOverride) : Promise.resolve(void 0),
44
+ flags.project !== void 0 ? resolveProjectId(client, workspaceKey, flags.project, input.retryOptsOverride) : Promise.resolve(void 0),
45
+ flags.cycle !== void 0 ? resolveCycleId(client, workspaceKey, teamId, flags.cycle, input.retryOptsOverride) : Promise.resolve(void 0),
46
+ flags.parent !== void 0 ? resolveIssueRefToUuid(client, flags.parent, input.retryOptsOverride) : Promise.resolve(void 0),
47
+ flags.assignee !== void 0 ? resolveAssignee(client, flags.assignee, input.retryOptsOverride) : Promise.resolve(void 0)
48
+ ]);
49
+ const createInput = {
50
+ teamId,
51
+ title
52
+ };
53
+ if (flags.description !== void 0) createInput.description = flags.description;
54
+ if (stateId !== void 0) createInput.stateId = stateId;
55
+ if (assigneeId !== void 0) createInput.assigneeId = assigneeId;
56
+ if (labelIds !== void 0) createInput.labelIds = labelIds;
57
+ if (projectId !== void 0) createInput.projectId = projectId;
58
+ if (cycleId !== void 0) createInput.cycleId = cycleId;
59
+ if (flags.priority !== void 0) createInput.priority = flags.priority;
60
+ if (parentId !== void 0) createInput.parentId = parentId;
61
+ const payload = await withRateLimitRetry(() => client.createIssue(createInput), input.retryOptsOverride);
62
+ if (!payload.success) throw LinearAgentError.linear.apiError({
63
+ message: "createIssue returned success=false",
64
+ details: { lastSyncId: payload.lastSyncId }
65
+ });
66
+ let created;
67
+ if (payload.issue !== void 0) {
68
+ const i = await Promise.resolve(payload.issue);
69
+ if (i !== void 0 && i !== null) created = i;
70
+ }
71
+ let data;
72
+ if (created) data = project(await hydrateForProjection(created, fields), fields);
73
+ else data = {};
74
+ const complexity = getLastComplexity();
75
+ const meta = {
76
+ workspace: resolved.name,
77
+ workspaceSource: resolved.source,
78
+ ...complexity !== void 0 ? { complexity } : {}
79
+ };
80
+ return {
81
+ data,
82
+ meta
83
+ };
84
+ });
85
+ }
86
+ /**
87
+ * Resolve a parent issue reference (`ENG-42` or UUID) to an issue UUID.
88
+ * Mirrors the `resolveIssue` helper in `issue-get-runtime.ts` but limited to
89
+ * the parent-id use case (no full issue hydration needed).
90
+ */
91
+ async function resolveIssueRefToUuid(client, ref, retryOpts) {
92
+ if (UUID_RE.test(ref)) return ref;
93
+ const m = ISSUE_IDENTIFIER_RE.exec(ref);
94
+ if (!m) throw new LinearAgentError({
95
+ code: "ISSUE_NOT_FOUND",
96
+ message: `parent issue not found: ${ref}`,
97
+ details: { ref }
98
+ });
99
+ const teamKey = m[1].toUpperCase();
100
+ const number = Number(m[2]);
101
+ const filter = {
102
+ team: { key: { eq: teamKey } },
103
+ number: { eq: number }
104
+ };
105
+ const node = (await withRateLimitRetry(() => client.issues({
106
+ filter,
107
+ first: 1
108
+ }), retryOpts)).nodes[0];
109
+ if (!node) throw new LinearAgentError({
110
+ code: "ISSUE_NOT_FOUND",
111
+ message: `parent issue not found: ${ref}`,
112
+ details: { ref }
113
+ });
114
+ return node.id;
115
+ }
116
+ /**
117
+ * Resolve a free-form assignee reference to a user UUID:
118
+ * - `me` → `client.viewer.id`
119
+ * - UUID → passthrough
120
+ * - email-shaped → `client.users({ filter: { email: { eq } }, first: 1 })`
121
+ * - other → `client.users({ filter: { name: { eq } }, first: 1 })`
122
+ */
123
+ async function resolveAssignee(client, ref, retryOpts) {
124
+ if (ref === "me") return (await withRateLimitRetry(() => Promise.resolve(client.viewer), retryOpts)).id;
125
+ if (UUID_RE.test(ref)) return ref;
126
+ const filter = EMAIL_RE.test(ref) ? { email: { eq: ref } } : { name: { eq: ref } };
127
+ const node = (await withRateLimitRetry(() => client.users({
128
+ filter,
129
+ first: 1
130
+ }), retryOpts)).nodes[0];
131
+ if (!node) throw LinearAgentError.linear.apiError({
132
+ message: `assignee not found: ${ref}`,
133
+ details: { ref }
134
+ });
135
+ return node.id;
136
+ }
137
+ const RELATION_KEYS = new Set([
138
+ "state",
139
+ "assignee",
140
+ "team",
141
+ "project",
142
+ "cycle",
143
+ "parent"
144
+ ]);
145
+ async function hydrateForProjection(issue, spec) {
146
+ const needs = neededRelations(spec);
147
+ if (needs.size === 0) {
148
+ const out = {};
149
+ for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
150
+ return out;
151
+ }
152
+ const hydrated = {};
153
+ for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
154
+ if (needs.has(k)) {
155
+ const value = issue[k];
156
+ hydrated[k] = await resolveLazy(value);
157
+ }
158
+ } else hydrated[k] = issue[k];
159
+ return hydrated;
160
+ }
161
+ function neededRelations(spec) {
162
+ if (spec === "*") return new Set(RELATION_KEYS);
163
+ const out = /* @__PURE__ */ new Set();
164
+ for (const path of spec) {
165
+ const head = path.split(".")[0];
166
+ if (head && RELATION_KEYS.has(head)) out.add(head);
167
+ }
168
+ return out;
169
+ }
170
+ async function resolveLazy(value) {
171
+ if (value && typeof value.then === "function") return await value;
172
+ return value;
173
+ }
174
+ //#endregion
175
+ export { issueCreateRuntime, resolveAssignee };
@@ -0,0 +1,153 @@
1
+ import { LinearAgentError } from "../core/errors/error.js";
2
+ import { redact } from "../core/redact/redact.js";
3
+ import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
4
+ import { withRateLimitRetry } from "../core/transport/rate-limit.js";
5
+ import "../core/transport/index.js";
6
+ import { createLinearClient } from "../core/client/factory.js";
7
+ import "../core/client/index.js";
8
+ import { loadConfig } from "../core/config/store.js";
9
+ import "../core/config/index.js";
10
+ import { parseFields, project } from "../core/projection/project.js";
11
+ import { resolveWorkspace } from "../core/workspace/resolver.js";
12
+ import { ISSUE_IDENTIFIER_RE } from "./filter-heuristics.js";
13
+ import { validateAndMergeIncludes } from "./include-fragments.js";
14
+ //#region src/lib/issue-get-runtime.ts
15
+ async function issueGetRuntime(input) {
16
+ const config = (input.loadConfigOverride ?? loadConfig)();
17
+ const envForResolver = {};
18
+ if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
19
+ if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
20
+ const resolved = resolveWorkspace({
21
+ flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
22
+ env: envForResolver,
23
+ config
24
+ });
25
+ const fields = parseFields(input.flags.fields ?? "defaults", "issue");
26
+ const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
27
+ const includes = input.flags.include ?? [];
28
+ if (includes.length === 0) return withFetchInterception(async () => {
29
+ const ref = input.args.identifier;
30
+ const issue = await resolveIssue(client, ref, input.retryOptsOverride);
31
+ if (!issue) throw new LinearAgentError({
32
+ code: "ISSUE_NOT_FOUND",
33
+ message: `issue not found: ${ref}`,
34
+ details: { ref }
35
+ });
36
+ const data = project(await hydrateForProjection(issue, fields), fields);
37
+ const complexity = getLastComplexity();
38
+ return {
39
+ data,
40
+ meta: {
41
+ workspace: resolved.name,
42
+ workspaceSource: resolved.source,
43
+ ...complexity !== void 0 ? { complexity } : {}
44
+ }
45
+ };
46
+ });
47
+ const query = composeIssueGetWithIncludes(validateAndMergeIncludes("issue get", includes));
48
+ return withFetchInterception(async () => {
49
+ const ref = input.args.identifier;
50
+ const response = await withRateLimitRetry(() => client.client.rawRequest(query, { id: ref }), input.retryOptsOverride);
51
+ if (response.error ?? !response.data) {
52
+ const safeMessage = redact(response.error ?? "no data returned from Linear API");
53
+ const safeCause = response.error !== void 0 ? redact(response.error) : void 0;
54
+ throw LinearAgentError.linear.apiError({
55
+ message: safeMessage,
56
+ details: {
57
+ command: "issue get",
58
+ cause: safeCause
59
+ }
60
+ });
61
+ }
62
+ const issueData = response.data.issue;
63
+ if (!issueData) throw new LinearAgentError({
64
+ code: "ISSUE_NOT_FOUND",
65
+ message: `issue not found: ${ref}`,
66
+ details: { ref }
67
+ });
68
+ const data = project(issueData, fields);
69
+ const complexity = getLastComplexity();
70
+ return {
71
+ data,
72
+ meta: {
73
+ workspace: resolved.name,
74
+ workspaceSource: resolved.source,
75
+ ...complexity !== void 0 ? { complexity } : {}
76
+ }
77
+ };
78
+ });
79
+ }
80
+ async function resolveIssue(client, ref, retryOpts) {
81
+ const m = ISSUE_IDENTIFIER_RE.exec(ref);
82
+ if (m) {
83
+ const teamKey = m[1].toUpperCase();
84
+ const number = Number(m[2]);
85
+ const filter = {
86
+ team: { key: { eq: teamKey } },
87
+ number: { eq: number }
88
+ };
89
+ return (await withRateLimitRetry(() => client.issues({
90
+ filter,
91
+ first: 1
92
+ }), retryOpts)).nodes[0];
93
+ }
94
+ return await withRateLimitRetry(() => client.issue(ref), retryOpts) ?? void 0;
95
+ }
96
+ const RELATION_KEYS = new Set([
97
+ "state",
98
+ "assignee",
99
+ "team",
100
+ "project",
101
+ "cycle",
102
+ "parent"
103
+ ]);
104
+ async function hydrateForProjection(issue, spec) {
105
+ const needs = neededRelations(spec);
106
+ if (needs.size === 0) {
107
+ const out = {};
108
+ for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
109
+ return out;
110
+ }
111
+ const hydrated = {};
112
+ for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
113
+ if (needs.has(k)) {
114
+ const value = issue[k];
115
+ hydrated[k] = await resolveLazy(value);
116
+ }
117
+ } else hydrated[k] = issue[k];
118
+ return hydrated;
119
+ }
120
+ function neededRelations(spec) {
121
+ if (spec === "*") return new Set(RELATION_KEYS);
122
+ const out = /* @__PURE__ */ new Set();
123
+ for (const path of spec) {
124
+ const head = path.split(".")[0];
125
+ if (head && RELATION_KEYS.has(head)) out.add(head);
126
+ }
127
+ return out;
128
+ }
129
+ async function resolveLazy(value) {
130
+ if (value && typeof value.then === "function") return await value;
131
+ return value;
132
+ }
133
+ function composeIssueGetWithIncludes(fragmentText) {
134
+ return `
135
+ query IssueWithIncludes($id: String!) {
136
+ issue(id: $id) {
137
+ id identifier title description priority priorityLabel
138
+ estimate sortOrder number url
139
+ createdAt updatedAt archivedAt completedAt startedAt canceledAt
140
+ dueDate snoozedUntilAt
141
+ state { id name type }
142
+ assignee { id email name }
143
+ team { id key name }
144
+ project { id name }
145
+ cycle { id number }
146
+ parent { id identifier }
147
+ ${fragmentText}
148
+ }
149
+ }
150
+ `.trim();
151
+ }
152
+ //#endregion
153
+ export { issueGetRuntime };
@@ -0,0 +1,164 @@
1
+ import { LinearAgentError } from "../core/errors/error.js";
2
+ import { redact } from "../core/redact/redact.js";
3
+ import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
4
+ import { withRateLimitRetry } from "../core/transport/rate-limit.js";
5
+ import "../core/transport/index.js";
6
+ import { createLinearClient } from "../core/client/factory.js";
7
+ import "../core/client/index.js";
8
+ import { loadConfig } from "../core/config/store.js";
9
+ import "../core/config/index.js";
10
+ import { parseFields, project } from "../core/projection/project.js";
11
+ import { resolveWorkspace } from "../core/workspace/resolver.js";
12
+ import { parsePagination } from "../core/pagination/flags.js";
13
+ import "../core/pagination/index.js";
14
+ import { buildIssueFilter } from "./filter-heuristics.js";
15
+ import { validateAndMergeIncludes } from "./include-fragments.js";
16
+ //#region src/lib/issue-list-runtime.ts
17
+ async function issueListRuntime(input) {
18
+ const config = (input.loadConfigOverride ?? loadConfig)();
19
+ const envForResolver = {};
20
+ if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
21
+ if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
22
+ const resolved = resolveWorkspace({
23
+ flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
24
+ env: envForResolver,
25
+ config
26
+ });
27
+ const fields = parseFields(input.flags.fields ?? "defaults", "issue");
28
+ const { first, after } = parsePagination({
29
+ ...input.flags.limit !== void 0 ? { limit: input.flags.limit } : {},
30
+ ...input.flags.cursor !== void 0 ? { cursor: input.flags.cursor } : {}
31
+ });
32
+ const filter = buildIssueFilter(input.flags);
33
+ const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
34
+ const includes = input.flags.include ?? [];
35
+ if (includes.length === 0) return withFetchInterception(async () => {
36
+ const issuesArgs = { first };
37
+ if (after !== void 0) issuesArgs.after = after;
38
+ if (filter !== void 0) issuesArgs.filter = filter;
39
+ const connection = await withRateLimitRetry(() => client.issues(issuesArgs), input.retryOptsOverride);
40
+ const projected = await Promise.all(connection.nodes.map(async (issue) => {
41
+ return project(await hydrateForProjection(issue, fields), fields);
42
+ }));
43
+ const complexity = getLastComplexity();
44
+ return {
45
+ data: projected,
46
+ meta: {
47
+ workspace: resolved.name,
48
+ workspaceSource: resolved.source,
49
+ pageInfo: {
50
+ hasNextPage: Boolean(connection.pageInfo?.hasNextPage),
51
+ endCursor: connection.pageInfo?.endCursor ?? null,
52
+ hasPreviousPage: Boolean(connection.pageInfo?.hasPreviousPage),
53
+ startCursor: connection.pageInfo?.startCursor ?? null
54
+ },
55
+ ...complexity !== void 0 ? { complexity } : {}
56
+ }
57
+ };
58
+ });
59
+ const query = composeIssueListWithIncludes(validateAndMergeIncludes("issue list", includes));
60
+ return withFetchInterception(async () => {
61
+ const vars = { first };
62
+ if (after !== void 0) vars.after = after;
63
+ if (filter !== void 0) vars.filter = filter;
64
+ const response = await withRateLimitRetry(() => client.client.rawRequest(query, vars), input.retryOptsOverride);
65
+ if (response.error ?? !response.data) {
66
+ const safeMessage = redact(response.error ?? "no data returned from Linear API");
67
+ const safeCause = response.error !== void 0 ? redact(response.error) : void 0;
68
+ throw LinearAgentError.linear.apiError({
69
+ message: safeMessage,
70
+ details: {
71
+ command: "issue list",
72
+ cause: safeCause
73
+ }
74
+ });
75
+ }
76
+ const root = response.data.issues;
77
+ if (!root) throw LinearAgentError.linear.apiError({
78
+ message: "unexpected response shape: missing issues field",
79
+ details: { command: "issue list" }
80
+ });
81
+ const projected = root.nodes.map((issue) => project(issue, fields));
82
+ const complexity = getLastComplexity();
83
+ return {
84
+ data: projected,
85
+ meta: {
86
+ workspace: resolved.name,
87
+ workspaceSource: resolved.source,
88
+ pageInfo: {
89
+ hasNextPage: Boolean(root.pageInfo?.hasNextPage),
90
+ endCursor: root.pageInfo?.endCursor ?? null,
91
+ hasPreviousPage: Boolean(root.pageInfo?.hasPreviousPage),
92
+ startCursor: root.pageInfo?.startCursor ?? null
93
+ },
94
+ ...complexity !== void 0 ? { complexity } : {}
95
+ }
96
+ };
97
+ });
98
+ }
99
+ /**
100
+ * The typed SDK exposes `issue.state`, `issue.assignee`, `issue.team` as
101
+ * lazy promise getters. To project paths like `state.name`, we must `await`
102
+ * each. We hydrate ONLY the related entities the projection spec actually
103
+ * references — narrows N+1 surface for the common `--fields=ids` case.
104
+ *
105
+ * Phase 2 will replace this with a single GraphQL fragment query that pulls
106
+ * the related entities in one round-trip; documented in PITFALLS as the
107
+ * known scaling cliff this Phase 1 implementation deliberately accepts.
108
+ */
109
+ async function hydrateForProjection(issue, spec) {
110
+ const needs = neededRelations(spec);
111
+ const hydrated = { ...issue };
112
+ if (needs.has("state") && hydrated.state !== void 0) hydrated.state = await resolveLazy(hydrated.state);
113
+ if (needs.has("assignee") && hydrated.assignee !== void 0) hydrated.assignee = await resolveLazy(hydrated.assignee);
114
+ if (needs.has("team") && hydrated.team !== void 0) hydrated.team = await resolveLazy(hydrated.team);
115
+ if (needs.has("project") && hydrated.project !== void 0) hydrated.project = await resolveLazy(hydrated.project);
116
+ if (needs.has("cycle") && hydrated.cycle !== void 0) hydrated.cycle = await resolveLazy(hydrated.cycle);
117
+ if (needs.has("parent") && hydrated.parent !== void 0) hydrated.parent = await resolveLazy(hydrated.parent);
118
+ return hydrated;
119
+ }
120
+ const RELATION_KEYS = new Set([
121
+ "state",
122
+ "assignee",
123
+ "team",
124
+ "project",
125
+ "cycle",
126
+ "parent"
127
+ ]);
128
+ function neededRelations(spec) {
129
+ if (spec === "*") return new Set(RELATION_KEYS);
130
+ const out = /* @__PURE__ */ new Set();
131
+ for (const path of spec) {
132
+ const head = path.split(".")[0];
133
+ if (head && RELATION_KEYS.has(head)) out.add(head);
134
+ }
135
+ return out;
136
+ }
137
+ async function resolveLazy(value) {
138
+ if (value && typeof value.then === "function") return await value;
139
+ return value;
140
+ }
141
+ function composeIssueListWithIncludes(fragmentText) {
142
+ return `
143
+ query IssuesWithIncludes($filter: IssueFilter, $first: Int, $after: String) {
144
+ issues(filter: $filter, first: $first, after: $after) {
145
+ nodes {
146
+ id identifier title description priority priorityLabel
147
+ estimate sortOrder number url
148
+ createdAt updatedAt archivedAt completedAt startedAt canceledAt
149
+ dueDate snoozedUntilAt
150
+ state { id name type }
151
+ assignee { id email name }
152
+ team { id key name }
153
+ project { id name }
154
+ cycle { id number }
155
+ parent { id identifier }
156
+ ${fragmentText}
157
+ }
158
+ pageInfo { hasNextPage endCursor hasPreviousPage startCursor }
159
+ }
160
+ }
161
+ `.trim();
162
+ }
163
+ //#endregion
164
+ export { issueListRuntime };
@@ -0,0 +1,89 @@
1
+ import { LinearAgentError } from "../core/errors/error.js";
2
+ import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
3
+ import { withRateLimitRetry } from "../core/transport/rate-limit.js";
4
+ import "../core/transport/index.js";
5
+ import { createLinearClient } from "../core/client/factory.js";
6
+ import "../core/client/index.js";
7
+ import { loadConfig } from "../core/config/store.js";
8
+ import "../core/config/index.js";
9
+ import { resolveWorkspace } from "../core/workspace/resolver.js";
10
+ import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
11
+ import { ISSUE_IDENTIFIER_RE, UUID_RE } from "./filter-heuristics.js";
12
+ //#region src/lib/issue-purge-runtime.ts
13
+ async function issuePurgeRuntime(input) {
14
+ const config = (input.loadConfigOverride ?? loadConfig)();
15
+ const envForResolver = {};
16
+ if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
17
+ if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
18
+ const resolved = resolveWorkspace({
19
+ flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
20
+ env: envForResolver,
21
+ config
22
+ });
23
+ requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
24
+ if (!input.flags.yes) throw new LinearAgentError({
25
+ code: "CONFIRMATION_REQUIRED",
26
+ message: "purge is permanent -- re-run with --yes to confirm",
27
+ details: { remediation: "pass --yes to confirm permanent deletion" }
28
+ });
29
+ const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
30
+ return withFetchInterception(async () => {
31
+ const ref = input.args.identifier;
32
+ const { id, identifier } = await resolveIssueRef(client, ref, input.retryOptsOverride);
33
+ const payload = await withRateLimitRetry(() => client.deleteIssue(id, { permanentlyDelete: true }), input.retryOptsOverride);
34
+ if (!payload.success) throw LinearAgentError.linear.apiError({
35
+ message: "deleteIssue returned success=false",
36
+ details: { lastSyncId: payload.lastSyncId }
37
+ });
38
+ const data = {
39
+ id,
40
+ permanentlyDeleted: true
41
+ };
42
+ if (identifier !== void 0) data.identifier = identifier;
43
+ const complexity = getLastComplexity();
44
+ return {
45
+ data,
46
+ meta: {
47
+ workspace: resolved.name,
48
+ workspaceSource: resolved.source,
49
+ ...complexity !== void 0 ? { complexity } : {}
50
+ }
51
+ };
52
+ });
53
+ }
54
+ async function resolveIssueRef(client, ref, retryOpts) {
55
+ if (UUID_RE.test(ref)) return { id: ref };
56
+ const m = ref.match(ISSUE_IDENTIFIER_RE);
57
+ if (!m) throw new LinearAgentError({
58
+ code: "ISSUE_NOT_FOUND",
59
+ message: `invalid issue ref: ${ref}`,
60
+ details: { ref }
61
+ });
62
+ const teamKey = m[1].toUpperCase();
63
+ const number = Number(m[2]);
64
+ const issue = (await withRateLimitRetry(() => client.issues({
65
+ filter: {
66
+ team: { key: { eq: teamKey } },
67
+ number: { eq: number }
68
+ },
69
+ first: 1,
70
+ includeArchived: true
71
+ }), retryOpts)).nodes[0];
72
+ if (!issue) throw new LinearAgentError({
73
+ code: "ISSUE_NOT_FOUND",
74
+ message: `issue not found: ${ref}`,
75
+ details: { ref }
76
+ });
77
+ const idRaw = issue.id;
78
+ if (typeof idRaw !== "string") throw new LinearAgentError({
79
+ code: "ISSUE_NOT_FOUND",
80
+ message: `issue not found: ${ref}`,
81
+ details: { ref }
82
+ });
83
+ const idRes = { id: idRaw };
84
+ const idn = issue.identifier;
85
+ if (typeof idn === "string") idRes.identifier = idn;
86
+ return idRes;
87
+ }
88
+ //#endregion
89
+ export { issuePurgeRuntime };