@iloom/cli 0.3.3 → 0.4.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 (178) hide show
  1. package/README.md +13 -3
  2. package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-TOM2KAUT.js} +4 -3
  3. package/dist/ClaudeContextManager-VEGJTS5E.js +16 -0
  4. package/dist/ClaudeService-ICSHJMQ5.js +15 -0
  5. package/dist/GitHubService-RPM27GWD.js +12 -0
  6. package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-SJBZFZXE.js} +25 -22
  7. package/dist/LoomLauncher-SJBZFZXE.js.map +1 -0
  8. package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
  9. package/dist/README.md +13 -3
  10. package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
  11. package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
  12. package/dist/{chunk-UJL4HI2R.js → chunk-3NFBZRPR.js} +2 -2
  13. package/dist/chunk-6UIGZD2N.js +20 -0
  14. package/dist/chunk-6UIGZD2N.js.map +1 -0
  15. package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
  16. package/dist/chunk-74VMN2KC.js.map +1 -0
  17. package/dist/{chunk-OYF4VIFI.js → chunk-75B2HZZ5.js} +147 -22
  18. package/dist/chunk-75B2HZZ5.js.map +1 -0
  19. package/dist/{chunk-PGPI5LR4.js → chunk-ADDNFQJ4.js} +7 -21
  20. package/dist/chunk-ADDNFQJ4.js.map +1 -0
  21. package/dist/{chunk-AKUJXDNW.js → chunk-F4J6KEL6.js} +3 -3
  22. package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
  23. package/dist/chunk-HD5SUKI2.js.map +1 -0
  24. package/dist/chunk-HHDSIE72.js +667 -0
  25. package/dist/chunk-HHDSIE72.js.map +1 -0
  26. package/dist/{chunk-OXAM2WVC.js → chunk-HVGQP44L.js} +21 -1
  27. package/dist/chunk-HVGQP44L.js.map +1 -0
  28. package/dist/{chunk-RW54ZMBM.js → chunk-JJUPY5MM.js} +2 -2
  29. package/dist/{chunk-UAN4A3YU.js → chunk-KM3W7YQX.js} +11 -11
  30. package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
  31. package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
  32. package/dist/{chunk-2MAIX45J.js → chunk-LTNDJMTH.js} +104 -43
  33. package/dist/chunk-LTNDJMTH.js.map +1 -0
  34. package/dist/{chunk-2CXREBLZ.js → chunk-M5XUCTTJ.js} +8 -6
  35. package/dist/chunk-M5XUCTTJ.js.map +1 -0
  36. package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
  37. package/dist/chunk-MLS5FAV7.js +189 -0
  38. package/dist/chunk-MLS5FAV7.js.map +1 -0
  39. package/dist/{chunk-2IJEMXOB.js → chunk-NFVFVYAP.js} +419 -427
  40. package/dist/chunk-NFVFVYAP.js.map +1 -0
  41. package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
  42. package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
  43. package/dist/chunk-OEGECBFS.js.map +1 -0
  44. package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
  45. package/dist/chunk-OF7BNW4D.js.map +1 -0
  46. package/dist/{chunk-SUOXY5WJ.js → chunk-P2WZIDF3.js} +5 -5
  47. package/dist/chunk-P2WZIDF3.js.map +1 -0
  48. package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
  49. package/dist/chunk-QHA67Q7A.js +281 -0
  50. package/dist/chunk-QHA67Q7A.js.map +1 -0
  51. package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
  52. package/dist/{chunk-IFB4Z76W.js → chunk-S44CHE3G.js} +13 -12
  53. package/dist/chunk-S44CHE3G.js.map +1 -0
  54. package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
  55. package/dist/chunk-SJ2GZ6RF.js.map +1 -0
  56. package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
  57. package/dist/{chunk-5Q3NDNNV.js → chunk-TR5MC2U6.js} +153 -6
  58. package/dist/chunk-TR5MC2U6.js.map +1 -0
  59. package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
  60. package/dist/chunk-UNXRACJ7.js.map +1 -0
  61. package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
  62. package/dist/chunk-UYVWLISQ.js.map +1 -0
  63. package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
  64. package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
  65. package/dist/chunk-VBFDVGAE.js.map +1 -0
  66. package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
  67. package/dist/chunk-VWNS6DH5.js.map +1 -0
  68. package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
  69. package/dist/{claude-W52VKI6L.js → claude-X7EBJRB2.js} +8 -5
  70. package/dist/{cleanup-H4VXU3C3.js → cleanup-7QVPYBJJ.js} +133 -122
  71. package/dist/cleanup-7QVPYBJJ.js.map +1 -0
  72. package/dist/cli.js +901 -425
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
  75. package/dist/{contribute-Y7IQV5QY.js → contribute-RZYCYUDX.js} +8 -6
  76. package/dist/{contribute-Y7IQV5QY.js.map → contribute-RZYCYUDX.js.map} +1 -1
  77. package/dist/dev-server-LOY7YWCP.js +298 -0
  78. package/dist/dev-server-LOY7YWCP.js.map +1 -0
  79. package/dist/{feedback-XTUCKJNT.js → feedback-562KPG5U.js} +13 -12
  80. package/dist/{feedback-XTUCKJNT.js.map → feedback-562KPG5U.js.map} +1 -1
  81. package/dist/{git-IYA53VIC.js → git-OXJACVAU.js} +16 -4
  82. package/dist/hooks/iloom-hook.js +258 -0
  83. package/dist/{ignite-T74RYXCA.js → ignite-VSIPGKKG.js} +245 -39
  84. package/dist/ignite-VSIPGKKG.js.map +1 -0
  85. package/dist/index.d.ts +459 -124
  86. package/dist/index.js +740 -210
  87. package/dist/index.js.map +1 -1
  88. package/dist/init-SCR2LQ4A.js +21 -0
  89. package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
  90. package/dist/mcp/issue-management-server.js +2 -1
  91. package/dist/mcp/issue-management-server.js.map +1 -1
  92. package/dist/neon-helpers-L5CXQ5CT.js +11 -0
  93. package/dist/{open-UMXANW5S.js → open-CX7HUE26.js} +12 -10
  94. package/dist/{open-UMXANW5S.js.map → open-CX7HUE26.js.map} +1 -1
  95. package/dist/projects-6DTNDVLH.js +73 -0
  96. package/dist/projects-6DTNDVLH.js.map +1 -0
  97. package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
  98. package/dist/prompts/init-prompt.txt +49 -0
  99. package/dist/prompts/issue-prompt.txt +110 -8
  100. package/dist/prompts/regular-prompt.txt +463 -14
  101. package/dist/prompts/session-summary-prompt.txt +82 -0
  102. package/dist/{rebase-VJ2VKR6R.js → rebase-55URTXZC.js} +11 -9
  103. package/dist/{rebase-VJ2VKR6R.js.map → rebase-55URTXZC.js.map} +1 -1
  104. package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
  105. package/dist/{run-MJYY4PUT.js → run-DP2U2CA2.js} +12 -10
  106. package/dist/{run-MJYY4PUT.js.map → run-DP2U2CA2.js.map} +1 -1
  107. package/dist/schema/settings.schema.json +49 -0
  108. package/dist/summary-J3CJSM7L.js +244 -0
  109. package/dist/summary-J3CJSM7L.js.map +1 -0
  110. package/dist/{test-git-IT5EWQ5C.js → test-git-QLAIBJLX.js} +6 -4
  111. package/dist/{test-git-IT5EWQ5C.js.map → test-git-QLAIBJLX.js.map} +1 -1
  112. package/dist/{test-prefix-NPWDPUUH.js → test-prefix-6YM2ZOON.js} +6 -4
  113. package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-6YM2ZOON.js.map} +1 -1
  114. package/dist/{test-tabs-PRMRSHKI.js → test-tabs-JGO3VOXJ.js} +4 -4
  115. package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
  116. package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
  117. package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
  118. package/package.json +6 -6
  119. package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
  120. package/dist/ClaudeService-DLYLJUPA.js +0 -14
  121. package/dist/GitHubService-FZHHBOFG.js +0 -11
  122. package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
  123. package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
  124. package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
  125. package/dist/chunk-2CXREBLZ.js.map +0 -1
  126. package/dist/chunk-2IJEMXOB.js.map +0 -1
  127. package/dist/chunk-2MAIX45J.js.map +0 -1
  128. package/dist/chunk-5Q3NDNNV.js.map +0 -1
  129. package/dist/chunk-5VK4NRSF.js.map +0 -1
  130. package/dist/chunk-CDZERT7Z.js.map +0 -1
  131. package/dist/chunk-CE26YH2U.js.map +0 -1
  132. package/dist/chunk-DLHA5VQ3.js.map +0 -1
  133. package/dist/chunk-GEHQXLEI.js.map +0 -1
  134. package/dist/chunk-IFB4Z76W.js.map +0 -1
  135. package/dist/chunk-M7JJCX53.js.map +0 -1
  136. package/dist/chunk-MKWYLDFK.js.map +0 -1
  137. package/dist/chunk-OXAM2WVC.js.map +0 -1
  138. package/dist/chunk-OYF4VIFI.js.map +0 -1
  139. package/dist/chunk-PGPI5LR4.js.map +0 -1
  140. package/dist/chunk-RIEO2WML.js.map +0 -1
  141. package/dist/chunk-SUOXY5WJ.js.map +0 -1
  142. package/dist/chunk-ZT3YZB4K.js.map +0 -1
  143. package/dist/cleanup-H4VXU3C3.js.map +0 -1
  144. package/dist/ignite-T74RYXCA.js.map +0 -1
  145. package/dist/init-4FHTAM3F.js +0 -19
  146. package/dist/logger-MKYH4UDV.js +0 -12
  147. package/dist/neon-helpers-77PBPGJ5.js +0 -10
  148. package/dist/update-notifier-QEX3CJHA.js.map +0 -1
  149. /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-TOM2KAUT.js.map} +0 -0
  150. /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-VEGJTS5E.js.map} +0 -0
  151. /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-ICSHJMQ5.js.map} +0 -0
  152. /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
  153. /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
  154. /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
  155. /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
  156. /package/dist/{chunk-UJL4HI2R.js.map → chunk-3NFBZRPR.js.map} +0 -0
  157. /package/dist/{chunk-AKUJXDNW.js.map → chunk-F4J6KEL6.js.map} +0 -0
  158. /package/dist/{chunk-RW54ZMBM.js.map → chunk-JJUPY5MM.js.map} +0 -0
  159. /package/dist/{chunk-UAN4A3YU.js.map → chunk-KM3W7YQX.js.map} +0 -0
  160. /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
  161. /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
  162. /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
  163. /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
  164. /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
  165. /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
  166. /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
  167. /package/dist/{claude-W52VKI6L.js.map → claude-X7EBJRB2.js.map} +0 -0
  168. /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
  169. /package/dist/{git-IYA53VIC.js.map → git-OXJACVAU.js.map} +0 -0
  170. /package/dist/{init-4FHTAM3F.js.map → init-SCR2LQ4A.js.map} +0 -0
  171. /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
  172. /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
  173. /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
  174. /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
  175. /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-JGO3VOXJ.js.map} +0 -0
  176. /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
  177. /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
  178. /package/dist/{remote-VUNCQZ6J.js.map → update-notifier-H55ZK7NU.js.map} +0 -0
@@ -0,0 +1,667 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createLinearComment,
4
+ fetchLinearIssue,
5
+ fetchLinearIssueComments,
6
+ getLinearComment,
7
+ updateLinearComment
8
+ } from "./chunk-QHA67Q7A.js";
9
+ import {
10
+ MetadataManager
11
+ } from "./chunk-MLS5FAV7.js";
12
+ import {
13
+ SettingsManager
14
+ } from "./chunk-VWNS6DH5.js";
15
+ import {
16
+ createIssueComment,
17
+ createPRComment,
18
+ executeGhCommand,
19
+ updateIssueComment
20
+ } from "./chunk-KO2FOMHL.js";
21
+ import {
22
+ generateDeterministicSessionId,
23
+ launchClaude
24
+ } from "./chunk-75B2HZZ5.js";
25
+ import {
26
+ PromptTemplateManager
27
+ } from "./chunk-74VMN2KC.js";
28
+ import {
29
+ logger
30
+ } from "./chunk-UYVWLISQ.js";
31
+
32
+ // src/utils/claude-transcript.ts
33
+ import { readFile } from "fs/promises";
34
+ import { homedir } from "os";
35
+ import { join } from "path";
36
+ function getClaudeProjectPath(worktreePath) {
37
+ return worktreePath.replace(/[/_]/g, "-");
38
+ }
39
+ function getClaudeProjectsDir() {
40
+ return join(homedir(), ".claude", "projects");
41
+ }
42
+ function findSessionTranscript(worktreePath, sessionId) {
43
+ const projectsDir = getClaudeProjectsDir();
44
+ const projectDirName = getClaudeProjectPath(worktreePath);
45
+ const transcriptPath = join(projectsDir, projectDirName, `${sessionId}.jsonl`);
46
+ return transcriptPath;
47
+ }
48
+ function extractMessageContent(message) {
49
+ if (!message) return null;
50
+ if (typeof message.content === "string") {
51
+ return message.content;
52
+ }
53
+ if (Array.isArray(message.content)) {
54
+ return message.content.filter((item) => item.type === "text" && item.text).map((item) => item.text).join("\n");
55
+ }
56
+ return null;
57
+ }
58
+ async function extractCompactSummaries(transcriptPath, maxSummaries = 3) {
59
+ try {
60
+ const content = await readFile(transcriptPath, "utf-8");
61
+ const lines = content.split("\n").filter((line) => line.trim());
62
+ const summaries = [];
63
+ for (const line of lines) {
64
+ try {
65
+ const entry = JSON.parse(line);
66
+ if (entry.isCompactSummary === true && entry.message) {
67
+ const summaryContent = extractMessageContent(entry.message);
68
+ if (summaryContent) {
69
+ summaries.push(summaryContent);
70
+ }
71
+ }
72
+ } catch {
73
+ logger.debug("Skipping malformed JSONL line in transcript");
74
+ }
75
+ }
76
+ return summaries.slice(-maxSummaries);
77
+ } catch (error) {
78
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
79
+ logger.debug("Transcript file not found:", transcriptPath);
80
+ } else {
81
+ logger.debug("Error reading transcript file:", error);
82
+ }
83
+ return [];
84
+ }
85
+ }
86
+ async function readSessionContext(worktreePath, sessionId, maxSummaries = 3) {
87
+ const transcriptPath = findSessionTranscript(worktreePath, sessionId);
88
+ if (!transcriptPath) {
89
+ return null;
90
+ }
91
+ logger.debug(`Checking transcript at: ${transcriptPath}`);
92
+ const summaries = await extractCompactSummaries(transcriptPath, maxSummaries);
93
+ if (summaries.length === 0) {
94
+ return null;
95
+ }
96
+ const formattedSummaries = summaries.reverse().map((summary, index) => {
97
+ const header = summaries.length > 1 ? `### Compact Summary ${index + 1} of ${summaries.length}
98
+
99
+ ` : "";
100
+ return `${header}${summary}`;
101
+ }).join("\n\n---\n\n");
102
+ return formattedSummaries;
103
+ }
104
+
105
+ // src/mcp/GitHubIssueManagementProvider.ts
106
+ function normalizeAuthor(author) {
107
+ if (!author) return null;
108
+ return {
109
+ id: author.id ? String(author.id) : author.login,
110
+ displayName: author.login,
111
+ // GitHub uses login as primary identifier
112
+ login: author.login,
113
+ // Preserve original GitHub field
114
+ ...author.avatarUrl && { avatarUrl: author.avatarUrl },
115
+ ...author.url && { url: author.url }
116
+ };
117
+ }
118
+ function extractNumericIdFromUrl(url) {
119
+ const match = url.match(/#issuecomment-(\d+)$/);
120
+ if (!(match == null ? void 0 : match[1])) {
121
+ throw new Error(`Cannot extract comment ID from URL: ${url}`);
122
+ }
123
+ return match[1];
124
+ }
125
+ var GitHubIssueManagementProvider = class {
126
+ constructor() {
127
+ this.providerName = "github";
128
+ }
129
+ /**
130
+ * Fetch issue details using gh CLI
131
+ * Normalizes GitHub-specific fields to provider-agnostic format
132
+ */
133
+ async getIssue(input) {
134
+ const { number, includeComments = true } = input;
135
+ const issueNumber = parseInt(number, 10);
136
+ if (isNaN(issueNumber)) {
137
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
138
+ }
139
+ const fields = includeComments ? "body,title,comments,labels,assignees,milestone,author,state,number,url" : "body,title,labels,assignees,milestone,author,state,number,url";
140
+ const raw = await executeGhCommand([
141
+ "issue",
142
+ "view",
143
+ String(issueNumber),
144
+ "--json",
145
+ fields
146
+ ]);
147
+ const result = {
148
+ // Core fields
149
+ id: String(raw.number),
150
+ title: raw.title,
151
+ body: raw.body,
152
+ state: raw.state,
153
+ url: raw.url,
154
+ provider: "github",
155
+ // Normalized author
156
+ author: normalizeAuthor(raw.author),
157
+ // Optional flexible fields
158
+ ...raw.assignees && {
159
+ assignees: raw.assignees.map((a) => normalizeAuthor(a)).filter((a) => a !== null)
160
+ },
161
+ ...raw.labels && {
162
+ labels: raw.labels
163
+ },
164
+ // GitHub-specific passthrough fields
165
+ ...raw.milestone && {
166
+ milestone: raw.milestone
167
+ }
168
+ };
169
+ if (raw.comments !== void 0) {
170
+ result.comments = raw.comments.map((comment) => ({
171
+ id: extractNumericIdFromUrl(comment.url),
172
+ body: comment.body,
173
+ createdAt: comment.createdAt,
174
+ author: normalizeAuthor(comment.author),
175
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
176
+ }));
177
+ }
178
+ return result;
179
+ }
180
+ /**
181
+ * Fetch a specific comment by ID using gh API
182
+ * Normalizes author to FlexibleAuthor format
183
+ */
184
+ async getComment(input) {
185
+ const { commentId } = input;
186
+ const numericCommentId = parseInt(commentId, 10);
187
+ if (isNaN(numericCommentId)) {
188
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
189
+ }
190
+ const raw = await executeGhCommand([
191
+ "api",
192
+ `repos/:owner/:repo/issues/comments/${numericCommentId}`,
193
+ "--jq",
194
+ "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
195
+ ]);
196
+ return {
197
+ id: String(raw.id),
198
+ body: raw.body,
199
+ author: normalizeAuthor(raw.user),
200
+ created_at: raw.created_at,
201
+ ...raw.updated_at && { updated_at: raw.updated_at },
202
+ // Passthrough GitHub-specific fields
203
+ ...raw.html_url && { html_url: raw.html_url },
204
+ ...raw.reactions && { reactions: raw.reactions }
205
+ };
206
+ }
207
+ /**
208
+ * Create a new comment on an issue or PR
209
+ */
210
+ async createComment(input) {
211
+ const { number, body, type } = input;
212
+ const numericId = parseInt(number, 10);
213
+ if (isNaN(numericId)) {
214
+ throw new Error(`Invalid GitHub ${type} number: ${number}. GitHub IDs must be numeric.`);
215
+ }
216
+ const result = type === "issue" ? await createIssueComment(numericId, body) : await createPRComment(numericId, body);
217
+ return {
218
+ ...result,
219
+ id: String(result.id)
220
+ };
221
+ }
222
+ /**
223
+ * Update an existing comment
224
+ */
225
+ async updateComment(input) {
226
+ const { commentId, body } = input;
227
+ const numericCommentId = parseInt(commentId, 10);
228
+ if (isNaN(numericCommentId)) {
229
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
230
+ }
231
+ const result = await updateIssueComment(numericCommentId, body);
232
+ return {
233
+ ...result,
234
+ id: String(result.id)
235
+ };
236
+ }
237
+ };
238
+
239
+ // src/utils/linear-markup-converter.ts
240
+ import { appendFileSync } from "fs";
241
+ import { join as join2, dirname, basename, extname } from "path";
242
+ var LinearMarkupConverter = class {
243
+ /**
244
+ * Convert HTML details/summary blocks to Linear's collapsible format
245
+ * Handles nested details blocks recursively
246
+ *
247
+ * @param text - Text containing HTML details/summary blocks
248
+ * @returns Text with details/summary converted to Linear format
249
+ */
250
+ static convertDetailsToLinear(text) {
251
+ if (!text) {
252
+ return text;
253
+ }
254
+ let previousText = "";
255
+ let currentText = text;
256
+ while (previousText !== currentText) {
257
+ previousText = currentText;
258
+ currentText = this.convertSinglePass(currentText);
259
+ }
260
+ return currentText;
261
+ }
262
+ /**
263
+ * Perform a single pass of details block conversion
264
+ * Converts the innermost details blocks first
265
+ */
266
+ static convertSinglePass(text) {
267
+ const detailsRegex = /<details[^>]*>\s*<summary[^>]*>(.*?)<\/summary>\s*(.*?)\s*<\/details>/gis;
268
+ return text.replace(detailsRegex, (_match, summary, content) => {
269
+ const cleanSummary = this.cleanText(summary);
270
+ const cleanContent = this.cleanContent(content);
271
+ if (cleanContent) {
272
+ return `+++ ${cleanSummary}
273
+
274
+ ${cleanContent}
275
+
276
+ +++`;
277
+ } else {
278
+ return `+++ ${cleanSummary}
279
+
280
+ +++`;
281
+ }
282
+ });
283
+ }
284
+ /**
285
+ * Clean text by trimming whitespace and decoding common HTML entities
286
+ */
287
+ static cleanText(text) {
288
+ return text.trim().replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
289
+ }
290
+ /**
291
+ * Clean content while preserving internal structure
292
+ * - Removes leading/trailing whitespace
293
+ * - Normalizes internal blank lines (max 2 consecutive newlines)
294
+ * - Preserves code blocks and other formatting
295
+ */
296
+ static cleanContent(content) {
297
+ if (!content) {
298
+ return "";
299
+ }
300
+ let cleaned = content.trim();
301
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
302
+ return cleaned;
303
+ }
304
+ /**
305
+ * Check if text contains HTML details/summary blocks
306
+ * Useful for conditional conversion
307
+ */
308
+ static hasDetailsBlocks(text) {
309
+ if (!text) {
310
+ return false;
311
+ }
312
+ const detailsRegex = /<details[^>]*>.*?<summary[^>]*>.*?<\/summary>.*?<\/details>/is;
313
+ return detailsRegex.test(text);
314
+ }
315
+ /**
316
+ * Remove wrapper tags from code sample details blocks
317
+ * Identifies details blocks where summary contains "X lines" pattern
318
+ * and removes the details/summary tags while preserving the content
319
+ *
320
+ * @param text - Text containing potential code sample details blocks
321
+ * @returns Text with code sample wrappers removed
322
+ */
323
+ static removeCodeSampleWrappers(text) {
324
+ if (!text) {
325
+ return text;
326
+ }
327
+ const codeSampleRegex = /<details[^>]*>\s*<summary[^>]*>([^<]*\d+\s+lines[^<]*)<\/summary>\s*([\s\S]*?)<\/details>/gi;
328
+ return text.replace(codeSampleRegex, (_match, _summary, content) => {
329
+ return content.trim();
330
+ });
331
+ }
332
+ /**
333
+ * Convert text for Linear - applies all necessary conversions
334
+ * Currently only converts details/summary blocks, but can be extended
335
+ * for other HTML to Linear markdown conversions
336
+ */
337
+ static convertToLinear(text) {
338
+ if (!text) {
339
+ return text;
340
+ }
341
+ this.logConversion("INPUT", text);
342
+ let converted = text;
343
+ converted = this.removeCodeSampleWrappers(converted);
344
+ converted = this.convertDetailsToLinear(converted);
345
+ this.logConversion("OUTPUT", converted);
346
+ return converted;
347
+ }
348
+ /**
349
+ * Log conversion input/output if LINEAR_MARKDOWN_LOG_FILE is set
350
+ */
351
+ static logConversion(label, content) {
352
+ const logFilePath = process.env.LINEAR_MARKDOWN_LOG_FILE;
353
+ if (!logFilePath) {
354
+ return;
355
+ }
356
+ try {
357
+ const timestampedPath = this.getTimestampedLogPath(logFilePath);
358
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
359
+ const separator = "================================";
360
+ const logEntry = `${separator}
361
+ [${timestamp}] CONVERSION ${label}
362
+ ${separator}
363
+ ${label}:
364
+ ${content}
365
+
366
+ `;
367
+ appendFileSync(timestampedPath, logEntry, "utf-8");
368
+ } catch {
369
+ }
370
+ }
371
+ /**
372
+ * Generate timestamped log file path
373
+ * Example: debug.log -> debug-20231202-161234.log
374
+ */
375
+ static getTimestampedLogPath(logFilePath) {
376
+ const dir = dirname(logFilePath);
377
+ const ext = extname(logFilePath);
378
+ const base = basename(logFilePath, ext);
379
+ const now = /* @__PURE__ */ new Date();
380
+ const timestamp = [
381
+ now.getFullYear(),
382
+ String(now.getMonth() + 1).padStart(2, "0"),
383
+ String(now.getDate()).padStart(2, "0")
384
+ ].join("") + "-" + [
385
+ String(now.getHours()).padStart(2, "0"),
386
+ String(now.getMinutes()).padStart(2, "0"),
387
+ String(now.getSeconds()).padStart(2, "0")
388
+ ].join("");
389
+ return join2(dir, `${base}-${timestamp}${ext}`);
390
+ }
391
+ };
392
+
393
+ // src/mcp/LinearIssueManagementProvider.ts
394
+ var LinearIssueManagementProvider = class {
395
+ constructor() {
396
+ this.providerName = "linear";
397
+ }
398
+ /**
399
+ * Fetch issue details using Linear SDK
400
+ */
401
+ async getIssue(input) {
402
+ const { number, includeComments = true } = input;
403
+ const raw = await fetchLinearIssue(number);
404
+ const state = raw.state && (raw.state.toLowerCase().includes("done") || raw.state.toLowerCase().includes("completed") || raw.state.toLowerCase().includes("canceled")) ? "closed" : "open";
405
+ const result = {
406
+ id: raw.identifier,
407
+ title: raw.title,
408
+ body: raw.description ?? "",
409
+ state,
410
+ url: raw.url,
411
+ provider: "linear",
412
+ author: null,
413
+ // Linear SDK doesn't return author in basic fetch
414
+ // Linear-specific fields
415
+ linearState: raw.state,
416
+ createdAt: raw.createdAt,
417
+ updatedAt: raw.updatedAt
418
+ };
419
+ if (includeComments) {
420
+ try {
421
+ const comments = await this.fetchIssueComments(number);
422
+ if (comments) {
423
+ result.comments = comments;
424
+ }
425
+ } catch {
426
+ }
427
+ }
428
+ return result;
429
+ }
430
+ /**
431
+ * Fetch comments for an issue
432
+ */
433
+ async fetchIssueComments(identifier) {
434
+ try {
435
+ const comments = await fetchLinearIssueComments(identifier);
436
+ return comments.map((comment) => ({
437
+ id: comment.id,
438
+ body: comment.body,
439
+ createdAt: comment.createdAt,
440
+ author: null,
441
+ // Linear SDK doesn't return comment author info in basic fetch
442
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
443
+ }));
444
+ } catch {
445
+ return [];
446
+ }
447
+ }
448
+ /**
449
+ * Fetch a specific comment by ID
450
+ */
451
+ async getComment(input) {
452
+ const { commentId } = input;
453
+ const raw = await getLinearComment(commentId);
454
+ return {
455
+ id: raw.id,
456
+ body: raw.body,
457
+ author: null,
458
+ // Linear SDK doesn't return comment author info in basic fetch
459
+ created_at: raw.createdAt
460
+ };
461
+ }
462
+ /**
463
+ * Create a new comment on an issue
464
+ */
465
+ async createComment(input) {
466
+ const { number, body } = input;
467
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
468
+ const result = await createLinearComment(number, convertedBody);
469
+ return {
470
+ id: result.id,
471
+ url: result.url,
472
+ created_at: result.createdAt
473
+ };
474
+ }
475
+ /**
476
+ * Update an existing comment
477
+ */
478
+ async updateComment(input) {
479
+ const { commentId, body } = input;
480
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
481
+ const result = await updateLinearComment(commentId, convertedBody);
482
+ return {
483
+ id: result.id,
484
+ url: result.url,
485
+ updated_at: result.updatedAt
486
+ };
487
+ }
488
+ };
489
+
490
+ // src/mcp/IssueManagementProviderFactory.ts
491
+ var IssueManagementProviderFactory = class {
492
+ /**
493
+ * Create an issue management provider based on the provider type
494
+ */
495
+ static create(provider) {
496
+ switch (provider) {
497
+ case "github":
498
+ return new GitHubIssueManagementProvider();
499
+ case "linear":
500
+ return new LinearIssueManagementProvider();
501
+ default:
502
+ throw new Error(`Unsupported issue management provider: ${provider}`);
503
+ }
504
+ }
505
+ };
506
+
507
+ // src/lib/SessionSummaryService.ts
508
+ var SessionSummaryService = class {
509
+ constructor(templateManager, metadataManager, settingsManager) {
510
+ this.templateManager = templateManager ?? new PromptTemplateManager();
511
+ this.metadataManager = metadataManager ?? new MetadataManager();
512
+ this.settingsManager = settingsManager ?? new SettingsManager();
513
+ }
514
+ /**
515
+ * Generate and post a session summary to the issue
516
+ *
517
+ * Non-blocking: Catches all errors and logs warnings instead of throwing
518
+ * This ensures the finish workflow continues even if summary generation fails
519
+ */
520
+ async generateAndPostSummary(input) {
521
+ try {
522
+ if (input.loomType === "branch") {
523
+ logger.debug("Skipping session summary: branch type has no associated issue");
524
+ return;
525
+ }
526
+ const metadata = await this.metadataManager.readMetadata(input.worktreePath);
527
+ const sessionId = (metadata == null ? void 0 : metadata.sessionId) ?? generateDeterministicSessionId(input.worktreePath);
528
+ const settings = await this.settingsManager.loadSettings(input.worktreePath);
529
+ if (!this.shouldGenerateSummary(input.loomType, settings)) {
530
+ logger.debug(`Skipping session summary: generateSummary is disabled for ${input.loomType} workflow`);
531
+ return;
532
+ }
533
+ logger.info("Generating session summary...");
534
+ logger.debug(`Looking for session transcript with sessionId: ${sessionId}`);
535
+ const compactSummaries = await readSessionContext(input.worktreePath, sessionId);
536
+ if (compactSummaries) {
537
+ logger.debug(`Found compact summaries (${compactSummaries.length} chars)`);
538
+ } else {
539
+ logger.debug("No compact summaries found in session transcript");
540
+ }
541
+ const prompt = await this.templateManager.getPrompt("session-summary", {
542
+ ISSUE_NUMBER: String(input.issueNumber),
543
+ BRANCH_NAME: input.branchName,
544
+ LOOM_TYPE: input.loomType,
545
+ COMPACT_SUMMARIES: compactSummaries ?? ""
546
+ });
547
+ logger.debug("Session summary prompt:\n" + prompt);
548
+ const summaryModel = this.settingsManager.getSummaryModel(settings);
549
+ const summaryResult = await launchClaude(prompt, {
550
+ headless: true,
551
+ model: summaryModel,
552
+ sessionId
553
+ // Resume this session so Claude has conversation context
554
+ });
555
+ if (!summaryResult || typeof summaryResult !== "string" || summaryResult.trim() === "") {
556
+ logger.warn("Session summary generation returned empty result");
557
+ return;
558
+ }
559
+ const summary = summaryResult.trim();
560
+ if (summary.length < 100) {
561
+ logger.warn("Session summary too short, skipping post");
562
+ return;
563
+ }
564
+ await this.postSummaryToIssue(input.issueNumber, summary, settings);
565
+ logger.success("Session summary posted to issue");
566
+ } catch (error) {
567
+ const errorMessage = error instanceof Error ? error.message : String(error);
568
+ logger.warn(`Failed to generate session summary: ${errorMessage}`);
569
+ logger.debug("Session summary generation error details:", { error });
570
+ }
571
+ }
572
+ /**
573
+ * Generate a session summary without posting it
574
+ *
575
+ * This method is useful for previewing the summary or for use by CLI commands
576
+ * that want to display the summary before optionally posting it.
577
+ *
578
+ * @param worktreePath - Path to the worktree
579
+ * @param branchName - Name of the branch
580
+ * @param loomType - Type of loom ('issue' | 'pr' | 'branch')
581
+ * @param issueNumber - Issue or PR number (optional, for template variables)
582
+ * @returns The generated summary and session ID
583
+ * @throws Error if Claude invocation fails
584
+ */
585
+ async generateSummary(worktreePath, branchName, loomType, issueNumber) {
586
+ const metadata = await this.metadataManager.readMetadata(worktreePath);
587
+ const sessionId = (metadata == null ? void 0 : metadata.sessionId) ?? generateDeterministicSessionId(worktreePath);
588
+ const settings = await this.settingsManager.loadSettings(worktreePath);
589
+ logger.info("Generating session summary...");
590
+ logger.debug(`Looking for session transcript with sessionId: ${sessionId}`);
591
+ const compactSummaries = await readSessionContext(worktreePath, sessionId);
592
+ if (compactSummaries) {
593
+ logger.debug(`Found compact summaries (${compactSummaries.length} chars)`);
594
+ } else {
595
+ logger.debug("No compact summaries found in session transcript");
596
+ }
597
+ const prompt = await this.templateManager.getPrompt("session-summary", {
598
+ ISSUE_NUMBER: issueNumber !== void 0 ? String(issueNumber) : "",
599
+ BRANCH_NAME: branchName,
600
+ LOOM_TYPE: loomType,
601
+ COMPACT_SUMMARIES: compactSummaries ?? ""
602
+ });
603
+ logger.debug("Session summary prompt:\n" + prompt);
604
+ const summaryModel = this.settingsManager.getSummaryModel(settings);
605
+ const summaryResult = await launchClaude(prompt, {
606
+ headless: true,
607
+ model: summaryModel,
608
+ sessionId
609
+ });
610
+ if (!summaryResult || typeof summaryResult !== "string" || summaryResult.trim() === "") {
611
+ throw new Error("Session summary generation returned empty result");
612
+ }
613
+ const summary = summaryResult.trim();
614
+ if (summary.length < 100) {
615
+ throw new Error("Session summary too short - generation may have failed");
616
+ }
617
+ return {
618
+ summary,
619
+ sessionId
620
+ };
621
+ }
622
+ /**
623
+ * Post a summary to an issue (used by both generateAndPostSummary and CLI commands)
624
+ *
625
+ * @param issueNumber - Issue or PR number to post to
626
+ * @param summary - The summary text to post
627
+ * @param worktreePath - Path to worktree for loading settings (optional)
628
+ */
629
+ async postSummary(issueNumber, summary, worktreePath) {
630
+ const settings = await this.settingsManager.loadSettings(worktreePath);
631
+ await this.postSummaryToIssue(issueNumber, summary, settings);
632
+ logger.success("Session summary posted to issue");
633
+ }
634
+ /**
635
+ * Determine if summary should be generated based on loom type and settings
636
+ *
637
+ * @param loomType - The type of loom being finished
638
+ * @param settings - The loaded iloom settings
639
+ * @returns true if summary should be generated
640
+ */
641
+ shouldGenerateSummary(loomType, settings) {
642
+ var _a, _b;
643
+ if (loomType === "branch") {
644
+ return false;
645
+ }
646
+ const workflowConfig = loomType === "issue" ? (_a = settings.workflows) == null ? void 0 : _a.issue : (_b = settings.workflows) == null ? void 0 : _b.pr;
647
+ return (workflowConfig == null ? void 0 : workflowConfig.generateSummary) ?? true;
648
+ }
649
+ /**
650
+ * Post the summary as a comment to the issue
651
+ */
652
+ async postSummaryToIssue(issueNumber, summary, settings) {
653
+ var _a;
654
+ const providerType = ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
655
+ const provider = IssueManagementProviderFactory.create(providerType);
656
+ await provider.createComment({
657
+ number: String(issueNumber),
658
+ body: summary,
659
+ type: "issue"
660
+ });
661
+ }
662
+ };
663
+
664
+ export {
665
+ SessionSummaryService
666
+ };
667
+ //# sourceMappingURL=chunk-HHDSIE72.js.map