@iloom/cli 0.3.4 → 0.4.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.
- package/README.md +13 -3
- package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-GCCWB3LK.js} +4 -3
- package/dist/ClaudeContextManager-DK77227F.js +16 -0
- package/dist/ClaudeService-W3SA7HVG.js +15 -0
- package/dist/GitHubService-RPM27GWD.js +12 -0
- package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-S3YGJRJQ.js} +43 -27
- package/dist/LoomLauncher-S3YGJRJQ.js.map +1 -0
- package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
- package/dist/README.md +13 -3
- package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
- package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
- package/dist/{chunk-5Q3NDNNV.js → chunk-2W2FBL5G.js} +153 -6
- package/dist/chunk-2W2FBL5G.js.map +1 -0
- package/dist/{chunk-OXAM2WVC.js → chunk-55TB3FSG.js} +21 -1
- package/dist/chunk-55TB3FSG.js.map +1 -0
- package/dist/chunk-6UIGZD2N.js +20 -0
- package/dist/chunk-6UIGZD2N.js.map +1 -0
- package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
- package/dist/chunk-74VMN2KC.js.map +1 -0
- package/dist/{chunk-2MAIX45J.js → chunk-BIIQHEXJ.js} +104 -43
- package/dist/chunk-BIIQHEXJ.js.map +1 -0
- package/dist/{chunk-UAN4A3YU.js → chunk-G6CIIJLT.js} +11 -11
- package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
- package/dist/chunk-HD5SUKI2.js.map +1 -0
- package/dist/{chunk-2IJEMXOB.js → chunk-IARWMDAX.js} +427 -428
- package/dist/chunk-IARWMDAX.js.map +1 -0
- package/dist/chunk-IJ7IGJT3.js +192 -0
- package/dist/chunk-IJ7IGJT3.js.map +1 -0
- package/dist/{chunk-2CXREBLZ.js → chunk-JC5HXN75.js} +8 -6
- package/dist/chunk-JC5HXN75.js.map +1 -0
- package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
- package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
- package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
- package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
- package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
- package/dist/chunk-OEGECBFS.js.map +1 -0
- package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
- package/dist/chunk-OF7BNW4D.js.map +1 -0
- package/dist/{chunk-PGPI5LR4.js → chunk-POI7KLBH.js} +7 -21
- package/dist/chunk-POI7KLBH.js.map +1 -0
- package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
- package/dist/chunk-QHA67Q7A.js +281 -0
- package/dist/chunk-QHA67Q7A.js.map +1 -0
- package/dist/{chunk-SUOXY5WJ.js → chunk-QIUJPPJQ.js} +5 -5
- package/dist/chunk-QIUJPPJQ.js.map +1 -0
- package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
- package/dist/{chunk-OYF4VIFI.js → chunk-RUC7OULH.js} +147 -22
- package/dist/chunk-RUC7OULH.js.map +1 -0
- package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
- package/dist/chunk-SJ2GZ6RF.js.map +1 -0
- package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
- package/dist/chunk-TMZAVPGF.js +667 -0
- package/dist/chunk-TMZAVPGF.js.map +1 -0
- package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
- package/dist/chunk-UNXRACJ7.js.map +1 -0
- package/dist/{chunk-AKUJXDNW.js → chunk-UPUAQYAW.js} +3 -3
- package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
- package/dist/chunk-UYVWLISQ.js.map +1 -0
- package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
- package/dist/{chunk-RW54ZMBM.js → chunk-VAYGNQTE.js} +2 -2
- package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
- package/dist/chunk-VBFDVGAE.js.map +1 -0
- package/dist/{chunk-IFB4Z76W.js → chunk-VTXCGKV5.js} +13 -12
- package/dist/chunk-VTXCGKV5.js.map +1 -0
- package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
- package/dist/chunk-VWNS6DH5.js.map +1 -0
- package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
- package/dist/{chunk-UJL4HI2R.js → chunk-Z5NXYJIG.js} +20 -2
- package/dist/chunk-Z5NXYJIG.js.map +1 -0
- package/dist/{claude-W52VKI6L.js → claude-ACVXNB6N.js} +8 -5
- package/dist/{cleanup-H4VXU3C3.js → cleanup-KDLVTT7M.js} +133 -122
- package/dist/cleanup-KDLVTT7M.js.map +1 -0
- package/dist/cli.js +953 -430
- package/dist/cli.js.map +1 -1
- package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
- package/dist/{contribute-Y7IQV5QY.js → contribute-HY372S6F.js} +8 -6
- package/dist/{contribute-Y7IQV5QY.js.map → contribute-HY372S6F.js.map} +1 -1
- package/dist/dev-server-JCJGQ3PV.js +298 -0
- package/dist/dev-server-JCJGQ3PV.js.map +1 -0
- package/dist/{feedback-XTUCKJNT.js → feedback-7PVBQNLJ.js} +13 -12
- package/dist/{feedback-XTUCKJNT.js.map → feedback-7PVBQNLJ.js.map} +1 -1
- package/dist/{git-IYA53VIC.js → git-4BVOOOOV.js} +16 -4
- package/dist/hooks/iloom-hook.js +258 -0
- package/dist/{ignite-T74RYXCA.js → ignite-3B264M7K.js} +245 -39
- package/dist/ignite-3B264M7K.js.map +1 -0
- package/dist/index.d.ts +461 -124
- package/dist/index.js +743 -210
- package/dist/index.js.map +1 -1
- package/dist/init-LBA6NUK2.js +21 -0
- package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
- package/dist/mcp/issue-management-server.js +2 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/neon-helpers-L5CXQ5CT.js +11 -0
- package/dist/{open-UMXANW5S.js → open-OGCV32Z4.js} +15 -13
- package/dist/{open-UMXANW5S.js.map → open-OGCV32Z4.js.map} +1 -1
- package/dist/projects-P55273AB.js +73 -0
- package/dist/projects-P55273AB.js.map +1 -0
- package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
- package/dist/prompts/init-prompt.txt +49 -0
- package/dist/prompts/issue-prompt.txt +110 -8
- package/dist/prompts/regular-prompt.txt +90 -0
- package/dist/prompts/session-summary-prompt.txt +82 -0
- package/dist/{rebase-VJ2VKR6R.js → rebase-4T5FQHNH.js} +11 -9
- package/dist/{rebase-VJ2VKR6R.js.map → rebase-4T5FQHNH.js.map} +1 -1
- package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
- package/dist/{run-MJYY4PUT.js → run-HNOP6WE2.js} +15 -13
- package/dist/{run-MJYY4PUT.js.map → run-HNOP6WE2.js.map} +1 -1
- package/dist/schema/settings.schema.json +49 -0
- package/dist/shell-DE3HKJSM.js +240 -0
- package/dist/shell-DE3HKJSM.js.map +1 -0
- package/dist/summary-GDT7DTRI.js +244 -0
- package/dist/summary-GDT7DTRI.js.map +1 -0
- package/dist/{test-git-IT5EWQ5C.js → test-git-YMAE57UP.js} +6 -4
- package/dist/{test-git-IT5EWQ5C.js.map → test-git-YMAE57UP.js.map} +1 -1
- package/dist/{test-prefix-NPWDPUUH.js → test-prefix-YCKL6CMT.js} +6 -4
- package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-YCKL6CMT.js.map} +1 -1
- package/dist/{test-tabs-PRMRSHKI.js → test-tabs-3SCJWRKT.js} +4 -4
- package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
- package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
- package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
- package/package.json +6 -6
- package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
- package/dist/ClaudeService-DLYLJUPA.js +0 -14
- package/dist/GitHubService-FZHHBOFG.js +0 -11
- package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
- package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
- package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
- package/dist/chunk-2CXREBLZ.js.map +0 -1
- package/dist/chunk-2IJEMXOB.js.map +0 -1
- package/dist/chunk-2MAIX45J.js.map +0 -1
- package/dist/chunk-5Q3NDNNV.js.map +0 -1
- package/dist/chunk-5VK4NRSF.js.map +0 -1
- package/dist/chunk-CDZERT7Z.js.map +0 -1
- package/dist/chunk-CE26YH2U.js.map +0 -1
- package/dist/chunk-DLHA5VQ3.js.map +0 -1
- package/dist/chunk-GEHQXLEI.js.map +0 -1
- package/dist/chunk-IFB4Z76W.js.map +0 -1
- package/dist/chunk-M7JJCX53.js.map +0 -1
- package/dist/chunk-MKWYLDFK.js.map +0 -1
- package/dist/chunk-OXAM2WVC.js.map +0 -1
- package/dist/chunk-OYF4VIFI.js.map +0 -1
- package/dist/chunk-PGPI5LR4.js.map +0 -1
- package/dist/chunk-RIEO2WML.js.map +0 -1
- package/dist/chunk-SUOXY5WJ.js.map +0 -1
- package/dist/chunk-UJL4HI2R.js.map +0 -1
- package/dist/chunk-ZT3YZB4K.js.map +0 -1
- package/dist/cleanup-H4VXU3C3.js.map +0 -1
- package/dist/ignite-T74RYXCA.js.map +0 -1
- package/dist/init-4FHTAM3F.js +0 -19
- package/dist/logger-MKYH4UDV.js +0 -12
- package/dist/neon-helpers-77PBPGJ5.js +0 -10
- package/dist/update-notifier-QEX3CJHA.js.map +0 -1
- /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-GCCWB3LK.js.map} +0 -0
- /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-DK77227F.js.map} +0 -0
- /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-W3SA7HVG.js.map} +0 -0
- /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
- /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
- /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
- /package/dist/{chunk-UAN4A3YU.js.map → chunk-G6CIIJLT.js.map} +0 -0
- /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
- /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
- /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
- /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
- /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
- /package/dist/{chunk-AKUJXDNW.js.map → chunk-UPUAQYAW.js.map} +0 -0
- /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
- /package/dist/{chunk-RW54ZMBM.js.map → chunk-VAYGNQTE.js.map} +0 -0
- /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
- /package/dist/{claude-W52VKI6L.js.map → claude-ACVXNB6N.js.map} +0 -0
- /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
- /package/dist/{git-IYA53VIC.js.map → git-4BVOOOOV.js.map} +0 -0
- /package/dist/{init-4FHTAM3F.js.map → init-LBA6NUK2.js.map} +0 -0
- /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
- /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
- /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
- /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
- /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-3SCJWRKT.js.map} +0 -0
- /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
- /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
- /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-IJ7IGJT3.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-RUC7OULH.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(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/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-TMZAVPGF.js.map
|