@iloom/cli 0.2.0 → 0.3.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.
Files changed (169) hide show
  1. package/README.md +234 -667
  2. package/dist/BranchNamingService-OMWKUYMM.js +13 -0
  3. package/dist/ClaudeContextManager-3VXA6UPR.js +13 -0
  4. package/dist/ClaudeService-6CPK43N4.js +12 -0
  5. package/dist/GitHubService-EBOETDIW.js +11 -0
  6. package/dist/{LoomLauncher-CTSWJL35.js → LoomLauncher-JF7JZMTZ.js} +63 -32
  7. package/dist/LoomLauncher-JF7JZMTZ.js.map +1 -0
  8. package/dist/ProjectCapabilityDetector-34LU7JJ4.js +9 -0
  9. package/dist/{PromptTemplateManager-WII75TKH.js → PromptTemplateManager-A52RUAMS.js} +2 -2
  10. package/dist/README.md +234 -667
  11. package/dist/{SettingsManager-XOYCLH3D.js → SettingsManager-ZCWJ56WP.js} +12 -4
  12. package/dist/SettingsMigrationManager-AGIIIPDQ.js +10 -0
  13. package/dist/agents/iloom-issue-analyze-and-plan.md +125 -35
  14. package/dist/agents/iloom-issue-analyzer.md +284 -32
  15. package/dist/agents/iloom-issue-complexity-evaluator.md +40 -21
  16. package/dist/agents/iloom-issue-enhancer.md +69 -48
  17. package/dist/agents/iloom-issue-implementer.md +36 -25
  18. package/dist/agents/iloom-issue-planner.md +35 -24
  19. package/dist/agents/iloom-issue-reviewer.md +62 -9
  20. package/dist/{chunk-SWCRXDZC.js → chunk-3RUPPQRG.js} +1 -18
  21. package/dist/chunk-3RUPPQRG.js.map +1 -0
  22. package/dist/{chunk-HBVFXN7R.js → chunk-4BGK7T6X.js} +26 -3
  23. package/dist/chunk-4BGK7T6X.js.map +1 -0
  24. package/dist/{chunk-6LEQW46Y.js → chunk-4E4LD3QR.js} +72 -2
  25. package/dist/{chunk-6LEQW46Y.js.map → chunk-4E4LD3QR.js.map} +1 -1
  26. package/dist/{chunk-CWR2SANQ.js → chunk-EBISESAP.js} +1 -1
  27. package/dist/{chunk-TS6DL67T.js → chunk-G2IEYOLQ.js} +11 -38
  28. package/dist/chunk-G2IEYOLQ.js.map +1 -0
  29. package/dist/chunk-HBYZH6GD.js +1989 -0
  30. package/dist/chunk-HBYZH6GD.js.map +1 -0
  31. package/dist/chunk-INW24J2W.js +55 -0
  32. package/dist/chunk-INW24J2W.js.map +1 -0
  33. package/dist/{chunk-ZMNQBJUI.js → chunk-IP7SMKIF.js} +61 -22
  34. package/dist/chunk-IP7SMKIF.js.map +1 -0
  35. package/dist/{chunk-4IV6W4U5.js → chunk-IXKLYTWO.js} +12 -12
  36. package/dist/chunk-IXKLYTWO.js.map +1 -0
  37. package/dist/{chunk-JNKJ7NJV.js → chunk-JKXJ7BGL.js} +6 -2
  38. package/dist/{chunk-JNKJ7NJV.js.map → chunk-JKXJ7BGL.js.map} +1 -1
  39. package/dist/{chunk-LAPY6NAE.js → chunk-JQFO7QQN.js} +68 -12
  40. package/dist/{chunk-LAPY6NAE.js.map → chunk-JQFO7QQN.js.map} +1 -1
  41. package/dist/{SettingsMigrationManager-MTQIMI54.js → chunk-KLBYVHPK.js} +3 -2
  42. package/dist/{chunk-USVVV3FP.js → chunk-MKWYLDFK.js} +5 -5
  43. package/dist/chunk-O5OH5MRX.js +396 -0
  44. package/dist/chunk-O5OH5MRX.js.map +1 -0
  45. package/dist/{chunk-DJUGYNQE.js → chunk-PA6Q6AWM.js} +16 -3
  46. package/dist/chunk-PA6Q6AWM.js.map +1 -0
  47. package/dist/chunk-RO26VS3W.js +444 -0
  48. package/dist/chunk-RO26VS3W.js.map +1 -0
  49. package/dist/{chunk-VETG35MF.js → chunk-TSKY3JI7.js} +3 -3
  50. package/dist/{chunk-VETG35MF.js.map → chunk-TSKY3JI7.js.map} +1 -1
  51. package/dist/{chunk-LHP6ROUM.js → chunk-U5QDY7ZD.js} +4 -16
  52. package/dist/chunk-U5QDY7ZD.js.map +1 -0
  53. package/dist/{chunk-SPYPLHMK.js → chunk-VU3QMIP2.js} +34 -2
  54. package/dist/chunk-VU3QMIP2.js.map +1 -0
  55. package/dist/{chunk-PVAVNJKS.js → chunk-WEN5C5DM.js} +10 -1
  56. package/dist/chunk-WEN5C5DM.js.map +1 -0
  57. package/dist/{chunk-2PLUQT6J.js → chunk-XPKDPZ5D.js} +2 -2
  58. package/dist/{chunk-RF2YI2XJ.js → chunk-ZBQVSHVT.js} +5 -5
  59. package/dist/chunk-ZBQVSHVT.js.map +1 -0
  60. package/dist/{chunk-GZP4UGGM.js → chunk-ZM3CFL5L.js} +2 -2
  61. package/dist/{chunk-BLCTGFZN.js → chunk-ZT3YZB4K.js} +3 -4
  62. package/dist/chunk-ZT3YZB4K.js.map +1 -0
  63. package/dist/{chunk-MFU53H6J.js → chunk-ZWFBBPJI.js} +6 -6
  64. package/dist/{chunk-MFU53H6J.js.map → chunk-ZWFBBPJI.js.map} +1 -1
  65. package/dist/{claude-ZIWDG4XG.js → claude-LUZ35IMK.js} +2 -2
  66. package/dist/{cleanup-FEIVZSIV.js → cleanup-3MONU4PU.js} +88 -27
  67. package/dist/cleanup-3MONU4PU.js.map +1 -0
  68. package/dist/cli.js +2511 -62
  69. package/dist/cli.js.map +1 -1
  70. package/dist/{contribute-EMZKCAC6.js → contribute-UWJAGIG7.js} +6 -6
  71. package/dist/{feedback-LFNMQBAZ.js → feedback-W3BXTGIM.js} +15 -14
  72. package/dist/{feedback-LFNMQBAZ.js.map → feedback-W3BXTGIM.js.map} +1 -1
  73. package/dist/{git-WC6HZLOT.js → git-34Z6QVDS.js} +4 -2
  74. package/dist/{ignite-MQWVJEAB.js → ignite-KVJEFXNO.js} +32 -27
  75. package/dist/ignite-KVJEFXNO.js.map +1 -0
  76. package/dist/index.d.ts +359 -45
  77. package/dist/index.js +1267 -503
  78. package/dist/index.js.map +1 -1
  79. package/dist/{init-GJDYN2IK.js → init-L55Q73H4.js} +104 -40
  80. package/dist/init-L55Q73H4.js.map +1 -0
  81. package/dist/mcp/issue-management-server.js +934 -0
  82. package/dist/mcp/issue-management-server.js.map +1 -0
  83. package/dist/{neon-helpers-ZVIRPKCI.js → neon-helpers-WPUACUVC.js} +3 -3
  84. package/dist/neon-helpers-WPUACUVC.js.map +1 -0
  85. package/dist/{open-NXSN7XOC.js → open-LNRZL3UU.js} +39 -36
  86. package/dist/open-LNRZL3UU.js.map +1 -0
  87. package/dist/{prompt-ANTQWHUF.js → prompt-7INJ7YRU.js} +4 -2
  88. package/dist/prompt-7INJ7YRU.js.map +1 -0
  89. package/dist/prompts/init-prompt.txt +541 -98
  90. package/dist/prompts/issue-prompt.txt +27 -27
  91. package/dist/{rebase-DUNFOJVS.js → rebase-C4WNCVGM.js} +6 -6
  92. package/dist/{remote-ZCXJVVNW.js → remote-VUNCQZ6J.js} +3 -2
  93. package/dist/remote-VUNCQZ6J.js.map +1 -0
  94. package/dist/{run-O7ZK7CKA.js → run-IOGNIOYN.js} +39 -36
  95. package/dist/run-IOGNIOYN.js.map +1 -0
  96. package/dist/schema/settings.schema.json +59 -3
  97. package/dist/{test-git-T76HOTIA.js → test-git-J7I5MFYH.js} +3 -3
  98. package/dist/{test-prefix-6HJUVQMH.js → test-prefix-ZCONBCBX.js} +3 -3
  99. package/dist/{test-webserver-M2I3EV4J.js → test-webserver-DAHONWCS.js} +4 -4
  100. package/dist/test-webserver-DAHONWCS.js.map +1 -0
  101. package/package.json +3 -2
  102. package/dist/ClaudeContextManager-LVCYRM6Q.js +0 -13
  103. package/dist/ClaudeService-WVTWB3DK.js +0 -12
  104. package/dist/GitHubService-7E2S5NNZ.js +0 -11
  105. package/dist/LoomLauncher-CTSWJL35.js.map +0 -1
  106. package/dist/add-issue-OBI325W7.js +0 -69
  107. package/dist/add-issue-OBI325W7.js.map +0 -1
  108. package/dist/chunk-4IV6W4U5.js.map +0 -1
  109. package/dist/chunk-BLCTGFZN.js.map +0 -1
  110. package/dist/chunk-CVLAZRNB.js +0 -54
  111. package/dist/chunk-CVLAZRNB.js.map +0 -1
  112. package/dist/chunk-DJUGYNQE.js.map +0 -1
  113. package/dist/chunk-H4E4THUZ.js +0 -55
  114. package/dist/chunk-H4E4THUZ.js.map +0 -1
  115. package/dist/chunk-H5LDRGVK.js +0 -642
  116. package/dist/chunk-H5LDRGVK.js.map +0 -1
  117. package/dist/chunk-HBVFXN7R.js.map +0 -1
  118. package/dist/chunk-LHP6ROUM.js.map +0 -1
  119. package/dist/chunk-PVAVNJKS.js.map +0 -1
  120. package/dist/chunk-RF2YI2XJ.js.map +0 -1
  121. package/dist/chunk-SPYPLHMK.js.map +0 -1
  122. package/dist/chunk-SWCRXDZC.js.map +0 -1
  123. package/dist/chunk-SYOSCMIT.js +0 -545
  124. package/dist/chunk-SYOSCMIT.js.map +0 -1
  125. package/dist/chunk-T3KEIB4D.js +0 -243
  126. package/dist/chunk-T3KEIB4D.js.map +0 -1
  127. package/dist/chunk-TS6DL67T.js.map +0 -1
  128. package/dist/chunk-ZMNQBJUI.js.map +0 -1
  129. package/dist/cleanup-FEIVZSIV.js.map +0 -1
  130. package/dist/enhance-MNA4ZGXW.js +0 -176
  131. package/dist/enhance-MNA4ZGXW.js.map +0 -1
  132. package/dist/finish-TX5CJICB.js +0 -1749
  133. package/dist/finish-TX5CJICB.js.map +0 -1
  134. package/dist/ignite-MQWVJEAB.js.map +0 -1
  135. package/dist/init-GJDYN2IK.js.map +0 -1
  136. package/dist/mcp/chunk-6SDFJ42P.js +0 -62
  137. package/dist/mcp/chunk-6SDFJ42P.js.map +0 -1
  138. package/dist/mcp/claude-NDFOCQQQ.js +0 -249
  139. package/dist/mcp/claude-NDFOCQQQ.js.map +0 -1
  140. package/dist/mcp/color-QS5BFCNN.js +0 -168
  141. package/dist/mcp/color-QS5BFCNN.js.map +0 -1
  142. package/dist/mcp/github-comment-server.js +0 -168
  143. package/dist/mcp/github-comment-server.js.map +0 -1
  144. package/dist/mcp/terminal-OMNRFWB3.js +0 -227
  145. package/dist/mcp/terminal-OMNRFWB3.js.map +0 -1
  146. package/dist/open-NXSN7XOC.js.map +0 -1
  147. package/dist/run-O7ZK7CKA.js.map +0 -1
  148. package/dist/start-73I5W7WW.js +0 -983
  149. package/dist/start-73I5W7WW.js.map +0 -1
  150. package/dist/test-webserver-M2I3EV4J.js.map +0 -1
  151. /package/dist/{ClaudeContextManager-LVCYRM6Q.js.map → BranchNamingService-OMWKUYMM.js.map} +0 -0
  152. /package/dist/{ClaudeService-WVTWB3DK.js.map → ClaudeContextManager-3VXA6UPR.js.map} +0 -0
  153. /package/dist/{GitHubService-7E2S5NNZ.js.map → ClaudeService-6CPK43N4.js.map} +0 -0
  154. /package/dist/{PromptTemplateManager-WII75TKH.js.map → GitHubService-EBOETDIW.js.map} +0 -0
  155. /package/dist/{SettingsManager-XOYCLH3D.js.map → ProjectCapabilityDetector-34LU7JJ4.js.map} +0 -0
  156. /package/dist/{claude-ZIWDG4XG.js.map → PromptTemplateManager-A52RUAMS.js.map} +0 -0
  157. /package/dist/{git-WC6HZLOT.js.map → SettingsManager-ZCWJ56WP.js.map} +0 -0
  158. /package/dist/{neon-helpers-ZVIRPKCI.js.map → SettingsMigrationManager-AGIIIPDQ.js.map} +0 -0
  159. /package/dist/{chunk-CWR2SANQ.js.map → chunk-EBISESAP.js.map} +0 -0
  160. /package/dist/{SettingsMigrationManager-MTQIMI54.js.map → chunk-KLBYVHPK.js.map} +0 -0
  161. /package/dist/{chunk-USVVV3FP.js.map → chunk-MKWYLDFK.js.map} +0 -0
  162. /package/dist/{chunk-2PLUQT6J.js.map → chunk-XPKDPZ5D.js.map} +0 -0
  163. /package/dist/{chunk-GZP4UGGM.js.map → chunk-ZM3CFL5L.js.map} +0 -0
  164. /package/dist/{prompt-ANTQWHUF.js.map → claude-LUZ35IMK.js.map} +0 -0
  165. /package/dist/{contribute-EMZKCAC6.js.map → contribute-UWJAGIG7.js.map} +0 -0
  166. /package/dist/{remote-ZCXJVVNW.js.map → git-34Z6QVDS.js.map} +0 -0
  167. /package/dist/{rebase-DUNFOJVS.js.map → rebase-C4WNCVGM.js.map} +0 -0
  168. /package/dist/{test-git-T76HOTIA.js.map → test-git-J7I5MFYH.js.map} +0 -0
  169. /package/dist/{test-prefix-6HJUVQMH.js.map → test-prefix-ZCONBCBX.js.map} +0 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitHubService
4
+ } from "./chunk-G2IEYOLQ.js";
5
+ import {
6
+ getRepoInfo
7
+ } from "./chunk-3RUPPQRG.js";
8
+ import {
9
+ promptConfirmation
10
+ } from "./chunk-JKXJ7BGL.js";
11
+ import {
12
+ logger
13
+ } from "./chunk-GEHQXLEI.js";
14
+
15
+ // src/types/linear.ts
16
+ var LinearServiceError = class _LinearServiceError extends Error {
17
+ constructor(code, message, details) {
18
+ super(message);
19
+ this.code = code;
20
+ this.details = details;
21
+ this.name = "LinearServiceError";
22
+ if (Error.captureStackTrace) {
23
+ Error.captureStackTrace(this, _LinearServiceError);
24
+ }
25
+ }
26
+ };
27
+
28
+ // src/utils/linear.ts
29
+ import { LinearClient } from "@linear/sdk";
30
+ function slugifyTitle(title, maxLength = 50) {
31
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
32
+ if (slug.length <= maxLength) {
33
+ return slug;
34
+ }
35
+ const parts = slug.split("-");
36
+ let result = "";
37
+ for (const part of parts) {
38
+ const candidate = result ? `${result}-${part}` : part;
39
+ if (candidate.length > maxLength) {
40
+ break;
41
+ }
42
+ result = candidate;
43
+ }
44
+ return result || slug.slice(0, maxLength);
45
+ }
46
+ function buildLinearIssueUrl(identifier, title) {
47
+ const base = `https://linear.app/issue/${identifier}`;
48
+ if (title) {
49
+ const slug = slugifyTitle(title);
50
+ return slug ? `${base}/${slug}` : base;
51
+ }
52
+ return base;
53
+ }
54
+ function getLinearApiToken() {
55
+ const token = process.env.LINEAR_API_TOKEN;
56
+ if (!token) {
57
+ throw new LinearServiceError(
58
+ "UNAUTHORIZED",
59
+ "LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable."
60
+ );
61
+ }
62
+ return token;
63
+ }
64
+ function createLinearClient() {
65
+ return new LinearClient({ apiKey: getLinearApiToken() });
66
+ }
67
+ function handleLinearError(error, context) {
68
+ logger.debug(`${context}: Handling error`, { error });
69
+ const errorMessage = error instanceof Error ? error.message : String(error);
70
+ if (errorMessage.includes("not found") || errorMessage.includes("Not found")) {
71
+ throw new LinearServiceError("NOT_FOUND", "Linear issue or resource not found", { error });
72
+ }
73
+ if (errorMessage.includes("unauthorized") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
74
+ throw new LinearServiceError(
75
+ "UNAUTHORIZED",
76
+ "Linear authentication failed. Check LINEAR_API_TOKEN.",
77
+ { error }
78
+ );
79
+ }
80
+ if (errorMessage.includes("rate limit")) {
81
+ throw new LinearServiceError("RATE_LIMITED", "Linear API rate limit exceeded", { error });
82
+ }
83
+ throw new LinearServiceError("CLI_ERROR", `Linear SDK error: ${errorMessage}`, { error });
84
+ }
85
+ async function fetchLinearIssue(identifier) {
86
+ try {
87
+ logger.debug(`Fetching Linear issue: ${identifier}`);
88
+ const client = createLinearClient();
89
+ const issue = await client.issue(identifier);
90
+ if (!issue) {
91
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
92
+ }
93
+ const result = {
94
+ id: issue.id,
95
+ identifier: issue.identifier,
96
+ title: issue.title,
97
+ url: issue.url,
98
+ createdAt: issue.createdAt.toISOString(),
99
+ updatedAt: issue.updatedAt.toISOString()
100
+ };
101
+ if (issue.description) {
102
+ result.description = issue.description;
103
+ }
104
+ if (issue.state) {
105
+ const state = await issue.state;
106
+ if (state == null ? void 0 : state.name) {
107
+ result.state = state.name;
108
+ }
109
+ }
110
+ return result;
111
+ } catch (error) {
112
+ if (error instanceof LinearServiceError) {
113
+ throw error;
114
+ }
115
+ handleLinearError(error, "fetchLinearIssue");
116
+ }
117
+ }
118
+ async function createLinearIssue(title, body, teamKey, _labels) {
119
+ try {
120
+ logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`);
121
+ const client = createLinearClient();
122
+ const teams = await client.teams();
123
+ const team = teams.nodes.find((t) => t.key === teamKey);
124
+ if (!team) {
125
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
126
+ }
127
+ const issueInput = {
128
+ teamId: team.id,
129
+ title
130
+ };
131
+ if (body) {
132
+ issueInput.description = body;
133
+ }
134
+ const payload = await client.createIssue(issueInput);
135
+ const issue = await payload.issue;
136
+ if (!issue) {
137
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue");
138
+ }
139
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
140
+ return {
141
+ identifier: issue.identifier,
142
+ url
143
+ };
144
+ } catch (error) {
145
+ if (error instanceof LinearServiceError) {
146
+ throw error;
147
+ }
148
+ handleLinearError(error, "createLinearIssue");
149
+ }
150
+ }
151
+ async function updateLinearIssueState(identifier, stateName) {
152
+ try {
153
+ logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`);
154
+ const client = createLinearClient();
155
+ const issue = await client.issue(identifier);
156
+ if (!issue) {
157
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
158
+ }
159
+ const team = await issue.team;
160
+ if (!team) {
161
+ throw new LinearServiceError("CLI_ERROR", "Issue has no team");
162
+ }
163
+ const states = await team.states();
164
+ const state = states.nodes.find((s) => s.name === stateName);
165
+ if (!state) {
166
+ throw new LinearServiceError(
167
+ "NOT_FOUND",
168
+ `State "${stateName}" not found in team ${team.key}`
169
+ );
170
+ }
171
+ await client.updateIssue(issue.id, {
172
+ stateId: state.id
173
+ });
174
+ } catch (error) {
175
+ if (error instanceof LinearServiceError) {
176
+ throw error;
177
+ }
178
+ handleLinearError(error, "updateLinearIssueState");
179
+ }
180
+ }
181
+
182
+ // src/lib/LinearService.ts
183
+ var LinearService = class {
184
+ constructor(config, options) {
185
+ // IssueTracker interface implementation
186
+ this.providerName = "linear";
187
+ this.supportsPullRequests = false;
188
+ this.config = config ?? {};
189
+ this.prompter = (options == null ? void 0 : options.prompter) ?? promptConfirmation;
190
+ }
191
+ /**
192
+ * Detect if input matches Linear identifier format (TEAM-NUMBER)
193
+ * @param input - User input string
194
+ * @param _repo - Repository (unused for Linear)
195
+ * @returns Detection result with type and identifier
196
+ */
197
+ async detectInputType(input, _repo) {
198
+ logger.debug(`LinearService.detectInputType called with input: "${input}"`);
199
+ const linearPattern = /^([A-Z]{2,}-\d+)$/i;
200
+ const match = input.match(linearPattern);
201
+ if (!(match == null ? void 0 : match[1])) {
202
+ logger.debug(`LinearService: Input "${input}" does not match Linear pattern`);
203
+ return { type: "unknown", identifier: null, rawInput: input };
204
+ }
205
+ const identifier = match[1].toUpperCase();
206
+ logger.debug(`LinearService: Matched Linear identifier: ${identifier}`);
207
+ logger.debug(`LinearService: Checking if ${identifier} is a valid Linear issue via SDK`);
208
+ const issue = await this.isValidIssue(identifier);
209
+ if (issue) {
210
+ logger.debug(`LinearService: Issue ${identifier} found: "${issue.title}"`);
211
+ return { type: "issue", identifier, rawInput: input };
212
+ }
213
+ logger.debug(`LinearService: Issue ${identifier} NOT found by SDK`);
214
+ return { type: "unknown", identifier: null, rawInput: input };
215
+ }
216
+ /**
217
+ * Fetch a Linear issue by identifier
218
+ * @param identifier - Linear issue identifier (string or number)
219
+ * @param _repo - Repository (unused for Linear)
220
+ * @returns Generic Issue type
221
+ * @throws LinearServiceError if issue not found
222
+ */
223
+ async fetchIssue(identifier, _repo) {
224
+ const linearIssue = await fetchLinearIssue(String(identifier));
225
+ return this.mapLinearIssueToIssue(linearIssue);
226
+ }
227
+ /**
228
+ * Check if an issue identifier is valid (silent validation)
229
+ * @param identifier - Linear issue identifier
230
+ * @param _repo - Repository (unused for Linear)
231
+ * @returns Issue if valid, false if not found
232
+ */
233
+ async isValidIssue(identifier, _repo) {
234
+ try {
235
+ return await this.fetchIssue(identifier);
236
+ } catch (error) {
237
+ if (error instanceof LinearServiceError && error.code === "NOT_FOUND") {
238
+ return false;
239
+ }
240
+ throw error;
241
+ }
242
+ }
243
+ /**
244
+ * Validate issue state and prompt user if closed
245
+ * @param issue - Issue to validate
246
+ * @throws LinearServiceError if user cancels due to closed issue
247
+ */
248
+ async validateIssueState(issue) {
249
+ if (issue.state === "closed") {
250
+ const shouldContinue = await this.prompter(
251
+ `Issue ${issue.number} is closed. Continue anyway?`
252
+ );
253
+ if (!shouldContinue) {
254
+ throw new LinearServiceError("INVALID_STATE", "User cancelled due to closed issue");
255
+ }
256
+ }
257
+ }
258
+ /**
259
+ * Create a new Linear issue
260
+ * @param title - Issue title
261
+ * @param body - Issue description (markdown)
262
+ * @param _repository - Repository (unused for Linear)
263
+ * @param labels - Optional label names
264
+ * @returns Created issue identifier and URL
265
+ * @throws LinearServiceError if teamId not configured or creation fails
266
+ */
267
+ async createIssue(title, body, _repository, labels) {
268
+ if (!this.config.teamId) {
269
+ throw new LinearServiceError(
270
+ "INVALID_STATE",
271
+ "Linear teamId not configured. Run `il init` to configure Linear settings."
272
+ );
273
+ }
274
+ logger.info(`Creating Linear issue in team ${this.config.teamId}: ${title}`);
275
+ const result = await createLinearIssue(title, body, this.config.teamId, labels);
276
+ return {
277
+ number: result.identifier,
278
+ url: result.url
279
+ };
280
+ }
281
+ /**
282
+ * Get the web URL for a Linear issue
283
+ * @param identifier - Linear issue identifier
284
+ * @param _repo - Repository (unused for Linear)
285
+ * @returns Issue URL
286
+ */
287
+ async getIssueUrl(identifier, _repo) {
288
+ const issue = await this.fetchIssue(identifier);
289
+ return issue.url;
290
+ }
291
+ /**
292
+ * Move a Linear issue to "In Progress" state
293
+ * @param identifier - Linear issue identifier
294
+ * @throws LinearServiceError if state update fails
295
+ */
296
+ async moveIssueToInProgress(identifier) {
297
+ logger.info(`Moving Linear issue ${identifier} to In Progress`);
298
+ await updateLinearIssueState(String(identifier), "In Progress");
299
+ }
300
+ /**
301
+ * Extract issue context for AI prompts
302
+ * @param entity - Issue (Linear doesn't have PRs)
303
+ * @returns Formatted context string
304
+ */
305
+ extractContext(entity) {
306
+ const issue = entity;
307
+ return `Linear Issue ${issue.number}: ${issue.title}
308
+ State: ${issue.state}
309
+
310
+ ${issue.body}`;
311
+ }
312
+ /**
313
+ * Map Linear API issue to generic Issue type
314
+ * @param linear - Linear issue from SDK
315
+ * @returns Generic Issue type
316
+ */
317
+ mapLinearIssueToIssue(linear) {
318
+ return {
319
+ number: linear.identifier,
320
+ // Keep as string (e.g., "ENG-123")
321
+ title: linear.title,
322
+ body: linear.description ?? "",
323
+ state: linear.state ? linear.state.toLowerCase().includes("done") || linear.state.toLowerCase().includes("completed") || linear.state.toLowerCase().includes("canceled") ? "closed" : "open" : "open",
324
+ labels: [],
325
+ assignees: [],
326
+ url: linear.url
327
+ };
328
+ }
329
+ };
330
+
331
+ // src/lib/IssueTrackerFactory.ts
332
+ var IssueTrackerFactory = class {
333
+ /**
334
+ * Create an IssueTracker instance based on settings configuration
335
+ * Defaults to GitHub if no provider specified
336
+ *
337
+ * @param settings - iloom settings containing issueManagement.provider
338
+ * @returns IssueTracker instance configured for the specified provider
339
+ * @throws Error if provider type is not supported
340
+ */
341
+ static create(settings) {
342
+ var _a, _b;
343
+ const provider = ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
344
+ logger.debug(`IssueTrackerFactory: Creating tracker for provider "${provider}"`);
345
+ logger.debug(`IssueTrackerFactory: issueManagement settings:`, JSON.stringify(settings.issueManagement, null, 2));
346
+ switch (provider) {
347
+ case "github":
348
+ logger.debug("IssueTrackerFactory: Creating GitHubService");
349
+ return new GitHubService();
350
+ case "linear": {
351
+ const linearSettings = (_b = settings.issueManagement) == null ? void 0 : _b.linear;
352
+ const linearConfig = {};
353
+ if (linearSettings == null ? void 0 : linearSettings.teamId) {
354
+ linearConfig.teamId = linearSettings.teamId;
355
+ }
356
+ if (linearSettings == null ? void 0 : linearSettings.branchFormat) {
357
+ linearConfig.branchFormat = linearSettings.branchFormat;
358
+ }
359
+ logger.debug(`IssueTrackerFactory: Creating LinearService with config:`, JSON.stringify(linearConfig, null, 2));
360
+ return new LinearService(linearConfig);
361
+ }
362
+ default:
363
+ throw new Error(`Unsupported issue tracker provider: ${provider}`);
364
+ }
365
+ }
366
+ /**
367
+ * Get the configured provider name from settings
368
+ * Defaults to 'github' if not configured
369
+ *
370
+ * @param settings - iloom settings
371
+ * @returns Provider type string
372
+ */
373
+ static getProviderName(settings) {
374
+ var _a;
375
+ return ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
376
+ }
377
+ };
378
+
379
+ // src/utils/mcp.ts
380
+ import path from "path";
381
+ async function generateIssueManagementMcpConfig(contextType, repo, provider = "github", settings) {
382
+ var _a, _b;
383
+ let envVars = {
384
+ ISSUE_PROVIDER: provider
385
+ };
386
+ if (provider === "github") {
387
+ let owner;
388
+ let name;
389
+ if (repo) {
390
+ const parts = repo.split("/");
391
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
392
+ throw new Error(`Invalid repo format: ${repo}. Expected "owner/repo"`);
393
+ }
394
+ owner = parts[0];
395
+ name = parts[1];
396
+ } else {
397
+ const repoInfo = await getRepoInfo();
398
+ owner = repoInfo.owner;
399
+ name = repoInfo.name;
400
+ }
401
+ const githubEventName = contextType === "issue" ? "issues" : contextType === "pr" ? "pull_request" : void 0;
402
+ envVars = {
403
+ ...envVars,
404
+ REPO_OWNER: owner,
405
+ REPO_NAME: name,
406
+ GITHUB_API_URL: "https://api.github.com/",
407
+ ...githubEventName && { GITHUB_EVENT_NAME: githubEventName }
408
+ };
409
+ logger.debug("Generated MCP config for GitHub issue management", {
410
+ provider,
411
+ repoOwner: owner,
412
+ repoName: name,
413
+ contextType: contextType ?? "auto-detect",
414
+ githubEventName: githubEventName ?? "auto-detect"
415
+ });
416
+ } else {
417
+ const apiToken = ((_b = (_a = settings == null ? void 0 : settings.issueManagement) == null ? void 0 : _a.linear) == null ? void 0 : _b.apiToken) ?? process.env.LINEAR_API_TOKEN;
418
+ if (apiToken) {
419
+ envVars.LINEAR_API_TOKEN = apiToken;
420
+ }
421
+ logger.debug("Generated MCP config for Linear issue management", {
422
+ provider,
423
+ hasApiToken: !!apiToken,
424
+ contextType: contextType ?? "auto-detect"
425
+ });
426
+ }
427
+ const mcpServerConfig = {
428
+ mcpServers: {
429
+ issue_management: {
430
+ transport: "stdio",
431
+ command: "node",
432
+ args: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), "../dist/mcp/issue-management-server.js")],
433
+ env: envVars
434
+ }
435
+ }
436
+ };
437
+ return [mcpServerConfig];
438
+ }
439
+
440
+ export {
441
+ IssueTrackerFactory,
442
+ generateIssueManagementMcpConfig
443
+ };
444
+ //# sourceMappingURL=chunk-RO26VS3W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/linear.ts","../src/utils/linear.ts","../src/lib/LinearService.ts","../src/lib/IssueTrackerFactory.ts","../src/utils/mcp.ts"],"sourcesContent":["/**\n * Linear API response types (from @linear/sdk)\n */\n\n/**\n * Linear issue response from SDK\n */\nexport interface LinearIssue {\n /** Linear internal UUID */\n id: string\n /** Issue identifier in TEAM-NUMBER format (e.g., ENG-123) */\n identifier: string\n /** Issue title */\n title: string\n /** Issue description (markdown) */\n description?: string\n /** Current workflow state name */\n state?: string\n /** Linear web URL */\n url: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n}\n\n/**\n * Linear comment response from SDK\n */\nexport interface LinearComment {\n /** Comment UUID */\n id: string\n /** Comment body (markdown) */\n body: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n /** Comment URL */\n url: string\n}\n\n/**\n * Linear error codes\n */\nexport type LinearErrorCode =\n | 'NOT_FOUND'\n | 'UNAUTHORIZED'\n | 'INVALID_STATE'\n | 'RATE_LIMITED'\n | 'CLI_NOT_FOUND'\n | 'CLI_ERROR'\n\n/**\n * Linear error details\n */\nexport interface LinearError {\n code: LinearErrorCode\n message: string\n details?: unknown\n}\n\n/**\n * Custom error class for Linear operations\n */\nexport class LinearServiceError extends Error {\n constructor(\n public code: LinearErrorCode,\n message: string,\n public details?: unknown,\n ) {\n super(message)\n this.name = 'LinearServiceError'\n // Maintain proper stack trace for where error was thrown (V8 only)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LinearServiceError)\n }\n }\n}\n","/**\n * Linear SDK utilities\n * Wrapper functions for the @linear/sdk\n */\n\nimport { LinearClient } from '@linear/sdk'\nimport type { LinearIssue, LinearComment } from '../types/linear.js'\nimport { LinearServiceError } from '../types/linear.js'\nimport { logger } from './logger.js'\n\n/**\n * Slugify a title for use in Linear URLs\n * Converts to lowercase, replaces non-alphanumeric with hyphens, truncates to reasonable length\n * @param title - Issue title\n * @param maxLength - Maximum slug length (default: 50)\n * @returns Slugified title\n */\nexport function slugifyTitle(title: string, maxLength: number = 50): string {\n // Convert to lowercase, replace non-alphanumeric chars with hyphens\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n\n // If already short enough, return as-is\n if (slug.length <= maxLength) {\n return slug\n }\n\n // Split by hyphens and rebuild until we hit the limit\n const parts = slug.split('-')\n let result = ''\n for (const part of parts) {\n const candidate = result ? `${result}-${part}` : part\n if (candidate.length > maxLength) {\n break\n }\n result = candidate\n }\n\n return result || slug.slice(0, maxLength) // fallback if first part is too long\n}\n\n/**\n * Build a Linear issue URL with optional title slug\n * @param identifier - Issue identifier (e.g., \"ENG-123\")\n * @param title - Optional issue title for slug\n * @returns Linear URL\n */\nexport function buildLinearIssueUrl(identifier: string, title?: string): string {\n const base = `https://linear.app/issue/${identifier}`\n if (title) {\n const slug = slugifyTitle(title)\n return slug ? `${base}/${slug}` : base\n }\n return base\n}\n\n/**\n * Get Linear API token from environment\n * @returns API token\n * @throws LinearServiceError if token not found\n */\nfunction getLinearApiToken(): string {\n const token = process.env.LINEAR_API_TOKEN\n if (!token) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable.',\n )\n }\n return token\n}\n\n/**\n * Create a Linear SDK client instance\n * @returns Configured LinearClient\n */\nfunction createLinearClient(): LinearClient {\n return new LinearClient({ apiKey: getLinearApiToken() })\n}\n\n/**\n * Handle SDK errors and convert to LinearServiceError\n * @param error - Error from SDK\n * @param context - Context string for debugging\n * @throws LinearServiceError\n */\nfunction handleLinearError(error: unknown, context: string): never {\n logger.debug(`${context}: Handling error`, { error })\n\n // SDK errors typically have a message property\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n // Map common error patterns\n if (errorMessage.includes('not found') || errorMessage.includes('Not found')) {\n throw new LinearServiceError('NOT_FOUND', 'Linear issue or resource not found', { error })\n }\n\n if (\n errorMessage.includes('unauthorized') ||\n errorMessage.includes('Unauthorized') ||\n errorMessage.includes('Invalid API key')\n ) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'Linear authentication failed. Check LINEAR_API_TOKEN.',\n { error },\n )\n }\n\n if (errorMessage.includes('rate limit')) {\n throw new LinearServiceError('RATE_LIMITED', 'Linear API rate limit exceeded', { error })\n }\n\n // Generic SDK error\n throw new LinearServiceError('CLI_ERROR', `Linear SDK error: ${errorMessage}`, { error })\n}\n\n/**\n * Fetch a Linear issue by identifier\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Linear issue details\n * @throws LinearServiceError if issue not found or SDK error\n */\nexport async function fetchLinearIssue(identifier: string): Promise<LinearIssue> {\n try {\n logger.debug(`Fetching Linear issue: ${identifier}`)\n const client = createLinearClient()\n const issue = await client.issue(identifier)\n\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Convert SDK issue to our LinearIssue type\n const result: LinearIssue = {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n createdAt: issue.createdAt.toISOString(),\n updatedAt: issue.updatedAt.toISOString(),\n }\n\n // Add optional fields if present\n if (issue.description) {\n result.description = issue.description\n }\n\n if (issue.state) {\n const state = await issue.state\n if (state?.name) {\n result.state = state.name\n }\n }\n\n return result\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssue')\n }\n}\n\n/**\n * Create a new Linear issue\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param teamKey - Team key (e.g., \"ENG\", \"PLAT\")\n * @param labels - Optional label names to apply\n * @returns Created issue identifier and URL\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearIssue(\n title: string,\n body: string,\n teamKey: string,\n _labels?: string[],\n): Promise<{ identifier: string; url: string }> {\n try {\n logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`)\n const client = createLinearClient()\n\n // Get team by key\n const teams = await client.teams()\n const team = teams.nodes.find((t) => t.key === teamKey)\n\n if (!team) {\n throw new LinearServiceError('NOT_FOUND', `Linear team ${teamKey} not found`)\n }\n\n // Create issue\n const issueInput: { teamId: string; title: string; description?: string } = {\n teamId: team.id,\n title,\n }\n\n if (body) {\n issueInput.description = body\n }\n\n const payload = await client.createIssue(issueInput)\n\n const issue = await payload.issue\n\n if (!issue) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear issue')\n }\n\n // Construct URL\n const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title)\n\n return {\n identifier: issue.identifier,\n url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearIssue')\n }\n}\n\n/**\n * Create a comment on a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param body - Comment body (markdown)\n * @returns Created comment details\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearComment(\n identifier: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Creating comment on Linear issue ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier to get its ID\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Create comment using issue ID\n const payload = await client.createComment({\n issueId: issue.id,\n body,\n })\n\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearComment')\n }\n}\n\n/**\n * Update a Linear issue's workflow state\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param stateName - Target state name (e.g., \"In Progress\", \"Done\")\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearIssueState(\n identifier: string,\n stateName: string,\n): Promise<void> {\n try {\n logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Get team to find state\n const team = await issue.team\n if (!team) {\n throw new LinearServiceError('CLI_ERROR', 'Issue has no team')\n }\n\n // Find state by name\n const states = await team.states()\n const state = states.nodes.find((s) => s.name === stateName)\n\n if (!state) {\n throw new LinearServiceError(\n 'NOT_FOUND',\n `State \"${stateName}\" not found in team ${team.key}`,\n )\n }\n\n // Update issue state\n await client.updateIssue(issue.id, {\n stateId: state.id,\n })\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearIssueState')\n }\n}\n\n/**\n * Get a specific comment by ID\n * @param commentId - Linear comment UUID\n * @returns Comment details\n * @throws LinearServiceError if comment not found\n */\nexport async function getLinearComment(commentId: string): Promise<LinearComment> {\n try {\n logger.debug(`Fetching Linear comment: ${commentId}`)\n const client = createLinearClient()\n const comment = await client.comment({ id: commentId })\n\n if (!comment) {\n throw new LinearServiceError('NOT_FOUND', `Linear comment ${commentId} not found`)\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'getLinearComment')\n }\n}\n\n/**\n * Update an existing comment\n * @param commentId - Linear comment UUID\n * @param body - New comment body (markdown)\n * @returns Updated comment details\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearComment(\n commentId: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Updating Linear comment: ${commentId}`)\n const client = createLinearClient()\n\n const payload = await client.updateComment(commentId, { body })\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to update Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearComment')\n }\n}\n\n/**\n * Fetch all comments for a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Array of comments\n * @throws LinearServiceError on fetch failure\n */\nexport async function fetchLinearIssueComments(identifier: string): Promise<LinearComment[]> {\n try {\n logger.debug(`Fetching comments for Linear issue: ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Fetch comments\n const comments = await issue.comments({ first: 100 })\n\n return comments.nodes.map((comment) => ({\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }))\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssueComments')\n }\n}\n","/**\n * LinearService - IssueTracker implementation for Linear\n * Implements issue tracking operations using the @linear/sdk\n */\n\nimport type { Issue, PullRequest, IssueTrackerInputDetection } from '../types/index.js'\nimport type { LinearIssue } from '../types/linear.js'\nimport { LinearServiceError } from '../types/linear.js'\nimport {\n fetchLinearIssue,\n createLinearIssue,\n updateLinearIssueState,\n} from '../utils/linear.js'\nimport { promptConfirmation } from '../utils/prompt.js'\nimport type { IssueTracker } from './IssueTracker.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Linear service configuration options\n */\nexport interface LinearServiceConfig {\n /** Linear team key (e.g., \"ENG\", \"PLAT\") */\n teamId?: string\n /** Branch naming template (e.g., \"feat/{{key}}__{{title}}\") */\n branchFormat?: string\n}\n\n/**\n * Linear implementation of IssueTracker interface\n */\nexport class LinearService implements IssueTracker {\n // IssueTracker interface implementation\n readonly providerName = 'linear'\n readonly supportsPullRequests = false // Linear doesn't have pull requests\n\n private config: LinearServiceConfig\n private prompter: (message: string) => Promise<boolean>\n\n constructor(\n config?: LinearServiceConfig,\n options?: { prompter?: (message: string) => Promise<boolean> },\n ) {\n this.config = config ?? {}\n this.prompter = options?.prompter ?? promptConfirmation\n }\n\n /**\n * Detect if input matches Linear identifier format (TEAM-NUMBER)\n * @param input - User input string\n * @param _repo - Repository (unused for Linear)\n * @returns Detection result with type and identifier\n */\n public async detectInputType(\n input: string,\n _repo?: string,\n ): Promise<IssueTrackerInputDetection> {\n logger.debug(`LinearService.detectInputType called with input: \"${input}\"`)\n\n // Pattern: TEAM-NUMBER (e.g., ENG-123, PLAT-456)\n // Requires at least 2 letters before dash to avoid conflict with PR-123 format\n const linearPattern = /^([A-Z]{2,}-\\d+)$/i\n const match = input.match(linearPattern)\n\n if (!match?.[1]) {\n logger.debug(`LinearService: Input \"${input}\" does not match Linear pattern`)\n return { type: 'unknown', identifier: null, rawInput: input }\n }\n\n const identifier = match[1].toUpperCase()\n logger.debug(`LinearService: Matched Linear identifier: ${identifier}`)\n\n // Validate the issue exists in Linear\n logger.debug(`LinearService: Checking if ${identifier} is a valid Linear issue via SDK`)\n const issue = await this.isValidIssue(identifier)\n\n if (issue) {\n logger.debug(`LinearService: Issue ${identifier} found: \"${issue.title}\"`)\n return { type: 'issue', identifier, rawInput: input }\n }\n\n // Not found\n logger.debug(`LinearService: Issue ${identifier} NOT found by SDK`)\n return { type: 'unknown', identifier: null, rawInput: input }\n }\n\n /**\n * Fetch a Linear issue by identifier\n * @param identifier - Linear issue identifier (string or number)\n * @param _repo - Repository (unused for Linear)\n * @returns Generic Issue type\n * @throws LinearServiceError if issue not found\n */\n public async fetchIssue(identifier: string | number, _repo?: string): Promise<Issue> {\n const linearIssue = await fetchLinearIssue(String(identifier))\n return this.mapLinearIssueToIssue(linearIssue)\n }\n\n /**\n * Check if an issue identifier is valid (silent validation)\n * @param identifier - Linear issue identifier\n * @param _repo - Repository (unused for Linear)\n * @returns Issue if valid, false if not found\n */\n public async isValidIssue(identifier: string | number, _repo?: string): Promise<Issue | false> {\n try {\n return await this.fetchIssue(identifier)\n } catch (error) {\n // Return false for NOT_FOUND errors (expected during detection)\n if (error instanceof LinearServiceError && error.code === 'NOT_FOUND') {\n return false\n }\n // Re-throw unexpected errors\n throw error\n }\n }\n\n /**\n * Validate issue state and prompt user if closed\n * @param issue - Issue to validate\n * @throws LinearServiceError if user cancels due to closed issue\n */\n public async validateIssueState(issue: Issue): Promise<void> {\n if (issue.state === 'closed') {\n const shouldContinue = await this.prompter(\n `Issue ${issue.number} is closed. Continue anyway?`,\n )\n\n if (!shouldContinue) {\n throw new LinearServiceError('INVALID_STATE', 'User cancelled due to closed issue')\n }\n }\n }\n\n /**\n * Create a new Linear issue\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param _repository - Repository (unused for Linear)\n * @param labels - Optional label names\n * @returns Created issue identifier and URL\n * @throws LinearServiceError if teamId not configured or creation fails\n */\n public async createIssue(\n title: string,\n body: string,\n _repository?: string,\n labels?: string[],\n ): Promise<{ number: string | number; url: string }> {\n // Require teamId configuration\n if (!this.config.teamId) {\n throw new LinearServiceError(\n 'INVALID_STATE',\n 'Linear teamId not configured. Run `il init` to configure Linear settings.',\n )\n }\n\n logger.info(`Creating Linear issue in team ${this.config.teamId}: ${title}`)\n\n const result = await createLinearIssue(title, body, this.config.teamId, labels)\n\n return {\n number: result.identifier,\n url: result.url,\n }\n }\n\n /**\n * Get the web URL for a Linear issue\n * @param identifier - Linear issue identifier\n * @param _repo - Repository (unused for Linear)\n * @returns Issue URL\n */\n public async getIssueUrl(identifier: string | number, _repo?: string): Promise<string> {\n const issue = await this.fetchIssue(identifier)\n return issue.url\n }\n\n /**\n * Move a Linear issue to \"In Progress\" state\n * @param identifier - Linear issue identifier\n * @throws LinearServiceError if state update fails\n */\n public async moveIssueToInProgress(identifier: string | number): Promise<void> {\n logger.info(`Moving Linear issue ${identifier} to In Progress`)\n await updateLinearIssueState(String(identifier), 'In Progress')\n }\n\n /**\n * Extract issue context for AI prompts\n * @param entity - Issue (Linear doesn't have PRs)\n * @returns Formatted context string\n */\n public extractContext(entity: Issue | PullRequest): string {\n // Linear doesn't have PRs, always an issue\n const issue = entity as Issue\n return `Linear Issue ${issue.number}: ${issue.title}\\nState: ${issue.state}\\n\\n${issue.body}`\n }\n\n /**\n * Map Linear API issue to generic Issue type\n * @param linear - Linear issue from SDK\n * @returns Generic Issue type\n */\n private mapLinearIssueToIssue(linear: LinearIssue): Issue {\n return {\n number: linear.identifier, // Keep as string (e.g., \"ENG-123\")\n title: linear.title,\n body: linear.description ?? '',\n state: linear.state ? (linear.state.toLowerCase().includes('done') || linear.state.toLowerCase().includes('completed') || linear.state.toLowerCase().includes('canceled') ? 'closed' : 'open') : 'open',\n labels: [],\n assignees: [],\n url: linear.url,\n }\n }\n}\n","// IssueTrackerFactory - creates appropriate IssueTracker based on settings\n// Follows pattern from database provider instantiation\n\nimport type { IssueTracker } from './IssueTracker.js'\nimport { GitHubService } from './GitHubService.js'\nimport { LinearService, type LinearServiceConfig } from './LinearService.js'\nimport type { IloomSettings } from './SettingsManager.js'\nimport { logger } from '../utils/logger.js'\n\nexport type IssueTrackerProviderType = 'github' | 'linear'\n\n/**\n * Factory for creating IssueTracker instances based on settings\n * Provides a single point of provider instantiation\n *\n * Usage:\n * const tracker = IssueTrackerFactory.create(settings, { useClaude: true })\n * const issue = await tracker.fetchIssue(123)\n */\nexport class IssueTrackerFactory {\n\t/**\n\t * Create an IssueTracker instance based on settings configuration\n\t * Defaults to GitHub if no provider specified\n\t *\n\t * @param settings - iloom settings containing issueManagement.provider\n\t * @returns IssueTracker instance configured for the specified provider\n\t * @throws Error if provider type is not supported\n\t */\n\tstatic create(settings: IloomSettings): IssueTracker {\n\t\tconst provider = settings.issueManagement?.provider ?? 'github'\n\n\t\tlogger.debug(`IssueTrackerFactory: Creating tracker for provider \"${provider}\"`)\n\t\tlogger.debug(`IssueTrackerFactory: issueManagement settings:`, JSON.stringify(settings.issueManagement, null, 2))\n\n\t\tswitch (provider) {\n\t\t\tcase 'github':\n\t\t\t\tlogger.debug('IssueTrackerFactory: Creating GitHubService')\n\t\t\t\treturn new GitHubService()\n\t\t\tcase 'linear': {\n\t\t\t\tconst linearSettings = settings.issueManagement?.linear\n\t\t\t\tconst linearConfig: LinearServiceConfig = {}\n\n\t\t\t\tif (linearSettings?.teamId) {\n\t\t\t\t\tlinearConfig.teamId = linearSettings.teamId\n\t\t\t\t}\n\t\t\t\tif (linearSettings?.branchFormat) {\n\t\t\t\t\tlinearConfig.branchFormat = linearSettings.branchFormat\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(`IssueTrackerFactory: Creating LinearService with config:`, JSON.stringify(linearConfig, null, 2))\n\t\t\t\treturn new LinearService(linearConfig)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported issue tracker provider: ${provider}`)\n\t\t}\n\t}\n\n\t/**\n\t * Get the configured provider name from settings\n\t * Defaults to 'github' if not configured\n\t *\n\t * @param settings - iloom settings\n\t * @returns Provider type string\n\t */\n\tstatic getProviderName(settings: IloomSettings): IssueTrackerProviderType {\n\t\treturn (settings.issueManagement?.provider ?? 'github') as IssueTrackerProviderType\n\t}\n}\n","import path from 'path'\nimport { getRepoInfo } from './github.js'\nimport { logger } from './logger.js'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Generate MCP configuration for issue management\n * Uses a single server that can handle both issues and pull requests\n * Returns array of MCP server config objects\n * @param contextType - Optional context type (issue or pr)\n * @param repo - Optional repo in \"owner/repo\" format. If not provided, will auto-detect from git.\n * @param provider - Issue management provider (default: 'github')\n * @param settings - Optional settings to extract Linear API token from\n */\nexport async function generateIssueManagementMcpConfig(\n\tcontextType?: 'issue' | 'pr',\n\trepo?: string,\n\tprovider: 'github' | 'linear' = 'github',\n\tsettings?: IloomSettings\n): Promise<Record<string, unknown>[]> {\n\t// Build provider-specific environment variables\n\tlet envVars: Record<string, string> = {\n\t\tISSUE_PROVIDER: provider,\n\t}\n\n\tif (provider === 'github') {\n\t\t// Get repository information for GitHub - either from provided repo string or auto-detect\n\t\tlet owner: string\n\t\tlet name: string\n\n\t\tif (repo) {\n\t\t\tconst parts = repo.split('/')\n\t\t\tif (parts.length !== 2 || !parts[0] || !parts[1]) {\n\t\t\t\tthrow new Error(`Invalid repo format: ${repo}. Expected \"owner/repo\"`)\n\t\t\t}\n\t\t\towner = parts[0]\n\t\t\tname = parts[1]\n\t\t} else {\n\t\t\tconst repoInfo = await getRepoInfo()\n\t\t\towner = repoInfo.owner\n\t\t\tname = repoInfo.name\n\t\t}\n\n\t\t// Map logical types to GitHub's webhook event names (handle GitHub's naming quirk here)\n\t\tconst githubEventName = contextType === 'issue' ? 'issues' : contextType === 'pr' ? 'pull_request' : undefined\n\n\t\tenvVars = {\n\t\t\t...envVars,\n\t\t\tREPO_OWNER: owner,\n\t\t\tREPO_NAME: name,\n\t\t\tGITHUB_API_URL: 'https://api.github.com/',\n\t\t\t...(githubEventName && { GITHUB_EVENT_NAME: githubEventName }),\n\t\t}\n\n\t\tlogger.debug('Generated MCP config for GitHub issue management', {\n\t\t\tprovider,\n\t\t\trepoOwner: owner,\n\t\t\trepoName: name,\n\t\t\tcontextType: contextType ?? 'auto-detect',\n\t\t\tgithubEventName: githubEventName ?? 'auto-detect'\n\t\t})\n\t} else {\n\t\t// Linear needs API token passed through\n\t\tconst apiToken = settings?.issueManagement?.linear?.apiToken ?? process.env.LINEAR_API_TOKEN\n\n\t\tif (apiToken) {\n\t\t\tenvVars.LINEAR_API_TOKEN = apiToken\n\t\t}\n\n\t\tlogger.debug('Generated MCP config for Linear issue management', {\n\t\t\tprovider,\n\t\t\thasApiToken: !!apiToken,\n\t\t\tcontextType: contextType ?? 'auto-detect',\n\t\t})\n\t}\n\n\t// Generate single MCP server config\n\tconst mcpServerConfig = {\n\t\tmcpServers: {\n\t\t\tissue_management: {\n\t\t\t\ttransport: 'stdio',\n\t\t\t\tcommand: 'node',\n\t\t\t\targs: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), '../dist/mcp/issue-management-server.js')],\n\t\t\t\tenv: envVars,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn [mcpServerConfig]\n}"],"mappings":"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","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  isPRBranch,
12
12
  isValidGitRepo,
13
13
  parseWorktreeList
14
- } from "./chunk-HBVFXN7R.js";
14
+ } from "./chunk-4BGK7T6X.js";
15
15
 
16
16
  // src/lib/GitWorktreeManager.ts
17
17
  import path from "path";
@@ -321,7 +321,7 @@ var GitWorktreeManager = class {
321
321
  */
322
322
  async findWorktreeForIssue(issueNumber) {
323
323
  const worktrees = await this.listWorktrees({ porcelain: true });
324
- const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|$)`);
324
+ const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|__|$)`, "i");
325
325
  return worktrees.find((wt) => pattern.test(wt.branch)) ?? null;
326
326
  }
327
327
  /**
@@ -386,4 +386,4 @@ var GitWorktreeManager = class {
386
386
  export {
387
387
  GitWorktreeManager
388
388
  };
389
- //# sourceMappingURL=chunk-VETG35MF.js.map
389
+ //# sourceMappingURL=chunk-TSKY3JI7.js.map