@iloom/cli 0.13.1 → 0.13.3
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/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/CLAUDE.md +66 -0
- package/dist/{ClaudeContextManager-ZH6LEA5I.js → ClaudeContextManager-KJ4VEA2F.js} +5 -5
- package/dist/{ClaudeService-YR66WXZN.js → ClaudeService-WTJO4UW6.js} +4 -4
- package/dist/{IssueTrackerFactory-O2ZBA666.js → IssueTrackerFactory-UEJALI4X.js} +3 -3
- package/dist/{LoomLauncher-V54ENBEF.js → LoomLauncher-KG2VBNQA.js} +5 -5
- package/dist/{PromptTemplateManager-4RFELNYY.js → PromptTemplateManager-QIUVJP6S.js} +2 -2
- package/dist/README.md +1 -1
- package/dist/{SettingsManager-SLSYEYDZ.js → SettingsManager-PVHBSCMI.js} +2 -2
- package/dist/agents/CLAUDE.md +68 -0
- package/dist/agents/iloom-code-reviewer.md +1 -0
- package/dist/agents/iloom-wave-verifier.md +1 -36
- package/dist/{build-ZTGWDHWU.js → build-2FXDYEZQ.js} +6 -6
- package/dist/{chunk-LNY2Y32V.js → chunk-2WRD6Y5E.js} +2 -2
- package/dist/{chunk-WYDLOQYO.js → chunk-32D4CWWH.js} +2 -2
- package/dist/{chunk-KGOBNC5A.js → chunk-5W44AI63.js} +3 -3
- package/dist/{chunk-PPQ5LV7U.js → chunk-D6FU4DLN.js} +2 -2
- package/dist/{chunk-PS6K2AOV.js → chunk-DMNI225H.js} +4 -4
- package/dist/{chunk-55NTREIU.js → chunk-DYLOITSO.js} +55 -35
- package/dist/chunk-DYLOITSO.js.map +1 -0
- package/dist/{chunk-T4KFKKEB.js → chunk-H4TSDALC.js} +6 -6
- package/dist/{chunk-J5JOJPK3.js → chunk-L3P3YJCE.js} +2 -2
- package/dist/{chunk-MRPIDNZU.js → chunk-LDE6VNG5.js} +1 -1
- package/dist/chunk-LDE6VNG5.js.map +1 -0
- package/dist/{chunk-F5NKWLMQ.js → chunk-MNPKEWBQ.js} +9 -5
- package/dist/chunk-MNPKEWBQ.js.map +1 -0
- package/dist/{chunk-EHAITKLS.js → chunk-MPHSR6GA.js} +3 -3
- package/dist/{chunk-HWDQRW3O.js → chunk-OHX3PSAY.js} +2 -2
- package/dist/{chunk-C2BVNJW5.js → chunk-OIJNBFMP.js} +2 -2
- package/dist/{chunk-TJDKGKQV.js → chunk-OMV47LLA.js} +2 -2
- package/dist/{chunk-P5MNWBLH.js → chunk-OVW26FHW.js} +19 -7
- package/dist/chunk-OVW26FHW.js.map +1 -0
- package/dist/{chunk-QNRXRSKC.js → chunk-RP6MHV24.js} +9 -9
- package/dist/chunk-RP6MHV24.js.map +1 -0
- package/dist/{chunk-UXBVDD7U.js → chunk-U2OPXZ6E.js} +282 -44
- package/dist/chunk-U2OPXZ6E.js.map +1 -0
- package/dist/{chunk-T4NESGYB.js → chunk-UMAOVKQX.js} +3 -3
- package/dist/{chunk-E5OM25WK.js → chunk-UQWMPQ2Q.js} +2 -2
- package/dist/{chunk-ZEFTWM5Z.js → chunk-VUIPDX3T.js} +2 -2
- package/dist/{chunk-GQDVH6FA.js → chunk-XC5JKRSH.js} +2 -2
- package/dist/{chunk-G2DGDCDP.js → chunk-Y2JHYPMX.js} +15 -13
- package/dist/chunk-Y2JHYPMX.js.map +1 -0
- package/dist/{chunk-ERMEYFT6.js → chunk-YVNG35OW.js} +2 -2
- package/dist/{chunk-7TN5VW4I.js → chunk-Z32HPRZF.js} +2 -1
- package/dist/chunk-Z32HPRZF.js.map +1 -0
- package/dist/{chunk-KCAWSZUO.js → chunk-Z3ZEJN3W.js} +13 -13
- package/dist/{chunk-GPBX2BY2.js → chunk-ZWXJ7G2C.js} +2 -2
- package/dist/{cleanup-BCVY7PEF.js → cleanup-W5FP3UKK.js} +16 -16
- package/dist/cli.js +115 -68
- package/dist/cli.js.map +1 -1
- package/dist/{commit-L5JNBU4U.js → commit-7RI2JFFW.js} +6 -6
- package/dist/{compile-GPJOHXH4.js → compile-NWTMKAGL.js} +6 -6
- package/dist/{contribute-QEGCI4PS.js → contribute-QWPOT4QR.js} +3 -3
- package/dist/{dev-server-UQKNKU2S.js → dev-server-U2XUN57X.js} +61 -30
- package/dist/dev-server-U2XUN57X.js.map +1 -0
- package/dist/{feedback-2LWXKLQZ.js → feedback-M43SGGK2.js} +4 -4
- package/dist/{git-IS7AV3ED.js → git-ZTMT6OAI.js} +3 -3
- package/dist/{ignite-VQDJQ37S.js → ignite-GUYKYC5G.js} +11 -11
- package/dist/index.d.ts +30 -3
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/{init-7SDJUAEZ.js → init-AMLCFVXG.js} +9 -7
- package/dist/init-AMLCFVXG.js.map +1 -0
- package/dist/{install-deps-NGSFDNUW.js → install-deps-XS2UUCUS.js} +6 -6
- package/dist/{issues-4HQKEUP7.js → issues-2IT7PSNZ.js} +4 -4
- package/dist/{lint-C5FOVRXY.js → lint-DKWJHET3.js} +6 -6
- package/dist/mcp/issue-management-server.js +8 -4
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{open-2HL6GV5F.js → open-XIUV5JMJ.js} +16 -15
- package/dist/open-XIUV5JMJ.js.map +1 -0
- package/dist/{plan-GC3HF73T.js → plan-UPYDB34J.js} +20 -20
- package/dist/prompts/epic-report-prompt.txt +145 -0
- package/dist/prompts/init-prompt.txt +32 -9
- package/dist/prompts/issue-prompt.txt +1 -1
- package/dist/prompts/swarm-orchestrator-prompt.txt +50 -6
- package/dist/{rebase-MLIN572O.js → rebase-6AXN45AE.js} +5 -5
- package/dist/{recap-CKGKFDJL.js → recap-XDKI3MTA.js} +6 -6
- package/dist/{run-CUNRQNZS.js → run-VOGGJGPO.js} +19 -19
- package/dist/run-VOGGJGPO.js.map +1 -0
- package/dist/schema/settings.schema.json +14 -0
- package/dist/{shell-M2YYPNGV.js → shell-XOILFEZW.js} +5 -5
- package/dist/{summary-XR4CBJEG.js → summary-BVYOM63C.js} +10 -8
- package/dist/{summary-XR4CBJEG.js.map → summary-BVYOM63C.js.map} +1 -1
- package/dist/{test-ESDAHEVE.js → test-6T2UMQ7T.js} +6 -6
- package/dist/{test-git-KWPLHYSI.js → test-git-CQ65OL45.js} +3 -3
- package/dist/{test-jira-6NK7UHSV.js → test-jira-CQQHGZ3S.js} +3 -3
- package/dist/{test-prefix-VVODGHXP.js → test-prefix-HMTZSS67.js} +3 -3
- package/dist/{test-webserver-AHXKC6H4.js → test-webserver-ZN73CM2T.js} +5 -5
- package/dist/{vscode-OY7HOVRO.js → vscode-ABQ5ZSH7.js} +5 -5
- package/package.json +1 -1
- package/dist/chunk-55NTREIU.js.map +0 -1
- package/dist/chunk-7TN5VW4I.js.map +0 -1
- package/dist/chunk-F5NKWLMQ.js.map +0 -1
- package/dist/chunk-G2DGDCDP.js.map +0 -1
- package/dist/chunk-MRPIDNZU.js.map +0 -1
- package/dist/chunk-P5MNWBLH.js.map +0 -1
- package/dist/chunk-QNRXRSKC.js.map +0 -1
- package/dist/chunk-UXBVDD7U.js.map +0 -1
- package/dist/dev-server-UQKNKU2S.js.map +0 -1
- package/dist/init-7SDJUAEZ.js.map +0 -1
- package/dist/open-2HL6GV5F.js.map +0 -1
- package/dist/run-CUNRQNZS.js.map +0 -1
- /package/dist/{ClaudeContextManager-ZH6LEA5I.js.map → ClaudeContextManager-KJ4VEA2F.js.map} +0 -0
- /package/dist/{ClaudeService-YR66WXZN.js.map → ClaudeService-WTJO4UW6.js.map} +0 -0
- /package/dist/{IssueTrackerFactory-O2ZBA666.js.map → IssueTrackerFactory-UEJALI4X.js.map} +0 -0
- /package/dist/{LoomLauncher-V54ENBEF.js.map → LoomLauncher-KG2VBNQA.js.map} +0 -0
- /package/dist/{PromptTemplateManager-4RFELNYY.js.map → PromptTemplateManager-QIUVJP6S.js.map} +0 -0
- /package/dist/{SettingsManager-SLSYEYDZ.js.map → SettingsManager-PVHBSCMI.js.map} +0 -0
- /package/dist/{build-ZTGWDHWU.js.map → build-2FXDYEZQ.js.map} +0 -0
- /package/dist/{chunk-LNY2Y32V.js.map → chunk-2WRD6Y5E.js.map} +0 -0
- /package/dist/{chunk-WYDLOQYO.js.map → chunk-32D4CWWH.js.map} +0 -0
- /package/dist/{chunk-KGOBNC5A.js.map → chunk-5W44AI63.js.map} +0 -0
- /package/dist/{chunk-PPQ5LV7U.js.map → chunk-D6FU4DLN.js.map} +0 -0
- /package/dist/{chunk-PS6K2AOV.js.map → chunk-DMNI225H.js.map} +0 -0
- /package/dist/{chunk-T4KFKKEB.js.map → chunk-H4TSDALC.js.map} +0 -0
- /package/dist/{chunk-J5JOJPK3.js.map → chunk-L3P3YJCE.js.map} +0 -0
- /package/dist/{chunk-EHAITKLS.js.map → chunk-MPHSR6GA.js.map} +0 -0
- /package/dist/{chunk-HWDQRW3O.js.map → chunk-OHX3PSAY.js.map} +0 -0
- /package/dist/{chunk-C2BVNJW5.js.map → chunk-OIJNBFMP.js.map} +0 -0
- /package/dist/{chunk-TJDKGKQV.js.map → chunk-OMV47LLA.js.map} +0 -0
- /package/dist/{chunk-T4NESGYB.js.map → chunk-UMAOVKQX.js.map} +0 -0
- /package/dist/{chunk-E5OM25WK.js.map → chunk-UQWMPQ2Q.js.map} +0 -0
- /package/dist/{chunk-ZEFTWM5Z.js.map → chunk-VUIPDX3T.js.map} +0 -0
- /package/dist/{chunk-GQDVH6FA.js.map → chunk-XC5JKRSH.js.map} +0 -0
- /package/dist/{chunk-ERMEYFT6.js.map → chunk-YVNG35OW.js.map} +0 -0
- /package/dist/{chunk-KCAWSZUO.js.map → chunk-Z3ZEJN3W.js.map} +0 -0
- /package/dist/{chunk-GPBX2BY2.js.map → chunk-ZWXJ7G2C.js.map} +0 -0
- /package/dist/{cleanup-BCVY7PEF.js.map → cleanup-W5FP3UKK.js.map} +0 -0
- /package/dist/{commit-L5JNBU4U.js.map → commit-7RI2JFFW.js.map} +0 -0
- /package/dist/{compile-GPJOHXH4.js.map → compile-NWTMKAGL.js.map} +0 -0
- /package/dist/{contribute-QEGCI4PS.js.map → contribute-QWPOT4QR.js.map} +0 -0
- /package/dist/{feedback-2LWXKLQZ.js.map → feedback-M43SGGK2.js.map} +0 -0
- /package/dist/{git-IS7AV3ED.js.map → git-ZTMT6OAI.js.map} +0 -0
- /package/dist/{ignite-VQDJQ37S.js.map → ignite-GUYKYC5G.js.map} +0 -0
- /package/dist/{install-deps-NGSFDNUW.js.map → install-deps-XS2UUCUS.js.map} +0 -0
- /package/dist/{issues-4HQKEUP7.js.map → issues-2IT7PSNZ.js.map} +0 -0
- /package/dist/{lint-C5FOVRXY.js.map → lint-DKWJHET3.js.map} +0 -0
- /package/dist/{plan-GC3HF73T.js.map → plan-UPYDB34J.js.map} +0 -0
- /package/dist/{rebase-MLIN572O.js.map → rebase-6AXN45AE.js.map} +0 -0
- /package/dist/{recap-CKGKFDJL.js.map → recap-XDKI3MTA.js.map} +0 -0
- /package/dist/{shell-M2YYPNGV.js.map → shell-XOILFEZW.js.map} +0 -0
- /package/dist/{test-ESDAHEVE.js.map → test-6T2UMQ7T.js.map} +0 -0
- /package/dist/{test-git-KWPLHYSI.js.map → test-git-CQ65OL45.js.map} +0 -0
- /package/dist/{test-jira-6NK7UHSV.js.map → test-jira-CQQHGZ3S.js.map} +0 -0
- /package/dist/{test-prefix-VVODGHXP.js.map → test-prefix-HMTZSS67.js.map} +0 -0
- /package/dist/{test-webserver-AHXKC6H4.js.map → test-webserver-ZN73CM2T.js.map} +0 -0
- /package/dist/{vscode-OY7HOVRO.js.map → vscode-ABQ5ZSH7.js.map} +0 -0
|
@@ -4,13 +4,19 @@ import {
|
|
|
4
4
|
} from "./chunk-NXMDEL3F.js";
|
|
5
5
|
import {
|
|
6
6
|
VCSProviderFactory
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-L3P3YJCE.js";
|
|
8
|
+
import {
|
|
9
|
+
TelemetryService
|
|
10
|
+
} from "./chunk-MY2Q3FJ3.js";
|
|
11
|
+
import {
|
|
12
|
+
resolveRecapFilePath
|
|
13
|
+
} from "./chunk-CQHHEW2M.js";
|
|
8
14
|
import {
|
|
9
15
|
IssueManagementProviderFactory
|
|
10
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VUIPDX3T.js";
|
|
11
17
|
import {
|
|
12
18
|
PromptTemplateManager
|
|
13
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-LDE6VNG5.js";
|
|
14
20
|
import {
|
|
15
21
|
hasMultipleRemotes
|
|
16
22
|
} from "./chunk-BZ7KTXPB.js";
|
|
@@ -20,19 +26,17 @@ import {
|
|
|
20
26
|
} from "./chunk-DDHWZNGL.js";
|
|
21
27
|
import {
|
|
22
28
|
SettingsManager
|
|
23
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-MNPKEWBQ.js";
|
|
24
30
|
import {
|
|
25
31
|
MetadataManager
|
|
26
32
|
} from "./chunk-XIVLGWUX.js";
|
|
33
|
+
import {
|
|
34
|
+
getLogger
|
|
35
|
+
} from "./chunk-FTYWGQFM.js";
|
|
27
36
|
import {
|
|
28
37
|
logger
|
|
29
38
|
} from "./chunk-VRPPI6GU.js";
|
|
30
39
|
|
|
31
|
-
// src/lib/SessionSummaryService.ts
|
|
32
|
-
import path from "path";
|
|
33
|
-
import os from "os";
|
|
34
|
-
import fs from "fs-extra";
|
|
35
|
-
|
|
36
40
|
// src/utils/claude-transcript.ts
|
|
37
41
|
import { readFile } from "fs/promises";
|
|
38
42
|
import { homedir } from "os";
|
|
@@ -106,46 +110,183 @@ async function readSessionContext(worktreePath, sessionId, maxSummaries = 3) {
|
|
|
106
110
|
return formattedSummaries;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
// src/lib/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
slug = slug.replace(/[/\\]/g, "___");
|
|
114
|
-
slug = slug.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
115
|
-
return `${slug}.json`;
|
|
116
|
-
}
|
|
117
|
-
async function readRecapFile(worktreePath) {
|
|
113
|
+
// src/lib/SwarmReportCollector.ts
|
|
114
|
+
import fs from "fs-extra";
|
|
115
|
+
var CONCURRENCY_LIMIT = 5;
|
|
116
|
+
async function readRecapForWorktree(worktreePath) {
|
|
118
117
|
try {
|
|
119
|
-
const filePath =
|
|
120
|
-
if (await fs.pathExists(filePath))
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
118
|
+
const filePath = resolveRecapFilePath(worktreePath);
|
|
119
|
+
if (!await fs.pathExists(filePath)) return null;
|
|
120
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
121
|
+
const recap = JSON.parse(content);
|
|
122
|
+
const hasGoal = recap.goal !== null && recap.goal !== void 0;
|
|
123
|
+
const hasComplexity = recap.complexity !== null && recap.complexity !== void 0;
|
|
124
|
+
const hasEntries = Array.isArray(recap.entries) && recap.entries.length > 0;
|
|
125
|
+
const hasArtifacts = Array.isArray(recap.artifacts) && recap.artifacts.length > 0;
|
|
126
|
+
const hasContent = hasGoal || hasComplexity || hasEntries || hasArtifacts;
|
|
127
|
+
if (!hasContent) return null;
|
|
128
|
+
const recapOutput = {
|
|
129
|
+
filePath,
|
|
130
|
+
goal: recap.goal ?? null,
|
|
131
|
+
complexity: recap.complexity ?? null,
|
|
132
|
+
entries: recap.entries ?? [],
|
|
133
|
+
artifacts: recap.artifacts ?? []
|
|
134
|
+
};
|
|
135
|
+
return formatRecapMarkdown(recapOutput);
|
|
140
136
|
} catch {
|
|
141
137
|
return null;
|
|
142
138
|
}
|
|
143
139
|
}
|
|
140
|
+
var SwarmReportCollector = class {
|
|
141
|
+
constructor(metadataManager) {
|
|
142
|
+
this.metadataManager = metadataManager ?? new MetadataManager();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Collect implementation data from child issues and their recap files.
|
|
146
|
+
*
|
|
147
|
+
* @param childIssueNumbers - Array of child issue identifiers to collect data for
|
|
148
|
+
* @param epicWorktreePath - Worktree path of the parent epic loom
|
|
149
|
+
* @param settings - IloomSettings for configuring the issue management provider
|
|
150
|
+
* @returns Array of ChildImplementationData, one per child
|
|
151
|
+
*/
|
|
152
|
+
async collectChildData(childIssueNumbers, epicWorktreePath, settings) {
|
|
153
|
+
var _a;
|
|
154
|
+
if (childIssueNumbers.length === 0) return [];
|
|
155
|
+
const providerType = ((_a = settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
|
|
156
|
+
const provider = IssueManagementProviderFactory.create(providerType, settings);
|
|
157
|
+
const worktreeMap = await this.buildChildWorktreeMap(childIssueNumbers, epicWorktreePath);
|
|
158
|
+
const results = [];
|
|
159
|
+
for (let i = 0; i < childIssueNumbers.length; i += CONCURRENCY_LIMIT) {
|
|
160
|
+
const batch = childIssueNumbers.slice(i, i + CONCURRENCY_LIMIT);
|
|
161
|
+
const batchResults = await Promise.allSettled(
|
|
162
|
+
batch.map((num) => this.fetchChildData(num, provider, worktreeMap))
|
|
163
|
+
);
|
|
164
|
+
for (let j = 0; j < batchResults.length; j++) {
|
|
165
|
+
const result = batchResults[j];
|
|
166
|
+
const issueNumber = batch[j] ?? "?";
|
|
167
|
+
if (!result) continue;
|
|
168
|
+
if (result.status === "fulfilled") {
|
|
169
|
+
results.push(result.value);
|
|
170
|
+
} else {
|
|
171
|
+
getLogger().error(
|
|
172
|
+
`SwarmReportCollector: unexpected rejection for issue ${issueNumber}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
173
|
+
);
|
|
174
|
+
results.push({
|
|
175
|
+
issueNumber,
|
|
176
|
+
title: "Unknown",
|
|
177
|
+
status: "failure",
|
|
178
|
+
implementationComment: null,
|
|
179
|
+
recapMarkdown: null
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build a map of issueNumber -> worktreePath by scanning all loom metadata
|
|
188
|
+
* for children whose parentLoom.worktreePath matches the epic worktree.
|
|
189
|
+
*/
|
|
190
|
+
async buildChildWorktreeMap(childIssueNumbers, epicWorktreePath) {
|
|
191
|
+
var _a;
|
|
192
|
+
const map = /* @__PURE__ */ new Map();
|
|
193
|
+
try {
|
|
194
|
+
const allMetadata = await this.metadataManager.listAllMetadata();
|
|
195
|
+
for (const metadata of allMetadata) {
|
|
196
|
+
if (((_a = metadata.parentLoom) == null ? void 0 : _a.worktreePath) !== epicWorktreePath) continue;
|
|
197
|
+
if (!metadata.worktreePath) continue;
|
|
198
|
+
for (const issueNum of metadata.issue_numbers) {
|
|
199
|
+
if (childIssueNumbers.includes(issueNum)) {
|
|
200
|
+
map.set(issueNum, metadata.worktreePath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
getLogger().debug(
|
|
206
|
+
`SwarmReportCollector: failed to build worktree map: ${error instanceof Error ? error.message : String(error)}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return map;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Fetch issue data and recap for a single child.
|
|
213
|
+
* Returns status='failure' on API errors, status='missing' when issue has no comments.
|
|
214
|
+
*/
|
|
215
|
+
async fetchChildData(issueNumber, provider, worktreeMap) {
|
|
216
|
+
try {
|
|
217
|
+
const issue = await provider.getIssue({ number: issueNumber, includeComments: true });
|
|
218
|
+
const comments = issue.comments ?? [];
|
|
219
|
+
const implementationComment = this.extractImplementationComment(comments);
|
|
220
|
+
const worktreePath = worktreeMap.get(issueNumber) ?? null;
|
|
221
|
+
const recapMarkdown = worktreePath ? await this.readChildRecap(worktreePath) : null;
|
|
222
|
+
let status;
|
|
223
|
+
if (comments.length === 0) {
|
|
224
|
+
status = "missing";
|
|
225
|
+
} else {
|
|
226
|
+
const issueState = issue.state.toLowerCase();
|
|
227
|
+
status = issueState === "closed" || issueState === "done" ? "success" : "failure";
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
issueNumber,
|
|
231
|
+
title: issue.title,
|
|
232
|
+
status,
|
|
233
|
+
implementationComment,
|
|
234
|
+
recapMarkdown
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
getLogger().debug(
|
|
238
|
+
`SwarmReportCollector: failed to fetch data for issue ${issueNumber}: ${error instanceof Error ? error.message : String(error)}`
|
|
239
|
+
);
|
|
240
|
+
return {
|
|
241
|
+
issueNumber,
|
|
242
|
+
title: "Unknown",
|
|
243
|
+
status: "failure",
|
|
244
|
+
implementationComment: null,
|
|
245
|
+
recapMarkdown: null
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Extract the implementation comment from an issue's comment list.
|
|
251
|
+
* Prefers comments that contain implementation markers; falls back to the last comment.
|
|
252
|
+
*/
|
|
253
|
+
extractImplementationComment(comments) {
|
|
254
|
+
if (comments.length === 0) return null;
|
|
255
|
+
const implementationMarkers = [
|
|
256
|
+
"implementation complete",
|
|
257
|
+
"# implementation",
|
|
258
|
+
"## summary",
|
|
259
|
+
"changes made",
|
|
260
|
+
"validation results"
|
|
261
|
+
];
|
|
262
|
+
for (let i = comments.length - 1; i >= 0; i--) {
|
|
263
|
+
const comment = comments[i];
|
|
264
|
+
if (!comment) continue;
|
|
265
|
+
const body = comment.body.toLowerCase();
|
|
266
|
+
if (implementationMarkers.some((marker) => body.includes(marker))) {
|
|
267
|
+
return comment.body;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const lastComment = comments[comments.length - 1];
|
|
271
|
+
return lastComment ? lastComment.body : null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Read and format the recap file for a child worktree path.
|
|
275
|
+
* Returns null if the recap file is missing or cannot be read.
|
|
276
|
+
*/
|
|
277
|
+
async readChildRecap(worktreePath) {
|
|
278
|
+
return readRecapForWorktree(worktreePath);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/lib/SessionSummaryService.ts
|
|
283
|
+
var GITHUB_COMMENT_MAX_LENGTH = 65536;
|
|
144
284
|
var SessionSummaryService = class {
|
|
145
|
-
constructor(templateManager, metadataManager, settingsManager) {
|
|
285
|
+
constructor(templateManager, metadataManager, settingsManager, swarmReportCollector) {
|
|
146
286
|
this.templateManager = templateManager ?? new PromptTemplateManager();
|
|
147
287
|
this.metadataManager = metadataManager ?? new MetadataManager();
|
|
148
288
|
this.settingsManager = settingsManager ?? new SettingsManager();
|
|
289
|
+
this.swarmReportCollector = swarmReportCollector ?? new SwarmReportCollector();
|
|
149
290
|
}
|
|
150
291
|
/**
|
|
151
292
|
* Generate and post a session summary to the issue
|
|
@@ -174,7 +315,7 @@ var SessionSummaryService = class {
|
|
|
174
315
|
} else {
|
|
175
316
|
logger.debug("No compact summaries found in session transcript");
|
|
176
317
|
}
|
|
177
|
-
const recapData = await
|
|
318
|
+
const recapData = await readRecapForWorktree(input.worktreePath);
|
|
178
319
|
if (recapData) {
|
|
179
320
|
logger.debug(`Found recap data (${recapData.length} chars)`);
|
|
180
321
|
} else {
|
|
@@ -215,6 +356,102 @@ var SessionSummaryService = class {
|
|
|
215
356
|
logger.debug("Session summary generation error details:", { error });
|
|
216
357
|
}
|
|
217
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Format child implementation data as readable markdown for the epic report template.
|
|
361
|
+
* This replaces raw JSON.stringify so the AI gets structured, readable input.
|
|
362
|
+
*/
|
|
363
|
+
formatChildDataAsMarkdown(childData) {
|
|
364
|
+
return childData.map((child) => {
|
|
365
|
+
const sections = [
|
|
366
|
+
`### Child #${child.issueNumber}: ${child.title}`,
|
|
367
|
+
`**Status:** ${child.status}`
|
|
368
|
+
];
|
|
369
|
+
if (child.implementationComment) {
|
|
370
|
+
sections.push("", "**Implementation Summary:**", child.implementationComment);
|
|
371
|
+
}
|
|
372
|
+
if (child.recapMarkdown) {
|
|
373
|
+
sections.push("", "**Recap (decisions, risks, insights):**", child.recapMarkdown);
|
|
374
|
+
}
|
|
375
|
+
if (!child.implementationComment && !child.recapMarkdown) {
|
|
376
|
+
sections.push("", "*No implementation data available for this child.*");
|
|
377
|
+
}
|
|
378
|
+
return sections.join("\n");
|
|
379
|
+
}).join("\n\n---\n\n");
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Truncate a report to GitHub's comment size limit, appending a notice if truncated
|
|
383
|
+
*/
|
|
384
|
+
truncateReport(report) {
|
|
385
|
+
if (report.length <= GITHUB_COMMENT_MAX_LENGTH) return report;
|
|
386
|
+
const truncationNotice = "\n\n---\n*Report truncated due to GitHub comment size limit.*";
|
|
387
|
+
const maxContentLength = GITHUB_COMMENT_MAX_LENGTH - truncationNotice.length;
|
|
388
|
+
return report.slice(0, maxContentLength) + truncationNotice;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Generate and post an epic implementation report to the issue or PR
|
|
392
|
+
*
|
|
393
|
+
* Non-blocking: Catches all errors and logs warnings instead of throwing
|
|
394
|
+
* This ensures the finish workflow continues even if report generation fails
|
|
395
|
+
*/
|
|
396
|
+
async generateAndPostEpicReport(input) {
|
|
397
|
+
const log = getLogger();
|
|
398
|
+
try {
|
|
399
|
+
const settings = await this.settingsManager.loadSettings(input.worktreePath);
|
|
400
|
+
if (!this.shouldGenerateSummary("epic", settings)) {
|
|
401
|
+
log.debug("Skipping epic report: generateSummary is disabled");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
log.info("Generating epic implementation report...");
|
|
405
|
+
const childData = await this.swarmReportCollector.collectChildData(
|
|
406
|
+
input.childIssueNumbers,
|
|
407
|
+
input.worktreePath,
|
|
408
|
+
settings
|
|
409
|
+
);
|
|
410
|
+
const totalSucceeded = childData.filter((c) => c.status === "success").length;
|
|
411
|
+
const totalFailed = childData.filter((c) => c.status === "failure").length;
|
|
412
|
+
const prompt = await this.templateManager.getPrompt("epic-report", {
|
|
413
|
+
EPIC_NUMBER: String(input.epicIssueNumber),
|
|
414
|
+
EPIC_TITLE: input.epicTitle,
|
|
415
|
+
CHILD_DATA: this.formatChildDataAsMarkdown(childData),
|
|
416
|
+
TOTAL_CHILDREN: String(childData.length),
|
|
417
|
+
TOTAL_SUCCEEDED: String(totalSucceeded),
|
|
418
|
+
TOTAL_FAILED: String(totalFailed)
|
|
419
|
+
});
|
|
420
|
+
log.debug("Epic report prompt:\n" + prompt);
|
|
421
|
+
const summaryModel = this.settingsManager.getSummaryModel(settings);
|
|
422
|
+
const reportResult = await launchClaude(prompt, {
|
|
423
|
+
headless: true,
|
|
424
|
+
model: summaryModel
|
|
425
|
+
});
|
|
426
|
+
if (!reportResult || typeof reportResult !== "string" || reportResult.trim() === "") {
|
|
427
|
+
log.warn("Epic report generation returned empty result");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const report = this.truncateReport(reportResult.trim());
|
|
431
|
+
await this.postSummaryToIssue(
|
|
432
|
+
input.epicIssueNumber,
|
|
433
|
+
report,
|
|
434
|
+
settings,
|
|
435
|
+
input.worktreePath,
|
|
436
|
+
input.prNumber
|
|
437
|
+
);
|
|
438
|
+
const target = input.prNumber ? `PR #${input.prNumber}` : "issue";
|
|
439
|
+
log.success(`Epic implementation report posted to ${target}`);
|
|
440
|
+
try {
|
|
441
|
+
TelemetryService.getInstance().track("epic.report_generated", {
|
|
442
|
+
total_children: childData.length,
|
|
443
|
+
succeeded: totalSucceeded,
|
|
444
|
+
failed: totalFailed
|
|
445
|
+
});
|
|
446
|
+
} catch (telemetryError) {
|
|
447
|
+
log.debug(`Telemetry tracking failed: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`);
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
451
|
+
log.warn(`Failed to generate epic report: ${errorMessage}`);
|
|
452
|
+
log.debug("Epic report generation error details:", { error });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
218
455
|
/**
|
|
219
456
|
* Generate a session summary without posting it
|
|
220
457
|
*
|
|
@@ -240,7 +477,7 @@ var SessionSummaryService = class {
|
|
|
240
477
|
} else {
|
|
241
478
|
logger.debug("No compact summaries found in session transcript");
|
|
242
479
|
}
|
|
243
|
-
const recapData = await
|
|
480
|
+
const recapData = await readRecapForWorktree(worktreePath);
|
|
244
481
|
if (recapData) {
|
|
245
482
|
logger.debug(`Found recap data (${recapData.length} chars)`);
|
|
246
483
|
} else {
|
|
@@ -299,7 +536,8 @@ var SessionSummaryService = class {
|
|
|
299
536
|
if (loomType === "branch") {
|
|
300
537
|
return false;
|
|
301
538
|
}
|
|
302
|
-
const
|
|
539
|
+
const effectiveType = loomType === "epic" ? "issue" : loomType;
|
|
540
|
+
const workflowConfig = effectiveType === "issue" ? (_a = settings.workflows) == null ? void 0 : _a.issue : (_b = settings.workflows) == null ? void 0 : _b.pr;
|
|
303
541
|
return (workflowConfig == null ? void 0 : workflowConfig.generateSummary) ?? true;
|
|
304
542
|
}
|
|
305
543
|
/**
|
|
@@ -379,4 +617,4 @@ var SessionSummaryService = class {
|
|
|
379
617
|
export {
|
|
380
618
|
SessionSummaryService
|
|
381
619
|
};
|
|
382
|
-
//# sourceMappingURL=chunk-
|
|
620
|
+
//# sourceMappingURL=chunk-U2OPXZ6E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/claude-transcript.ts","../src/lib/SwarmReportCollector.ts","../src/lib/SessionSummaryService.ts"],"sourcesContent":["/**\n * Claude Transcript Utilities\n *\n * Provides functions to read and parse Claude Code session transcript files\n * stored in ~/.claude/projects/. These transcripts contain the full conversation\n * history including compact summaries from when conversations were compacted.\n */\n\nimport { readFile } from 'fs/promises'\nimport { homedir } from 'os'\nimport { join } from 'path'\nimport { logger } from './logger.js'\n\n/**\n * Entry in a Claude Code JSONL transcript file\n */\nexport interface TranscriptEntry {\n\ttype: 'user' | 'assistant' | 'system' | 'file-history-snapshot' | 'queue-operation'\n\tsessionId?: string\n\tmessage?: { role: string; content: string | Array<{ type: string; text?: string }> }\n\tisCompactSummary?: boolean\n\tisVisibleInTranscriptOnly?: boolean\n\tsubtype?: string // 'compact_boundary' for compaction markers\n\tcontent?: string\n\ttimestamp?: string\n\tuuid?: string\n\tparentUuid?: string\n}\n\n/**\n * Get the Claude projects directory path encoding for a worktree path\n * Encoding: /Users/adam/Projects/foo_bar -> -Users-adam-Projects-foo-bar\n *\n * Claude Code encodes paths by replacing both '/' and '_' with '-'\n *\n * @param worktreePath - Absolute path to the worktree\n * @returns Encoded directory name for Claude projects\n */\nexport function getClaudeProjectPath(worktreePath: string): string {\n\t// Replace all '/' and '_' with '-' (matching Claude Code's encoding)\n\treturn worktreePath.replace(/[/_]/g, '-')\n}\n\n/**\n * Get the full path to the Claude projects directory\n * @returns Path to ~/.claude/projects/\n */\nexport function getClaudeProjectsDir(): string {\n\treturn join(homedir(), '.claude', 'projects')\n}\n\n/**\n * Find the session transcript file for a given worktree and session ID\n *\n * @param worktreePath - Absolute path to the worktree\n * @param sessionId - Session ID to find transcript for\n * @returns Full path to the transcript file, or null if not found\n */\nexport function findSessionTranscript(worktreePath: string, sessionId: string): string | null {\n\tconst projectsDir = getClaudeProjectsDir()\n\tconst projectDirName = getClaudeProjectPath(worktreePath)\n\tconst transcriptPath = join(projectsDir, projectDirName, `${sessionId}.jsonl`)\n\treturn transcriptPath\n}\n\n/**\n * Extract the content from a compact summary message\n * Handles both string content and array content formats\n */\nfunction extractMessageContent(message: TranscriptEntry['message']): string | null {\n\tif (!message) return null\n\n\tif (typeof message.content === 'string') {\n\t\treturn message.content\n\t}\n\n\tif (Array.isArray(message.content)) {\n\t\t// Concatenate all text elements\n\t\treturn message.content\n\t\t\t.filter((item) => item.type === 'text' && item.text)\n\t\t\t.map((item) => item.text)\n\t\t\t.join('\\n')\n\t}\n\n\treturn null\n}\n\n/**\n * Extract compact summaries from a session transcript file\n *\n * Returns empty array if file doesn't exist or no summaries found.\n * Each compact summary contains structured history of pre-compaction conversation.\n *\n * @param transcriptPath - Full path to the transcript JSONL file\n * @param maxSummaries - Maximum number of summaries to return (default 3)\n * @returns Array of compact summary content strings, newest first\n */\nexport async function extractCompactSummaries(\n\ttranscriptPath: string,\n\tmaxSummaries = 3\n): Promise<string[]> {\n\ttry {\n\t\tconst content = await readFile(transcriptPath, 'utf-8')\n\t\tconst lines = content.split('\\n').filter((line) => line.trim())\n\n\t\tconst summaries: string[] = []\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as TranscriptEntry\n\n\t\t\t\t// Look for compact summary entries\n\t\t\t\tif (entry.isCompactSummary === true && entry.message) {\n\t\t\t\t\tconst summaryContent = extractMessageContent(entry.message)\n\t\t\t\t\tif (summaryContent) {\n\t\t\t\t\t\tsummaries.push(summaryContent)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed JSON lines\n\t\t\t\tlogger.debug('Skipping malformed JSONL line in transcript')\n\t\t\t}\n\t\t}\n\n\t\t// Return most recent summaries (they appear in order in the file)\n\t\t// Limit to maxSummaries\n\t\treturn summaries.slice(-maxSummaries)\n\t} catch (error) {\n\t\t// File not found or permission error - return empty array (graceful degradation)\n\t\tif (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n\t\t\tlogger.debug('Transcript file not found:', transcriptPath)\n\t\t} else {\n\t\t\tlogger.debug('Error reading transcript file:', error)\n\t\t}\n\t\treturn []\n\t}\n}\n\n/**\n * Read session transcript and extract compact summaries for summary generation\n *\n * This is the main entry point for SessionSummaryService to get pre-compaction\n * conversation context. It gracefully handles all error cases.\n *\n * @param worktreePath - Absolute path to the worktree\n * @param sessionId - Session ID to find transcript for\n * @param maxSummaries - Maximum number of summaries to return (default 3)\n * @returns Formatted string of compact summaries, or null if none found\n */\nexport async function readSessionContext(\n\tworktreePath: string,\n\tsessionId: string,\n\tmaxSummaries = 3\n): Promise<string | null> {\n\tconst transcriptPath = findSessionTranscript(worktreePath, sessionId)\n\tif (!transcriptPath) {\n\t\treturn null\n\t}\n\n\tlogger.debug(`Checking transcript at: ${transcriptPath}`)\n\n\tconst summaries = await extractCompactSummaries(transcriptPath, maxSummaries)\n\n\tif (summaries.length === 0) {\n\t\treturn null\n\t}\n\n\t// Format summaries with separators\n\t// Newest summaries are at the end, so we reverse to show newest first\n\tconst formattedSummaries = summaries\n\t\t.reverse()\n\t\t.map((summary, index) => {\n\t\t\tconst header =\n\t\t\t\tsummaries.length > 1\n\t\t\t\t\t? `### Compact Summary ${index + 1} of ${summaries.length}\\n\\n`\n\t\t\t\t\t: ''\n\t\t\treturn `${header}${summary}`\n\t\t})\n\t\t.join('\\n\\n---\\n\\n')\n\n\treturn formattedSummaries\n}\n","/**\n * SwarmReportCollector: Aggregates implementation data from child issues and recap files\n *\n * This service collects structured per-child implementation data for use in\n * epic swarm implementation reports. It gathers:\n * 1. Issue title and implementation comments from the issue tracker\n * 2. Recap file data (decisions, risks, assumptions, insights) from disk\n */\n\nimport fs from 'fs-extra'\nimport { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'\nimport type { IssueManagementProvider, IssueProvider } from '../mcp/types.js'\nimport type { IloomSettings } from './SettingsManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport { resolveRecapFilePath } from '../utils/mcp.js'\nimport type { RecapFile, RecapOutput } from '../mcp/recap-types.js'\nimport { formatRecapMarkdown } from '../utils/recap-formatter.js'\nimport { getLogger } from '../utils/logger-context.js'\n\nexport interface ChildImplementationData {\n\tissueNumber: string\n\ttitle: string\n\tstatus: 'success' | 'failure' | 'missing'\n\timplementationComment: string | null\n\trecapMarkdown: string | null\n}\n\n// Concurrency limit for API calls\nconst CONCURRENCY_LIMIT = 5\n\n/**\n * Read and format the recap file for a worktree path.\n * Returns formatted recap markdown or null if not found/empty/error.\n * Shared between SwarmReportCollector and SessionSummaryService.\n */\nexport async function readRecapForWorktree(worktreePath: string): Promise<string | null> {\n\ttry {\n\t\tconst filePath = resolveRecapFilePath(worktreePath)\n\t\tif (!(await fs.pathExists(filePath))) return null\n\n\t\tconst content = await fs.readFile(filePath, 'utf8')\n\t\tconst recap = JSON.parse(content) as RecapFile\n\n\t\tconst hasGoal = recap.goal !== null && recap.goal !== undefined\n\t\tconst hasComplexity = recap.complexity !== null && recap.complexity !== undefined\n\t\tconst hasEntries = Array.isArray(recap.entries) && recap.entries.length > 0\n\t\tconst hasArtifacts = Array.isArray(recap.artifacts) && recap.artifacts.length > 0\n\t\tconst hasContent = hasGoal || hasComplexity || hasEntries || hasArtifacts\n\n\t\tif (!hasContent) return null\n\n\t\tconst recapOutput: RecapOutput = {\n\t\t\tfilePath,\n\t\t\tgoal: recap.goal ?? null,\n\t\t\tcomplexity: recap.complexity ?? null,\n\t\t\tentries: recap.entries ?? [],\n\t\t\tartifacts: recap.artifacts ?? [],\n\t\t}\n\t\treturn formatRecapMarkdown(recapOutput)\n\t} catch {\n\t\t// Graceful degradation - return null on any error\n\t\treturn null\n\t}\n}\n\nexport class SwarmReportCollector {\n\tprivate metadataManager: MetadataManager\n\n\tconstructor(metadataManager?: MetadataManager) {\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t}\n\n\t/**\n\t * Collect implementation data from child issues and their recap files.\n\t *\n\t * @param childIssueNumbers - Array of child issue identifiers to collect data for\n\t * @param epicWorktreePath - Worktree path of the parent epic loom\n\t * @param settings - IloomSettings for configuring the issue management provider\n\t * @returns Array of ChildImplementationData, one per child\n\t */\n\tasync collectChildData(\n\t\tchildIssueNumbers: string[],\n\t\tepicWorktreePath: string,\n\t\tsettings: IloomSettings\n\t): Promise<ChildImplementationData[]> {\n\t\tif (childIssueNumbers.length === 0) return []\n\n\t\t// Create issue management provider from settings\n\t\tconst providerType = (settings.issueManagement?.provider ?? 'github') as IssueProvider\n\t\tconst provider = IssueManagementProviderFactory.create(providerType, settings)\n\n\t\t// Build map of issueNumber -> worktreePath from loom metadata\n\t\tconst worktreeMap = await this.buildChildWorktreeMap(childIssueNumbers, epicWorktreePath)\n\n\t\t// Process children in batches using Promise.allSettled for bounded concurrency\n\t\tconst results: ChildImplementationData[] = []\n\t\tfor (let i = 0; i < childIssueNumbers.length; i += CONCURRENCY_LIMIT) {\n\t\t\tconst batch = childIssueNumbers.slice(i, i + CONCURRENCY_LIMIT)\n\t\t\tconst batchResults = await Promise.allSettled(\n\t\t\t\tbatch.map(num => this.fetchChildData(num, provider, worktreeMap))\n\t\t\t)\n\t\t\tfor (let j = 0; j < batchResults.length; j++) {\n\t\t\t\tconst result = batchResults[j]\n\t\t\t\tconst issueNumber = batch[j] ?? '?'\n\t\t\t\tif (!result) continue\n\t\t\t\tif (result.status === 'fulfilled') {\n\t\t\t\t\tresults.push(result.value)\n\t\t\t\t} else {\n\t\t\t\t\t// fetchChildData catches internally; this branch is a defensive fallback\n\t\t\t\t\tgetLogger().error(\n\t\t\t\t\t\t`SwarmReportCollector: unexpected rejection for issue ${issueNumber}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`\n\t\t\t\t\t)\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tissueNumber,\n\t\t\t\t\t\ttitle: 'Unknown',\n\t\t\t\t\t\tstatus: 'failure',\n\t\t\t\t\t\timplementationComment: null,\n\t\t\t\t\t\trecapMarkdown: null,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn results\n\t}\n\n\t/**\n\t * Build a map of issueNumber -> worktreePath by scanning all loom metadata\n\t * for children whose parentLoom.worktreePath matches the epic worktree.\n\t */\n\tprivate async buildChildWorktreeMap(\n\t\tchildIssueNumbers: string[],\n\t\tepicWorktreePath: string\n\t): Promise<Map<string, string>> {\n\t\tconst map = new Map<string, string>()\n\t\ttry {\n\t\t\tconst allMetadata = await this.metadataManager.listAllMetadata()\n\t\t\tfor (const metadata of allMetadata) {\n\t\t\t\t// Only consider child looms that belong to this epic\n\t\t\t\tif (metadata.parentLoom?.worktreePath !== epicWorktreePath) continue\n\t\t\t\tif (!metadata.worktreePath) continue\n\n\t\t\t\t// Map each issue number associated with this child loom\n\t\t\t\tfor (const issueNum of metadata.issue_numbers) {\n\t\t\t\t\tif (childIssueNumbers.includes(issueNum)) {\n\t\t\t\t\t\tmap.set(issueNum, metadata.worktreePath)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`SwarmReportCollector: failed to build worktree map: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t)\n\t\t}\n\t\treturn map\n\t}\n\n\t/**\n\t * Fetch issue data and recap for a single child.\n\t * Returns status='failure' on API errors, status='missing' when issue has no comments.\n\t */\n\tprivate async fetchChildData(\n\t\tissueNumber: string,\n\t\tprovider: IssueManagementProvider,\n\t\tworktreeMap: Map<string, string>\n\t): Promise<ChildImplementationData> {\n\t\ttry {\n\t\t\tconst issue = await provider.getIssue({ number: issueNumber, includeComments: true })\n\n\t\t\tconst comments = issue.comments ?? []\n\t\t\tconst implementationComment = this.extractImplementationComment(comments)\n\n\t\t\tconst worktreePath = worktreeMap.get(issueNumber) ?? null\n\t\t\tconst recapMarkdown = worktreePath ? await this.readChildRecap(worktreePath) : null\n\n\t\t\t// Determine status based on issue state and comments:\n\t\t\t// - No comments at all: 'missing' (child may not have started)\n\t\t\t// - Issue closed (state includes 'closed' or 'done'): 'success' (completed normally)\n\t\t\t// - Issue still open with comments: 'failure' (child started but didn't finish)\n\t\t\tlet status: 'success' | 'failure' | 'missing'\n\t\t\tif (comments.length === 0) {\n\t\t\t\tstatus = 'missing'\n\t\t\t} else {\n\t\t\t\tconst issueState = issue.state.toLowerCase()\n\t\t\t\tstatus = (issueState === 'closed' || issueState === 'done') ? 'success' : 'failure'\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tissueNumber,\n\t\t\t\ttitle: issue.title,\n\t\t\t\tstatus,\n\t\t\t\timplementationComment,\n\t\t\t\trecapMarkdown,\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tgetLogger().debug(\n\t\t\t\t`SwarmReportCollector: failed to fetch data for issue ${issueNumber}: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tissueNumber,\n\t\t\t\ttitle: 'Unknown',\n\t\t\t\tstatus: 'failure',\n\t\t\t\timplementationComment: null,\n\t\t\t\trecapMarkdown: null,\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Extract the implementation comment from an issue's comment list.\n\t * Prefers comments that contain implementation markers; falls back to the last comment.\n\t */\n\tprivate extractImplementationComment(\n\t\tcomments: Array<{ id: string; body: string; author: unknown; createdAt: string; [key: string]: unknown }>\n\t): string | null {\n\t\tif (comments.length === 0) return null\n\n\t\t// Prefer a comment with clear implementation markers\n\t\tconst implementationMarkers = [\n\t\t\t'implementation complete',\n\t\t\t'# implementation',\n\t\t\t'## summary',\n\t\t\t'changes made',\n\t\t\t'validation results',\n\t\t]\n\n\t\tfor (let i = comments.length - 1; i >= 0; i--) {\n\t\t\tconst comment = comments[i]\n\t\t\tif (!comment) continue\n\t\t\tconst body = comment.body.toLowerCase()\n\t\t\tif (implementationMarkers.some(marker => body.includes(marker))) {\n\t\t\t\treturn comment.body\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to the last comment\n\t\tconst lastComment = comments[comments.length - 1]\n\t\treturn lastComment ? lastComment.body : null\n\t}\n\n\t/**\n\t * Read and format the recap file for a child worktree path.\n\t * Returns null if the recap file is missing or cannot be read.\n\t */\n\tprivate async readChildRecap(worktreePath: string): Promise<string | null> {\n\t\treturn readRecapForWorktree(worktreePath)\n\t}\n}\n","/**\n * SessionSummaryService: Generates and posts Claude session summaries\n *\n * This service orchestrates:\n * 1. Reading session metadata to get session ID\n * 2. Loading and processing the session-summary prompt template\n * 3. Invoking Claude headless to generate the summary\n * 4. Posting the summary as a comment to the issue/PR\n */\n\nimport { logger } from '../utils/logger.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { launchClaude, generateDeterministicSessionId } from '../utils/claude.js'\nimport { readSessionContext } from '../utils/claude-transcript.js'\nimport { PromptTemplateManager } from './PromptTemplateManager.js'\nimport { MetadataManager } from './MetadataManager.js'\nimport { SettingsManager, type IloomSettings } from './SettingsManager.js'\nimport { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js'\nimport type { IssueProvider } from '../mcp/types.js'\nimport { VCSProviderFactory } from './VCSProviderFactory.js'\nimport { hasMultipleRemotes } from '../utils/remote.js'\nimport { SwarmReportCollector, readRecapForWorktree, type ChildImplementationData } from './SwarmReportCollector.js'\nimport { TelemetryService } from './TelemetryService.js'\n\nconst GITHUB_COMMENT_MAX_LENGTH = 65_536\n\n/**\n * Input for generating and posting a session summary\n */\nexport interface SessionSummaryInput {\n\tworktreePath: string\n\tissueNumber: string | number\n\tbranchName: string\n\tloomType: 'issue' | 'pr' | 'branch' | 'epic'\n\t/** Optional PR number - when provided, summary is posted to the PR instead of the issue */\n\tprNumber?: number\n}\n\n/**\n * Result from generating a session summary\n */\nexport interface SessionSummaryResult {\n\tsummary: string\n\tsessionId: string\n}\n\n/**\n * Input for generating and posting an epic implementation report\n */\nexport interface EpicReportInput {\n\tworktreePath: string\n\tepicIssueNumber: string | number\n\tchildIssueNumbers: string[]\n\tepicTitle: string\n\t/** Optional PR number - when provided, report is posted to the PR instead of the issue */\n\tprNumber?: number\n}\n\n/**\n * Service that generates and posts Claude session summaries to issues\n */\nexport class SessionSummaryService {\n\tprivate templateManager: PromptTemplateManager\n\treadonly metadataManager: MetadataManager\n\tprivate settingsManager: SettingsManager\n\tprivate swarmReportCollector: SwarmReportCollector\n\n\tconstructor(\n\t\ttemplateManager?: PromptTemplateManager,\n\t\tmetadataManager?: MetadataManager,\n\t\tsettingsManager?: SettingsManager,\n\t\tswarmReportCollector?: SwarmReportCollector\n\t) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.metadataManager = metadataManager ?? new MetadataManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.swarmReportCollector = swarmReportCollector ?? new SwarmReportCollector()\n\t}\n\n\t/**\n\t * Generate and post a session summary to the issue\n\t *\n\t * Non-blocking: Catches all errors and logs warnings instead of throwing\n\t * This ensures the finish workflow continues even if summary generation fails\n\t */\n\tasync generateAndPostSummary(input: SessionSummaryInput): Promise<void> {\n\t\ttry {\n\t\t\t// 1. Skip for branch type (no issue to comment on)\n\t\t\tif (input.loomType === 'branch') {\n\t\t\t\tlogger.debug('Skipping session summary: branch type has no associated issue')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 2. Read metadata to get sessionId, or generate deterministically\n\t\t\tconst metadata = await this.metadataManager.readMetadata(input.worktreePath)\n\t\t\tconst sessionId = metadata?.sessionId ?? generateDeterministicSessionId(input.worktreePath)\n\n\t\t\t// 3. Load settings to check generateSummary config\n\t\t\tconst settings = await this.settingsManager.loadSettings(input.worktreePath)\n\t\t\tif (!this.shouldGenerateSummary(input.loomType, settings)) {\n\t\t\t\tlogger.debug(`Skipping session summary: generateSummary is disabled for ${input.loomType} workflow`)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlogger.info('Generating session summary...')\n\n\t\t\t// 4. Try to read compact summaries from session transcript for additional context\n\t\t\tlogger.debug(`Looking for session transcript with sessionId: ${sessionId}`)\n\t\t\tconst compactSummaries = await readSessionContext(input.worktreePath, sessionId)\n\t\t\tif (compactSummaries) {\n\t\t\t\tlogger.debug(`Found compact summaries (${compactSummaries.length} chars)`)\n\t\t\t} else {\n\t\t\t\tlogger.debug('No compact summaries found in session transcript')\n\t\t\t}\n\n\t\t\t// 5. Try to read recap data for high-signal context\n\t\t\tconst recapData = await readRecapForWorktree(input.worktreePath)\n\t\t\tif (recapData) {\n\t\t\t\tlogger.debug(`Found recap data (${recapData.length} chars)`)\n\t\t\t} else {\n\t\t\t\tlogger.debug('No recap data found')\n\t\t\t}\n\n\t\t\t// 6. Load and process the session-summary template\n\t\t\tconst prompt = await this.templateManager.getPrompt('session-summary', {\n\t\t\t\tISSUE_NUMBER: String(input.issueNumber),\n\t\t\t\tBRANCH_NAME: input.branchName,\n\t\t\t\tLOOM_TYPE: input.loomType,\n\t\t\t\tCOMPACT_SUMMARIES: compactSummaries ?? '',\n\t\t\t\tRECAP_DATA: recapData ?? '',\n\t\t\t})\n\n\t\t\tlogger.debug('Session summary prompt:\\n' + prompt)\n\n\t\t\t// 7. Invoke Claude headless to generate summary\n\t\t\t// Use --resume with session ID so Claude knows which conversation to summarize\n\t\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\t\tconst summaryResult = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: summaryModel,\n\t\t\t\tsessionId: sessionId, // Resume this session so Claude has conversation context\n\t\t\t\tnoSessionPersistence: true, // Don't persist new data after generating summary\n\t\t\t})\n\n\t\t\tif (!summaryResult || typeof summaryResult !== 'string' || summaryResult.trim() === '') {\n\t\t\t\tlogger.warn('Session summary generation returned empty result')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst summary = summaryResult.trim()\n\n\t\t\t// 8. Skip posting if summary is too short (likely failed generation)\n\t\t\tif (summary.length < 100) {\n\t\t\t\tlogger.warn('Session summary too short, skipping post')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 9. Post summary to issue or PR (PR takes priority when prNumber is provided)\n\t\t\tawait this.postSummaryToIssue(input.issueNumber, summary, settings, input.worktreePath, input.prNumber)\n\n\t\t\tconst targetDescription = input.prNumber ? `PR #${input.prNumber}` : 'issue'\n\t\t\tlogger.success(`Session summary posted to ${targetDescription}`)\n\t\t} catch (error) {\n\t\t\t// Non-blocking: Log warning but don't throw\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\t\tlogger.warn(`Failed to generate session summary: ${errorMessage}`)\n\t\t\tlogger.debug('Session summary generation error details:', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Format child implementation data as readable markdown for the epic report template.\n\t * This replaces raw JSON.stringify so the AI gets structured, readable input.\n\t */\n\tprivate formatChildDataAsMarkdown(childData: ChildImplementationData[]): string {\n\t\treturn childData.map(child => {\n\t\t\tconst sections: string[] = [\n\t\t\t\t`### Child #${child.issueNumber}: ${child.title}`,\n\t\t\t\t`**Status:** ${child.status}`,\n\t\t\t]\n\n\t\t\tif (child.implementationComment) {\n\t\t\t\tsections.push('', '**Implementation Summary:**', child.implementationComment)\n\t\t\t}\n\n\t\t\tif (child.recapMarkdown) {\n\t\t\t\tsections.push('', '**Recap (decisions, risks, insights):**', child.recapMarkdown)\n\t\t\t}\n\n\t\t\tif (!child.implementationComment && !child.recapMarkdown) {\n\t\t\t\tsections.push('', '*No implementation data available for this child.*')\n\t\t\t}\n\n\t\t\treturn sections.join('\\n')\n\t\t}).join('\\n\\n---\\n\\n')\n\t}\n\n\t/**\n\t * Truncate a report to GitHub's comment size limit, appending a notice if truncated\n\t */\n\tprivate truncateReport(report: string): string {\n\t\tif (report.length <= GITHUB_COMMENT_MAX_LENGTH) return report\n\t\tconst truncationNotice = '\\n\\n---\\n*Report truncated due to GitHub comment size limit.*'\n\t\tconst maxContentLength = GITHUB_COMMENT_MAX_LENGTH - truncationNotice.length\n\t\treturn report.slice(0, maxContentLength) + truncationNotice\n\t}\n\n\t/**\n\t * Generate and post an epic implementation report to the issue or PR\n\t *\n\t * Non-blocking: Catches all errors and logs warnings instead of throwing\n\t * This ensures the finish workflow continues even if report generation fails\n\t */\n\tasync generateAndPostEpicReport(input: EpicReportInput): Promise<void> {\n\t\tconst log = getLogger()\n\t\ttry {\n\t\t\t// 1. Load settings, check generateSummary\n\t\t\tconst settings = await this.settingsManager.loadSettings(input.worktreePath)\n\t\t\tif (!this.shouldGenerateSummary('epic', settings)) {\n\t\t\t\tlog.debug('Skipping epic report: generateSummary is disabled')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlog.info('Generating epic implementation report...')\n\n\t\t\t// 2. Collect child data via SwarmReportCollector\n\t\t\tconst childData = await this.swarmReportCollector.collectChildData(\n\t\t\t\tinput.childIssueNumbers, input.worktreePath, settings\n\t\t\t)\n\n\t\t\t// 3. Load epic-report template with variables\n\t\t\tconst totalSucceeded = childData.filter(c => c.status === 'success').length\n\t\t\tconst totalFailed = childData.filter(c => c.status === 'failure').length\n\t\t\tconst prompt = await this.templateManager.getPrompt('epic-report', {\n\t\t\t\tEPIC_NUMBER: String(input.epicIssueNumber),\n\t\t\t\tEPIC_TITLE: input.epicTitle,\n\t\t\t\tCHILD_DATA: this.formatChildDataAsMarkdown(childData),\n\t\t\t\tTOTAL_CHILDREN: String(childData.length),\n\t\t\t\tTOTAL_SUCCEEDED: String(totalSucceeded),\n\t\t\t\tTOTAL_FAILED: String(totalFailed),\n\t\t\t})\n\n\t\t\tlog.debug('Epic report prompt:\\n' + prompt)\n\n\t\t\t// 4. Invoke Claude headless to generate report\n\t\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\t\tconst reportResult = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: summaryModel,\n\t\t\t})\n\n\t\t\tif (!reportResult || typeof reportResult !== 'string' || reportResult.trim() === '') {\n\t\t\t\tlog.warn('Epic report generation returned empty result')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 5. Truncate if needed, then post\n\t\t\tconst report = this.truncateReport(reportResult.trim())\n\t\t\tawait this.postSummaryToIssue(\n\t\t\t\tinput.epicIssueNumber, report, settings, input.worktreePath, input.prNumber\n\t\t\t)\n\n\t\t\tconst target = input.prNumber ? `PR #${input.prNumber}` : 'issue'\n\t\t\tlog.success(`Epic implementation report posted to ${target}`)\n\n\t\t\t// Track telemetry for epic report generation\n\t\t\ttry {\n\t\t\t\tTelemetryService.getInstance().track('epic.report_generated', {\n\t\t\t\t\ttotal_children: childData.length,\n\t\t\t\t\tsucceeded: totalSucceeded,\n\t\t\t\t\tfailed: totalFailed,\n\t\t\t\t})\n\t\t\t} catch (telemetryError) {\n\t\t\t\tlog.debug(`Telemetry tracking failed: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Non-blocking: log warning but don't throw\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\t\tlog.warn(`Failed to generate epic report: ${errorMessage}`)\n\t\t\tlog.debug('Epic report generation error details:', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Generate a session summary without posting it\n\t *\n\t * This method is useful for previewing the summary or for use by CLI commands\n\t * that want to display the summary before optionally posting it.\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param branchName - Name of the branch\n\t * @param loomType - Type of loom ('issue' | 'pr' | 'branch')\n\t * @param issueNumber - Issue or PR number (optional, for template variables)\n\t * @returns The generated summary and session ID\n\t * @throws Error if Claude invocation fails\n\t */\n\tasync generateSummary(\n\t\tworktreePath: string,\n\t\tbranchName: string,\n\t\tloomType: 'issue' | 'pr' | 'branch' | 'epic',\n\t\tissueNumber?: string | number\n\t): Promise<SessionSummaryResult> {\n\t\t// 1. Read metadata or generate deterministic session ID\n\t\tconst metadata = await this.metadataManager.readMetadata(worktreePath)\n\t\tconst sessionId = metadata?.sessionId ?? generateDeterministicSessionId(worktreePath)\n\n\t\t// 2. Load settings for model configuration\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\n\t\tlogger.info('Generating session summary...')\n\n\t\t// 3. Try to read compact summaries from session transcript for additional context\n\t\tlogger.debug(`Looking for session transcript with sessionId: ${sessionId}`)\n\t\tconst compactSummaries = await readSessionContext(worktreePath, sessionId)\n\t\tif (compactSummaries) {\n\t\t\tlogger.debug(`Found compact summaries (${compactSummaries.length} chars)`)\n\t\t} else {\n\t\t\tlogger.debug('No compact summaries found in session transcript')\n\t\t}\n\n\t\t// 4. Try to read recap data for high-signal context\n\t\tconst recapData = await readRecapForWorktree(worktreePath)\n\t\tif (recapData) {\n\t\t\tlogger.debug(`Found recap data (${recapData.length} chars)`)\n\t\t} else {\n\t\t\tlogger.debug('No recap data found')\n\t\t}\n\n\t\t// 5. Load and process the session-summary template\n\t\tconst prompt = await this.templateManager.getPrompt('session-summary', {\n\t\t\tISSUE_NUMBER: issueNumber !== undefined ? String(issueNumber) : '',\n\t\t\tBRANCH_NAME: branchName,\n\t\t\tLOOM_TYPE: loomType,\n\t\t\tCOMPACT_SUMMARIES: compactSummaries ?? '',\n\t\t\tRECAP_DATA: recapData ?? '',\n\t\t})\n\n\t\tlogger.debug('Session summary prompt:\\n' + prompt)\n\n\t\t// 6. Invoke Claude headless to generate summary\n\t\tconst summaryModel = this.settingsManager.getSummaryModel(settings)\n\t\tconst summaryResult = await launchClaude(prompt, {\n\t\t\theadless: true,\n\t\t\tmodel: summaryModel,\n\t\t\tsessionId: sessionId,\n\t\t\tnoSessionPersistence: true, // Don't persist new data after generating summary\n\t\t})\n\n\t\tif (!summaryResult || typeof summaryResult !== 'string' || summaryResult.trim() === '') {\n\t\t\tthrow new Error('Session summary generation returned empty result')\n\t\t}\n\n\t\tconst summary = summaryResult.trim()\n\n\t\t// 7. Check if summary is too short (likely failed generation)\n\t\tif (summary.length < 100) {\n\t\t\tthrow new Error('Session summary too short - generation may have failed')\n\t\t}\n\n\t\treturn {\n\t\t\tsummary,\n\t\t\tsessionId: sessionId,\n\t\t}\n\t}\n\n\t/**\n\t * Post a summary to an issue (used by both generateAndPostSummary and CLI commands)\n\t *\n\t * @param issueNumber - Issue or PR number to post to\n\t * @param summary - The summary text to post\n\t * @param worktreePath - Path to worktree for loading settings (optional)\n\t */\n\tasync postSummary(\n\t\tissueNumber: string | number,\n\t\tsummary: string,\n\t\tworktreePath?: string,\n\t\tprNumber?: number\n\t): Promise<void> {\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\tawait this.postSummaryToIssue(issueNumber, summary, settings, worktreePath ?? process.cwd(), prNumber)\n\t\tconst target = prNumber ? `PR #${prNumber}` : 'issue'\n\t\tlogger.success(`Session summary posted to ${target}`)\n\t}\n\n\t/**\n\t * Determine if summary should be generated based on loom type and settings\n\t *\n\t * @param loomType - The type of loom being finished\n\t * @param settings - The loaded iloom settings\n\t * @returns true if summary should be generated\n\t */\n\tshouldGenerateSummary(\n\t\tloomType: 'issue' | 'pr' | 'branch' | 'epic',\n\t\tsettings: IloomSettings\n\t): boolean {\n\t\t// Branch type never generates summaries (no issue to comment on)\n\t\tif (loomType === 'branch') {\n\t\t\treturn false\n\t\t}\n\n\t\t// Epic uses issue workflow config (epics are issue-based)\n\t\tconst effectiveType = loomType === 'epic' ? 'issue' : loomType\n\n\t\t// Get workflow-specific config\n\t\tconst workflowConfig =\n\t\t\teffectiveType === 'issue'\n\t\t\t\t? settings.workflows?.issue\n\t\t\t\t: settings.workflows?.pr\n\n\t\t// Default to true if not explicitly set (for issue and pr types)\n\t\treturn workflowConfig?.generateSummary ?? true\n\t}\n\n\t/**\n\t * Apply attribution footer to summary based on settings\n\t *\n\t * @param summary - The summary text\n\t * @param worktreePath - Path to worktree for loading settings and detecting remotes\n\t * @returns Summary with attribution footer if applicable\n\t */\n\tasync applyAttribution(summary: string, worktreePath: string): Promise<string> {\n\t\tconst settings = await this.settingsManager.loadSettings(worktreePath)\n\t\treturn this.applyAttributionWithSettings(summary, settings, worktreePath)\n\t}\n\n\t/**\n\t * Apply attribution footer to summary based on provided settings\n\t *\n\t * @param summary - The summary text\n\t * @param settings - The loaded iloom settings\n\t * @param worktreePath - Path to worktree for detecting remotes\n\t * @returns Summary with attribution footer if applicable\n\t */\n\tasync applyAttributionWithSettings(\n\t\tsummary: string,\n\t\tsettings: IloomSettings,\n\t\tworktreePath: string\n\t): Promise<string> {\n\t\tconst attributionSetting = settings.attribution ?? 'upstreamOnly'\n\t\tlogger.debug(`Attribution setting from config: ${settings.attribution}`)\n\t\tlogger.debug(`Attribution setting (with default): ${attributionSetting}`)\n\n\t\tlet shouldShowAttribution = false\n\t\tif (attributionSetting === 'on') {\n\t\t\tshouldShowAttribution = true\n\t\t\tlogger.debug('Attribution: always on')\n\t\t} else if (attributionSetting === 'upstreamOnly') {\n\t\t\t// Only show attribution when contributing to external repos (multiple remotes)\n\t\t\tshouldShowAttribution = await hasMultipleRemotes(worktreePath)\n\t\t\tlogger.debug(`Attribution: upstreamOnly, hasMultipleRemotes=${shouldShowAttribution}`)\n\t\t} else {\n\t\t\tlogger.debug('Attribution: off')\n\t\t}\n\t\t// 'off' keeps shouldShowAttribution = false\n\n\t\tlogger.debug(`Should show attribution: ${shouldShowAttribution}`)\n\t\tif (shouldShowAttribution) {\n\t\t\tlogger.debug('Attribution footer appended to summary')\n\t\t\treturn `${summary}\\n\\n---\\n*Generated with 🤖❤️ by [iloom.ai](https://iloom.ai)*`\n\t\t}\n\n\t\treturn summary\n\t}\n\n\t/**\n\t * Post the summary as a comment to the issue or PR\n\t *\n\t * @param issueNumber - The issue number (used when prNumber is not provided)\n\t * @param summary - The summary text to post\n\t * @param settings - The loaded iloom settings\n\t * @param worktreePath - Path to worktree for attribution detection\n\t * @param prNumber - Optional PR number - when provided, posts to the PR instead\n\t */\n\tprivate async postSummaryToIssue(\n\t\tissueNumber: string | number,\n\t\tsummary: string,\n\t\tsettings: IloomSettings,\n\t\tworktreePath: string,\n\t\tprNumber?: number\n\t): Promise<void> {\n\t\t// Apply attribution if configured\n\t\tconst finalSummary = await this.applyAttributionWithSettings(summary, settings, worktreePath)\n\n\t\t// When prNumber is provided, post to the PR instead of the issue\n\t\tconst targetNumber = prNumber ?? issueNumber\n\t\tconst targetType = prNumber !== undefined ? 'pr' : 'issue'\n\n\t\tif (prNumber !== undefined) {\n\t\t\t// Try VCS provider first for PR comments (e.g., BitBucket)\n\t\t\tconst vcsProvider = VCSProviderFactory.create(settings)\n\t\t\tif (vcsProvider) {\n\t\t\t\tawait vcsProvider.createPRComment(prNumber, finalSummary, worktreePath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Fall through to issue management provider (GitHub)\n\t\t}\n\n\t\t// Use issue management provider (GitHub, Linear, Jira)\n\t\tconst providerType = prNumber !== undefined\n\t\t\t? 'github'\n\t\t\t: (settings.issueManagement?.provider ?? 'github') as IssueProvider\n\t\tconst provider = IssueManagementProviderFactory.create(providerType, settings)\n\n\t\t// Create the comment\n\t\tawait provider.createComment({\n\t\t\tnumber: String(targetNumber),\n\t\t\tbody: finalSummary,\n\t\t\ttype: targetType,\n\t\t})\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PromptTemplateManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LDE6VNG5.js";
|
|
5
5
|
import {
|
|
6
6
|
detectClaudeCli,
|
|
7
7
|
launchClaude,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-DDHWZNGL.js";
|
|
10
10
|
import {
|
|
11
11
|
SettingsManager
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MNPKEWBQ.js";
|
|
13
13
|
import {
|
|
14
14
|
logger
|
|
15
15
|
} from "./chunk-VRPPI6GU.js";
|
|
@@ -121,4 +121,4 @@ var ClaudeService = class {
|
|
|
121
121
|
export {
|
|
122
122
|
ClaudeService
|
|
123
123
|
};
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
124
|
+
//# sourceMappingURL=chunk-UMAOVKQX.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
findMainWorktreePathWithSettings
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5W44AI63.js";
|
|
5
5
|
import {
|
|
6
6
|
MetadataManager
|
|
7
7
|
} from "./chunk-XIVLGWUX.js";
|
|
@@ -70,4 +70,4 @@ export {
|
|
|
70
70
|
archiveRecap,
|
|
71
71
|
findArchivedRecap
|
|
72
72
|
};
|
|
73
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
//# sourceMappingURL=chunk-UQWMPQ2Q.js.map
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
} from "./chunk-KV4NU3RP.js";
|
|
39
39
|
import {
|
|
40
40
|
SettingsManager
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-MNPKEWBQ.js";
|
|
42
42
|
import {
|
|
43
43
|
logger
|
|
44
44
|
} from "./chunk-VRPPI6GU.js";
|
|
@@ -1436,4 +1436,4 @@ var IssueManagementProviderFactory = class {
|
|
|
1436
1436
|
export {
|
|
1437
1437
|
IssueManagementProviderFactory
|
|
1438
1438
|
};
|
|
1439
|
-
//# sourceMappingURL=chunk-
|
|
1439
|
+
//# sourceMappingURL=chunk-VUIPDX3T.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
extractIssueNumber,
|
|
4
4
|
extractPRNumber
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5W44AI63.js";
|
|
6
6
|
|
|
7
7
|
// src/utils/IdentifierParser.ts
|
|
8
8
|
function matchIssueIdentifier(input) {
|
|
@@ -108,4 +108,4 @@ export {
|
|
|
108
108
|
matchIssueIdentifier,
|
|
109
109
|
IdentifierParser
|
|
110
110
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-XC5JKRSH.js.map
|