@iloom/cli 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +159 -40
- package/dist/{BranchNamingService-K6XNWQ6C.js → BranchNamingService-ECJHBB67.js} +2 -2
- package/dist/ClaudeContextManager-QXX6ZFST.js +14 -0
- package/dist/ClaudeService-NJNK2SUH.js +13 -0
- package/dist/{GitHubService-TGWJN4V4.js → GitHubService-MEHKHUQP.js} +4 -4
- package/dist/IssueTrackerFactory-NG53YX5S.js +14 -0
- package/dist/{LoomLauncher-73NXL2CL.js → LoomLauncher-L64HHS3T.js} +9 -9
- package/dist/{MetadataManager-W3C54UYT.js → MetadataManager-5QZSTKNN.js} +2 -2
- package/dist/{ProjectCapabilityDetector-N5L7T4IY.js → ProjectCapabilityDetector-5KSYUTBJ.js} +3 -3
- package/dist/{PromptTemplateManager-36YLQRHP.js → PromptTemplateManager-DULSVRRE.js} +2 -2
- package/dist/README.md +159 -40
- package/dist/{SettingsManager-AW3JTJHD.js → SettingsManager-BQDQA3FK.js} +4 -2
- package/dist/agents/iloom-artifact-reviewer.md +11 -0
- package/dist/agents/iloom-code-reviewer.md +14 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +55 -12
- package/dist/agents/iloom-issue-analyzer.md +49 -6
- package/dist/agents/iloom-issue-complexity-evaluator.md +47 -6
- package/dist/agents/iloom-issue-enhancer.md +86 -7
- package/dist/agents/iloom-issue-implementer.md +48 -7
- package/dist/agents/iloom-issue-planner.md +115 -62
- package/dist/{build-THZI572G.js → build-5GO3XW26.js} +9 -9
- package/dist/{chunk-NUACL52E.js → chunk-3D7WQM7I.js} +2 -2
- package/dist/chunk-4232AHNQ.js +35 -0
- package/dist/chunk-4232AHNQ.js.map +1 -0
- package/dist/{chunk-QN47QVBX.js → chunk-4WJNIR5O.js} +1 -1
- package/dist/chunk-4WJNIR5O.js.map +1 -0
- package/dist/{chunk-A7NJF73J.js → chunk-5MWV33NN.js} +4 -4
- package/dist/{chunk-3I4ONZRT.js → chunk-6EU6TCF6.js} +10 -10
- package/dist/chunk-6EU6TCF6.js.map +1 -0
- package/dist/{chunk-CWRI4JC3.js → chunk-FB47TIJG.js} +29 -11
- package/dist/chunk-FB47TIJG.js.map +1 -0
- package/dist/chunk-HEXKPKCK.js +1396 -0
- package/dist/chunk-HEXKPKCK.js.map +1 -0
- package/dist/{chunk-KAYXR544.js → chunk-J5S7DFYC.js} +2 -2
- package/dist/{chunk-ULSWCPQG.js → chunk-JO2LZ6EQ.js} +476 -5
- package/dist/chunk-JO2LZ6EQ.js.map +1 -0
- package/dist/{chunk-KBEIQP4G.js → chunk-KB64WNBZ.js} +43 -3
- package/dist/chunk-KB64WNBZ.js.map +1 -0
- package/dist/{chunk-OFDN5NKS.js → chunk-KXDRI47U.js} +69 -12
- package/dist/chunk-KXDRI47U.js.map +1 -0
- package/dist/{chunk-R4YWBGY6.js → chunk-LXLMMXXY.js} +54 -14
- package/dist/chunk-LXLMMXXY.js.map +1 -0
- package/dist/{chunk-AR5QKYNE.js → chunk-MNHZB4Z2.js} +4 -4
- package/dist/{chunk-TL72BGP6.js → chunk-MORRVYPT.js} +2 -2
- package/dist/{chunk-KJTVU3HZ.js → chunk-NRSWLOAZ.js} +8 -8
- package/dist/chunk-NRSWLOAZ.js.map +1 -0
- package/dist/{chunk-FO5GGFOV.js → chunk-ONQYPICO.js} +13 -5
- package/dist/chunk-ONQYPICO.js.map +1 -0
- package/dist/{chunk-7ZEHSSUP.js → chunk-P4O6EH46.js} +4 -4
- package/dist/chunk-QZWEJVWV.js +207 -0
- package/dist/chunk-QZWEJVWV.js.map +1 -0
- package/dist/chunk-RSYT7MVI.js +202 -0
- package/dist/chunk-RSYT7MVI.js.map +1 -0
- package/dist/{chunk-Z2TWEXR7.js → chunk-RYWFS37M.js} +6 -6
- package/dist/chunk-RYWFS37M.js.map +1 -0
- package/dist/{chunk-B7U6OKUR.js → chunk-SF2P22EE.js} +11 -3
- package/dist/chunk-SF2P22EE.js.map +1 -0
- package/dist/{chunk-6IIL5M2L.js → chunk-SN3SQCFK.js} +10 -8
- package/dist/{chunk-6IIL5M2L.js.map → chunk-SN3SQCFK.js.map} +1 -1
- package/dist/{chunk-SOSQILHO.js → chunk-UD3WJDIV.js} +92 -82
- package/dist/chunk-UD3WJDIV.js.map +1 -0
- package/dist/{chunk-KXGQYLFZ.js → chunk-UKBAJ2QQ.js} +61 -7
- package/dist/chunk-UKBAJ2QQ.js.map +1 -0
- package/dist/{chunk-W6DP5RVR.js → chunk-UVD4CZKS.js} +3 -3
- package/dist/chunk-UWGVCXRF.js +207 -0
- package/dist/chunk-UWGVCXRF.js.map +1 -0
- package/dist/{chunk-NWMORW3U.js → chunk-VECNX6VX.js} +2 -2
- package/dist/{chunk-4CO6KG5S.js → chunk-VG45TUYK.js} +53 -7
- package/dist/{chunk-4CO6KG5S.js.map → chunk-VG45TUYK.js.map} +1 -1
- package/dist/{chunk-TC7APDKU.js → chunk-VGGST52X.js} +2 -2
- package/dist/{chunk-4LKGCFGG.js → chunk-WWKOVDWC.js} +2 -2
- package/dist/{chunk-YKFCCV6S.js → chunk-WY4QBK43.js} +7 -7
- package/dist/chunk-WY4QBK43.js.map +1 -0
- package/dist/chunk-Y4YZTHZE.js +73 -0
- package/dist/chunk-Y4YZTHZE.js.map +1 -0
- package/dist/{chunk-VOGGLPG5.js → chunk-YQ57ORTV.js} +14 -1
- package/dist/chunk-YQ57ORTV.js.map +1 -0
- package/dist/{chunk-RI2YL6TK.js → chunk-YYAKPQBT.js} +65 -18
- package/dist/chunk-YYAKPQBT.js.map +1 -0
- package/dist/{chunk-IZIYLYPK.js → chunk-ZEWU5PZK.js} +2 -2
- package/dist/{chunk-VPTAX5TR.js → chunk-ZHPNZC75.js} +12 -12
- package/dist/chunk-ZHPNZC75.js.map +1 -0
- package/dist/{chunk-DGG2VY7B.js → chunk-ZW2LKWWE.js} +9 -9
- package/dist/chunk-ZW2LKWWE.js.map +1 -0
- package/dist/{claude-TP2QO3BU.js → claude-P3NQR6IJ.js} +2 -2
- package/dist/{cleanup-PJRIFFU4.js → cleanup-6UCPVMFG.js} +81 -32
- package/dist/cleanup-6UCPVMFG.js.map +1 -0
- package/dist/cli.js +638 -349
- package/dist/cli.js.map +1 -1
- package/dist/{commit-IVP3M4HG.js → commit-L3EPY5QG.js} +21 -20
- package/dist/commit-L3EPY5QG.js.map +1 -0
- package/dist/{compile-R2J65HBQ.js → compile-ZS4HYRX5.js} +9 -9
- package/dist/{contribute-VDZXHK5Y.js → contribute-ORDDQGSL.js} +14 -6
- package/dist/contribute-ORDDQGSL.js.map +1 -0
- package/dist/{dev-server-7F622OEO.js → dev-server-FYZ2AQIH.js} +29 -15
- package/dist/dev-server-FYZ2AQIH.js.map +1 -0
- package/dist/{feedback-E7VET7CL.js → feedback-TMBXSCM5.js} +15 -15
- package/dist/{git-2QDQ2X2S.js → git-ET64COO3.js} +4 -4
- package/dist/hooks/iloom-hook.js +15 -0
- package/dist/ignite-CGOV3TD4.js +1393 -0
- package/dist/ignite-CGOV3TD4.js.map +1 -0
- package/dist/index.d.ts +382 -53
- package/dist/index.js +1167 -36
- package/dist/index.js.map +1 -1
- package/dist/{init-676DHF6R.js → init-GFQ5W7GK.js} +57 -21
- package/dist/init-GFQ5W7GK.js.map +1 -0
- package/dist/{issues-PJSOLOBJ.js → issues-T4ZZSPEG.js} +61 -20
- package/dist/issues-T4ZZSPEG.js.map +1 -0
- package/dist/{lint-CJM7BAIM.js → lint-6TQXDZ3T.js} +9 -9
- package/dist/mcp/issue-management-server.js +2471 -256
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/mcp/recap-server.js +144 -21
- package/dist/mcp/recap-server.js.map +1 -1
- package/dist/{neon-helpers-VVFFTLXE.js → neon-helpers-CQN2PB4S.js} +3 -3
- package/dist/neon-helpers-CQN2PB4S.js.map +1 -0
- package/dist/{open-544H7JF5.js → open-5QZGXQRF.js} +15 -15
- package/dist/open-5QZGXQRF.js.map +1 -0
- package/dist/{plan-Q7ELXDLC.js → plan-U7ZQWLFY.js} +41 -25
- package/dist/plan-U7ZQWLFY.js.map +1 -0
- package/dist/{projects-LH362JZQ.js → projects-2UOXFLNZ.js} +4 -4
- package/dist/prompts/CLAUDE.md +62 -0
- package/dist/prompts/init-prompt.txt +347 -26
- package/dist/prompts/issue-prompt.txt +427 -54
- package/dist/prompts/plan-prompt.txt +97 -16
- package/dist/prompts/pr-prompt.txt +44 -1
- package/dist/prompts/regular-prompt.txt +42 -1
- package/dist/prompts/session-summary-prompt.txt +14 -0
- package/dist/prompts/swarm-orchestrator-prompt.txt +437 -0
- package/dist/{rebase-YND35CIE.js → rebase-DWIB77KV.js} +10 -10
- package/dist/{recap-3W7COH7D.js → recap-MX63HAKV.js} +47 -19
- package/dist/recap-MX63HAKV.js.map +1 -0
- package/dist/{run-QUXJKDQQ.js → run-O3TFNQFC.js} +15 -15
- package/dist/run-O3TFNQFC.js.map +1 -0
- package/dist/schema/package-iloom.schema.json +58 -0
- package/dist/schema/settings.schema.json +115 -15
- package/dist/{shell-QGECBLST.js → shell-G6VC2CYR.js} +14 -7
- package/dist/shell-G6VC2CYR.js.map +1 -0
- package/dist/{summary-G2T4452H.js → summary-FWHAX55O.js} +27 -25
- package/dist/summary-FWHAX55O.js.map +1 -0
- package/dist/{test-EA5NQFDC.js → test-F7JNJZYP.js} +9 -9
- package/dist/{test-git-M7LSLEFL.js → test-git-BTAOIUE2.js} +4 -4
- package/dist/test-jira-CHYNV33F.js +96 -0
- package/dist/test-jira-CHYNV33F.js.map +1 -0
- package/dist/{test-prefix-64NAAUON.js → test-prefix-Q6TFSU6F.js} +4 -4
- package/dist/{test-webserver-OK6Z5FJM.js → test-webserver-EONCG7E7.js} +6 -6
- package/dist/{vscode-AR5NNXXI.js → vscode-VA5X4P25.js} +7 -7
- package/package.json +5 -1
- package/dist/ClaudeContextManager-HR5JQKAI.js +0 -14
- package/dist/ClaudeService-TK7FMC2X.js +0 -13
- package/dist/chunk-3I4ONZRT.js.map +0 -1
- package/dist/chunk-B7U6OKUR.js.map +0 -1
- package/dist/chunk-CWRI4JC3.js.map +0 -1
- package/dist/chunk-DGG2VY7B.js.map +0 -1
- package/dist/chunk-FJDRTVJX.js +0 -520
- package/dist/chunk-FJDRTVJX.js.map +0 -1
- package/dist/chunk-FO5GGFOV.js.map +0 -1
- package/dist/chunk-KBEIQP4G.js.map +0 -1
- package/dist/chunk-KJTVU3HZ.js.map +0 -1
- package/dist/chunk-KXGQYLFZ.js.map +0 -1
- package/dist/chunk-OFDN5NKS.js.map +0 -1
- package/dist/chunk-QN47QVBX.js.map +0 -1
- package/dist/chunk-R4YWBGY6.js.map +0 -1
- package/dist/chunk-RI2YL6TK.js.map +0 -1
- package/dist/chunk-SOSQILHO.js.map +0 -1
- package/dist/chunk-ULSWCPQG.js.map +0 -1
- package/dist/chunk-VOGGLPG5.js.map +0 -1
- package/dist/chunk-VPTAX5TR.js.map +0 -1
- package/dist/chunk-WHI5KEOX.js +0 -121
- package/dist/chunk-WHI5KEOX.js.map +0 -1
- package/dist/chunk-YKFCCV6S.js.map +0 -1
- package/dist/chunk-Z2TWEXR7.js.map +0 -1
- package/dist/cleanup-PJRIFFU4.js.map +0 -1
- package/dist/commit-IVP3M4HG.js.map +0 -1
- package/dist/contribute-VDZXHK5Y.js.map +0 -1
- package/dist/dev-server-7F622OEO.js.map +0 -1
- package/dist/ignite-IW35CDBD.js +0 -784
- package/dist/ignite-IW35CDBD.js.map +0 -1
- package/dist/init-676DHF6R.js.map +0 -1
- package/dist/issues-PJSOLOBJ.js.map +0 -1
- package/dist/open-544H7JF5.js.map +0 -1
- package/dist/plan-Q7ELXDLC.js.map +0 -1
- package/dist/recap-3W7COH7D.js.map +0 -1
- package/dist/run-QUXJKDQQ.js.map +0 -1
- package/dist/shell-QGECBLST.js.map +0 -1
- package/dist/summary-G2T4452H.js.map +0 -1
- /package/dist/{BranchNamingService-K6XNWQ6C.js.map → BranchNamingService-ECJHBB67.js.map} +0 -0
- /package/dist/{ClaudeContextManager-HR5JQKAI.js.map → ClaudeContextManager-QXX6ZFST.js.map} +0 -0
- /package/dist/{ClaudeService-TK7FMC2X.js.map → ClaudeService-NJNK2SUH.js.map} +0 -0
- /package/dist/{GitHubService-TGWJN4V4.js.map → GitHubService-MEHKHUQP.js.map} +0 -0
- /package/dist/{MetadataManager-W3C54UYT.js.map → IssueTrackerFactory-NG53YX5S.js.map} +0 -0
- /package/dist/{LoomLauncher-73NXL2CL.js.map → LoomLauncher-L64HHS3T.js.map} +0 -0
- /package/dist/{ProjectCapabilityDetector-N5L7T4IY.js.map → MetadataManager-5QZSTKNN.js.map} +0 -0
- /package/dist/{PromptTemplateManager-36YLQRHP.js.map → ProjectCapabilityDetector-5KSYUTBJ.js.map} +0 -0
- /package/dist/{SettingsManager-AW3JTJHD.js.map → PromptTemplateManager-DULSVRRE.js.map} +0 -0
- /package/dist/{claude-TP2QO3BU.js.map → SettingsManager-BQDQA3FK.js.map} +0 -0
- /package/dist/{build-THZI572G.js.map → build-5GO3XW26.js.map} +0 -0
- /package/dist/{chunk-NUACL52E.js.map → chunk-3D7WQM7I.js.map} +0 -0
- /package/dist/{chunk-A7NJF73J.js.map → chunk-5MWV33NN.js.map} +0 -0
- /package/dist/{chunk-KAYXR544.js.map → chunk-J5S7DFYC.js.map} +0 -0
- /package/dist/{chunk-AR5QKYNE.js.map → chunk-MNHZB4Z2.js.map} +0 -0
- /package/dist/{chunk-TL72BGP6.js.map → chunk-MORRVYPT.js.map} +0 -0
- /package/dist/{chunk-7ZEHSSUP.js.map → chunk-P4O6EH46.js.map} +0 -0
- /package/dist/{chunk-W6DP5RVR.js.map → chunk-UVD4CZKS.js.map} +0 -0
- /package/dist/{chunk-NWMORW3U.js.map → chunk-VECNX6VX.js.map} +0 -0
- /package/dist/{chunk-TC7APDKU.js.map → chunk-VGGST52X.js.map} +0 -0
- /package/dist/{chunk-4LKGCFGG.js.map → chunk-WWKOVDWC.js.map} +0 -0
- /package/dist/{chunk-IZIYLYPK.js.map → chunk-ZEWU5PZK.js.map} +0 -0
- /package/dist/{git-2QDQ2X2S.js.map → claude-P3NQR6IJ.js.map} +0 -0
- /package/dist/{compile-R2J65HBQ.js.map → compile-ZS4HYRX5.js.map} +0 -0
- /package/dist/{feedback-E7VET7CL.js.map → feedback-TMBXSCM5.js.map} +0 -0
- /package/dist/{neon-helpers-VVFFTLXE.js.map → git-ET64COO3.js.map} +0 -0
- /package/dist/{lint-CJM7BAIM.js.map → lint-6TQXDZ3T.js.map} +0 -0
- /package/dist/{projects-LH362JZQ.js.map → projects-2UOXFLNZ.js.map} +0 -0
- /package/dist/{rebase-YND35CIE.js.map → rebase-DWIB77KV.js.map} +0 -0
- /package/dist/{test-EA5NQFDC.js.map → test-F7JNJZYP.js.map} +0 -0
- /package/dist/{test-git-M7LSLEFL.js.map → test-git-BTAOIUE2.js.map} +0 -0
- /package/dist/{test-prefix-64NAAUON.js.map → test-prefix-Q6TFSU6F.js.map} +0 -0
- /package/dist/{test-webserver-OK6Z5FJM.js.map → test-webserver-EONCG7E7.js.map} +0 -0
- /package/dist/{vscode-AR5NNXXI.js.map → vscode-VA5X4P25.js.map} +0 -0
|
@@ -0,0 +1,1393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildDependencyMap,
|
|
4
|
+
fetchChildIssueDetails
|
|
5
|
+
} from "./chunk-QZWEJVWV.js";
|
|
6
|
+
import {
|
|
7
|
+
IssueManagementProviderFactory
|
|
8
|
+
} from "./chunk-JO2LZ6EQ.js";
|
|
9
|
+
import "./chunk-4232AHNQ.js";
|
|
10
|
+
import {
|
|
11
|
+
getWorkspacePort
|
|
12
|
+
} from "./chunk-3D7WQM7I.js";
|
|
13
|
+
import {
|
|
14
|
+
installDependencies
|
|
15
|
+
} from "./chunk-WWKOVDWC.js";
|
|
16
|
+
import {
|
|
17
|
+
TelemetryService
|
|
18
|
+
} from "./chunk-RSYT7MVI.js";
|
|
19
|
+
import {
|
|
20
|
+
GitWorktreeManager
|
|
21
|
+
} from "./chunk-VGGST52X.js";
|
|
22
|
+
import {
|
|
23
|
+
FirstRunManager
|
|
24
|
+
} from "./chunk-Q7POFB5Q.js";
|
|
25
|
+
import {
|
|
26
|
+
generateAndWriteMcpConfigFile,
|
|
27
|
+
generateIssueManagementMcpConfig,
|
|
28
|
+
generateRecapMcpConfig
|
|
29
|
+
} from "./chunk-UWGVCXRF.js";
|
|
30
|
+
import "./chunk-YQ57ORTV.js";
|
|
31
|
+
import {
|
|
32
|
+
AgentManager
|
|
33
|
+
} from "./chunk-SF2P22EE.js";
|
|
34
|
+
import {
|
|
35
|
+
launchClaude
|
|
36
|
+
} from "./chunk-ONQYPICO.js";
|
|
37
|
+
import {
|
|
38
|
+
PromptTemplateManager,
|
|
39
|
+
buildReviewTemplateVariables
|
|
40
|
+
} from "./chunk-4WJNIR5O.js";
|
|
41
|
+
import {
|
|
42
|
+
extractSettingsOverrides
|
|
43
|
+
} from "./chunk-GYCR2LOU.js";
|
|
44
|
+
import {
|
|
45
|
+
extractIssueNumber,
|
|
46
|
+
findMainWorktreePathWithSettings,
|
|
47
|
+
generateWorktreePath,
|
|
48
|
+
getWorktreeRoot,
|
|
49
|
+
isValidGitRepo
|
|
50
|
+
} from "./chunk-MNHZB4Z2.js";
|
|
51
|
+
import {
|
|
52
|
+
SettingsManager
|
|
53
|
+
} from "./chunk-YYAKPQBT.js";
|
|
54
|
+
import {
|
|
55
|
+
MetadataManager
|
|
56
|
+
} from "./chunk-KB64WNBZ.js";
|
|
57
|
+
import {
|
|
58
|
+
IssueTrackerFactory
|
|
59
|
+
} from "./chunk-UKBAJ2QQ.js";
|
|
60
|
+
import "./chunk-HEXKPKCK.js";
|
|
61
|
+
import "./chunk-KXDRI47U.js";
|
|
62
|
+
import "./chunk-VG45TUYK.js";
|
|
63
|
+
import {
|
|
64
|
+
getLogger,
|
|
65
|
+
withLogger
|
|
66
|
+
} from "./chunk-6MLEBAYZ.js";
|
|
67
|
+
import "./chunk-7JDMYTFZ.js";
|
|
68
|
+
import {
|
|
69
|
+
createStderrLogger,
|
|
70
|
+
logger
|
|
71
|
+
} from "./chunk-VT4PDUYT.js";
|
|
72
|
+
|
|
73
|
+
// src/commands/ignite.ts
|
|
74
|
+
import path4 from "path";
|
|
75
|
+
import fs4 from "fs-extra";
|
|
76
|
+
import { readFile } from "fs/promises";
|
|
77
|
+
|
|
78
|
+
// src/lib/ClaudeHookManager.ts
|
|
79
|
+
import os from "os";
|
|
80
|
+
import path from "path";
|
|
81
|
+
import fs from "fs-extra";
|
|
82
|
+
import { parse, modify, applyEdits } from "jsonc-parser";
|
|
83
|
+
import { fileURLToPath } from "url";
|
|
84
|
+
import { accessSync } from "fs";
|
|
85
|
+
var ClaudeHookManager = class {
|
|
86
|
+
constructor() {
|
|
87
|
+
this.claudeDir = path.join(os.homedir(), ".claude");
|
|
88
|
+
this.hooksDir = path.join(this.claudeDir, "hooks");
|
|
89
|
+
this.settingsPath = path.join(this.claudeDir, "settings.json");
|
|
90
|
+
const currentFileUrl = import.meta.url;
|
|
91
|
+
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
92
|
+
const distDir = path.dirname(currentFilePath);
|
|
93
|
+
let templateDir = path.join(distDir, "hooks");
|
|
94
|
+
let currentDir = distDir;
|
|
95
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
96
|
+
const candidatePath = path.join(currentDir, "hooks");
|
|
97
|
+
try {
|
|
98
|
+
accessSync(candidatePath);
|
|
99
|
+
templateDir = candidatePath;
|
|
100
|
+
break;
|
|
101
|
+
} catch {
|
|
102
|
+
currentDir = path.dirname(currentDir);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.templateDir = templateDir;
|
|
106
|
+
logger.debug("ClaudeHookManager initialized", {
|
|
107
|
+
claudeDir: this.claudeDir,
|
|
108
|
+
hooksDir: this.hooksDir,
|
|
109
|
+
settingsPath: this.settingsPath,
|
|
110
|
+
templateDir: this.templateDir
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Install Claude hooks for VSCode integration
|
|
115
|
+
*
|
|
116
|
+
* This is idempotent - safe to call on every spin.
|
|
117
|
+
* Installs hook script to ~/.claude/hooks/ and merges
|
|
118
|
+
* hook configuration into ~/.claude/settings.json
|
|
119
|
+
*/
|
|
120
|
+
async installHooks() {
|
|
121
|
+
try {
|
|
122
|
+
await fs.ensureDir(this.hooksDir);
|
|
123
|
+
await this.installHookScript();
|
|
124
|
+
await this.mergeHookConfig();
|
|
125
|
+
logger.debug("Claude hooks installed successfully");
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logger.warn(
|
|
128
|
+
`Failed to install Claude hooks: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if hooks are already installed
|
|
134
|
+
*/
|
|
135
|
+
async isHooksInstalled() {
|
|
136
|
+
try {
|
|
137
|
+
const hookScriptPath = path.join(this.hooksDir, "iloom-hook.js");
|
|
138
|
+
if (!await fs.pathExists(hookScriptPath)) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (!await fs.pathExists(this.settingsPath)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const content = await fs.readFile(this.settingsPath, "utf8");
|
|
145
|
+
const errors = [];
|
|
146
|
+
const settings = parse(content, errors, { allowTrailingComma: true });
|
|
147
|
+
if (errors.length > 0 || !(settings == null ? void 0 : settings.hooks)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return Array.isArray(settings.hooks.SessionStart);
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Install the hook script from bundled templates
|
|
157
|
+
* Skips write if destination already has identical content
|
|
158
|
+
*/
|
|
159
|
+
async installHookScript() {
|
|
160
|
+
const sourcePath = path.join(this.templateDir, "iloom-hook.js");
|
|
161
|
+
const destPath = path.join(this.hooksDir, "iloom-hook.js");
|
|
162
|
+
if (!await fs.pathExists(sourcePath)) {
|
|
163
|
+
throw new Error(`Hook template not found at ${sourcePath}`);
|
|
164
|
+
}
|
|
165
|
+
if (await fs.pathExists(destPath)) {
|
|
166
|
+
const [sourceContent, destContent] = await Promise.all([
|
|
167
|
+
fs.readFile(sourcePath, "utf8"),
|
|
168
|
+
fs.readFile(destPath, "utf8")
|
|
169
|
+
]);
|
|
170
|
+
if (sourceContent === destContent) {
|
|
171
|
+
logger.debug("Hook script already up to date, skipping");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await fs.copyFile(sourcePath, destPath);
|
|
176
|
+
logger.debug("Hook script installed", { sourcePath, destPath });
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Merge hook configuration into settings.json
|
|
180
|
+
* Preserves existing user hooks and comments
|
|
181
|
+
*/
|
|
182
|
+
async mergeHookConfig() {
|
|
183
|
+
var _a, _b;
|
|
184
|
+
await fs.ensureDir(this.claudeDir);
|
|
185
|
+
let existingContent = "{}";
|
|
186
|
+
let existingSettings = {};
|
|
187
|
+
if (await fs.pathExists(this.settingsPath)) {
|
|
188
|
+
existingContent = await fs.readFile(this.settingsPath, "utf8");
|
|
189
|
+
const errors = [];
|
|
190
|
+
existingSettings = parse(existingContent, errors, { allowTrailingComma: true });
|
|
191
|
+
if (errors.length > 0) {
|
|
192
|
+
logger.warn("Existing settings.json has parse errors, will attempt to merge anyway");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const ourHooks = this.getHookConfig();
|
|
196
|
+
const mergedHooks = { ...existingSettings.hooks ?? {} };
|
|
197
|
+
let hooksAdded = false;
|
|
198
|
+
for (const [eventName, eventConfigs] of Object.entries(ourHooks)) {
|
|
199
|
+
const existing = mergedHooks[eventName] ?? [];
|
|
200
|
+
const ourConfig = eventConfigs[0];
|
|
201
|
+
const ourCommand = (_b = (_a = ourConfig == null ? void 0 : ourConfig.hooks) == null ? void 0 : _a[0]) == null ? void 0 : _b.command;
|
|
202
|
+
const existingConfigIndex = existing.findIndex(
|
|
203
|
+
(config) => {
|
|
204
|
+
var _a2;
|
|
205
|
+
return (_a2 = config.hooks) == null ? void 0 : _a2.some((h) => h.command === ourCommand);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
if (existingConfigIndex === -1) {
|
|
209
|
+
mergedHooks[eventName] = [...existing, ...eventConfigs];
|
|
210
|
+
hooksAdded = true;
|
|
211
|
+
} else {
|
|
212
|
+
const existingConfig = existing[existingConfigIndex];
|
|
213
|
+
const ourMatcher = ourConfig == null ? void 0 : ourConfig.matcher;
|
|
214
|
+
if (existingConfig && ourMatcher !== void 0 && existingConfig.matcher !== ourMatcher) {
|
|
215
|
+
existing[existingConfigIndex] = {
|
|
216
|
+
...existingConfig,
|
|
217
|
+
matcher: ourMatcher
|
|
218
|
+
};
|
|
219
|
+
hooksAdded = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!hooksAdded) {
|
|
224
|
+
logger.debug("All hooks already registered, skipping settings.json update");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
let content;
|
|
228
|
+
if (existingContent.includes("//") || existingContent.includes("/*")) {
|
|
229
|
+
let modifiedContent = existingContent;
|
|
230
|
+
const edits = modify(modifiedContent, ["hooks"], mergedHooks, {});
|
|
231
|
+
content = applyEdits(modifiedContent, edits);
|
|
232
|
+
} else {
|
|
233
|
+
const updatedSettings = {
|
|
234
|
+
...existingSettings,
|
|
235
|
+
hooks: mergedHooks
|
|
236
|
+
};
|
|
237
|
+
content = JSON.stringify(updatedSettings, null, 2) + "\n";
|
|
238
|
+
}
|
|
239
|
+
const tempPath = `${this.settingsPath}.tmp`;
|
|
240
|
+
await fs.writeFile(tempPath, content, "utf8");
|
|
241
|
+
await fs.rename(tempPath, this.settingsPath);
|
|
242
|
+
logger.debug("Hook configuration merged into settings.json");
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get the hook configuration to register
|
|
246
|
+
*
|
|
247
|
+
* Each event maps to a hook that runs iloom-hook.js
|
|
248
|
+
*/
|
|
249
|
+
getHookConfig() {
|
|
250
|
+
const hookCommand = `node ${path.join(this.hooksDir, "iloom-hook.js")}`;
|
|
251
|
+
return {
|
|
252
|
+
Notification: [
|
|
253
|
+
{ hooks: [{ type: "command", command: hookCommand }] }
|
|
254
|
+
],
|
|
255
|
+
Stop: [
|
|
256
|
+
{ hooks: [{ type: "command", command: hookCommand }] }
|
|
257
|
+
],
|
|
258
|
+
SubagentStop: [
|
|
259
|
+
{ hooks: [{ type: "command", command: hookCommand }] }
|
|
260
|
+
],
|
|
261
|
+
PermissionRequest: [
|
|
262
|
+
{ matcher: "*", hooks: [{ type: "command", command: hookCommand, timeout: 86400 }] }
|
|
263
|
+
],
|
|
264
|
+
PreToolUse: [
|
|
265
|
+
{ matcher: "*", hooks: [{ type: "command", command: hookCommand }] }
|
|
266
|
+
],
|
|
267
|
+
PostToolUse: [
|
|
268
|
+
{ matcher: "*", hooks: [{ type: "command", command: hookCommand }] }
|
|
269
|
+
],
|
|
270
|
+
SessionStart: [
|
|
271
|
+
{ matcher: "*", hooks: [{ type: "command", command: hookCommand }] }
|
|
272
|
+
],
|
|
273
|
+
SessionEnd: [
|
|
274
|
+
{ hooks: [{ type: "command", command: hookCommand }] }
|
|
275
|
+
],
|
|
276
|
+
UserPromptSubmit: [
|
|
277
|
+
{ hooks: [{ type: "command", command: hookCommand }] }
|
|
278
|
+
]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/lib/SwarmSetupService.ts
|
|
284
|
+
import path2 from "path";
|
|
285
|
+
import fs2 from "fs-extra";
|
|
286
|
+
var SwarmSetupService = class {
|
|
287
|
+
constructor(gitWorktree, metadataManager, agentManager, settingsManager, templateManager) {
|
|
288
|
+
this.gitWorktree = gitWorktree;
|
|
289
|
+
this.metadataManager = metadataManager;
|
|
290
|
+
this.agentManager = agentManager;
|
|
291
|
+
this.settingsManager = settingsManager;
|
|
292
|
+
this.templateManager = templateManager;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Create child worktrees for each child issue, branched off the epic branch.
|
|
296
|
+
* Writes iloom-metadata.json for each child with state: 'pending' and parentLoom.
|
|
297
|
+
* Generates and writes per-loom MCP config file for each child.
|
|
298
|
+
*
|
|
299
|
+
* Uses standard iloom naming conventions via generateWorktreePath().
|
|
300
|
+
*
|
|
301
|
+
* @param childIssues - Array of child issues from epic metadata
|
|
302
|
+
* @param epicBranch - The epic branch name (base branch for children)
|
|
303
|
+
* @param epicWorktreePath - Path to the epic worktree
|
|
304
|
+
* @param mainWorktreePath - Path to the main worktree (project root)
|
|
305
|
+
* @param epicIssueNumber - The parent epic issue number
|
|
306
|
+
* @param issueTrackerName - The issue tracker provider name (e.g., 'github')
|
|
307
|
+
* @param settings - Optional settings for MCP config generation
|
|
308
|
+
* @returns Array of results for each child worktree creation
|
|
309
|
+
*/
|
|
310
|
+
async createChildWorktrees(childIssues, epicBranch, epicWorktreePath, mainWorktreePath, epicIssueNumber, issueTrackerName, settings) {
|
|
311
|
+
const results = [];
|
|
312
|
+
for (const child of childIssues) {
|
|
313
|
+
try {
|
|
314
|
+
const rawId = child.number.replace(/^#/, "");
|
|
315
|
+
const safeId = rawId.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
316
|
+
const childBranch = `issue/${safeId}`;
|
|
317
|
+
const childWorktreePath = generateWorktreePath(
|
|
318
|
+
childBranch,
|
|
319
|
+
mainWorktreePath
|
|
320
|
+
);
|
|
321
|
+
getLogger().info(`Creating child worktree for ${child.number}: ${childWorktreePath}...`);
|
|
322
|
+
await this.gitWorktree.createWorktree({
|
|
323
|
+
path: childWorktreePath,
|
|
324
|
+
branch: childBranch,
|
|
325
|
+
createBranch: true,
|
|
326
|
+
baseBranch: epicBranch
|
|
327
|
+
});
|
|
328
|
+
const metadataInput = {
|
|
329
|
+
description: child.title,
|
|
330
|
+
branchName: childBranch,
|
|
331
|
+
worktreePath: childWorktreePath,
|
|
332
|
+
issueType: "issue",
|
|
333
|
+
issue_numbers: [rawId],
|
|
334
|
+
pr_numbers: [],
|
|
335
|
+
issueTracker: issueTrackerName,
|
|
336
|
+
colorHex: "#808080",
|
|
337
|
+
sessionId: "",
|
|
338
|
+
// No session - not launching Claude directly
|
|
339
|
+
projectPath: mainWorktreePath,
|
|
340
|
+
issueUrls: { [rawId]: child.url },
|
|
341
|
+
prUrls: {},
|
|
342
|
+
capabilities: [],
|
|
343
|
+
state: "pending",
|
|
344
|
+
parentLoom: {
|
|
345
|
+
type: "epic",
|
|
346
|
+
identifier: epicIssueNumber,
|
|
347
|
+
branchName: epicBranch,
|
|
348
|
+
worktreePath: epicWorktreePath
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
try {
|
|
352
|
+
await this.metadataManager.writeMetadata(childWorktreePath, metadataInput);
|
|
353
|
+
} catch (metaError) {
|
|
354
|
+
getLogger().warn(`Metadata write failed for ${child.number}, cleaning up worktree...`);
|
|
355
|
+
try {
|
|
356
|
+
await this.gitWorktree.removeWorktree(childWorktreePath, { force: true });
|
|
357
|
+
} catch {
|
|
358
|
+
getLogger().debug(`Could not clean up worktree at ${childWorktreePath}`);
|
|
359
|
+
}
|
|
360
|
+
throw metaError;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const childMetadata = await this.metadataManager.readMetadata(childWorktreePath);
|
|
364
|
+
if (childMetadata) {
|
|
365
|
+
const providerName = IssueTrackerFactory.getProviderName(
|
|
366
|
+
settings ?? await this.settingsManager.loadSettings()
|
|
367
|
+
);
|
|
368
|
+
const mcpConfigPath = await generateAndWriteMcpConfigFile(
|
|
369
|
+
childWorktreePath,
|
|
370
|
+
childMetadata,
|
|
371
|
+
providerName,
|
|
372
|
+
settings
|
|
373
|
+
);
|
|
374
|
+
await this.metadataManager.updateMetadata(childWorktreePath, { mcpConfigPath });
|
|
375
|
+
const claudeDir = path2.join(childWorktreePath, ".claude");
|
|
376
|
+
await fs2.ensureDir(claudeDir);
|
|
377
|
+
await fs2.writeFile(
|
|
378
|
+
path2.join(claudeDir, "iloom-swarm-mcp-config-path"),
|
|
379
|
+
mcpConfigPath,
|
|
380
|
+
"utf-8"
|
|
381
|
+
);
|
|
382
|
+
getLogger().debug(`Wrote MCP config for ${child.number}: ${mcpConfigPath}`);
|
|
383
|
+
}
|
|
384
|
+
} catch (error) {
|
|
385
|
+
getLogger().warn(
|
|
386
|
+
`Failed to write MCP config for child ${child.number}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
await installDependencies(childWorktreePath, true, true);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
getLogger().warn(
|
|
393
|
+
`Failed to install dependencies in child worktree ${child.number}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
results.push({
|
|
397
|
+
issueId: rawId,
|
|
398
|
+
worktreePath: childWorktreePath,
|
|
399
|
+
branch: childBranch,
|
|
400
|
+
success: true
|
|
401
|
+
});
|
|
402
|
+
getLogger().success(`Created child worktree for ${child.number}`);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
const rawId = child.number.replace(/^#/, "");
|
|
405
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
406
|
+
getLogger().warn(`Failed to create child worktree for ${child.number}: ${errorMessage}`);
|
|
407
|
+
results.push({
|
|
408
|
+
issueId: rawId,
|
|
409
|
+
worktreePath: "",
|
|
410
|
+
branch: "",
|
|
411
|
+
success: false,
|
|
412
|
+
error: errorMessage
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return results;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Render swarm-mode agent templates to the epic worktree's .claude/agents/ directory.
|
|
420
|
+
*
|
|
421
|
+
* Phase agent files are written WITHOUT frontmatter (prompt body only) because they are
|
|
422
|
+
* loaded via `--append-system-prompt-file` which does not parse YAML frontmatter.
|
|
423
|
+
* Model and tools metadata is extracted from the agent config and returned separately
|
|
424
|
+
* for use as CLI flags in `claude -p` commands.
|
|
425
|
+
*/
|
|
426
|
+
async renderSwarmAgents(epicWorktreePath) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const claudeAgentsDir = path2.join(epicWorktreePath, ".claude", "agents");
|
|
429
|
+
await fs2.ensureDir(claudeAgentsDir);
|
|
430
|
+
const settings = await this.settingsManager.loadSettings();
|
|
431
|
+
const templateVariables = {
|
|
432
|
+
SWARM_MODE: true
|
|
433
|
+
};
|
|
434
|
+
const agents = await this.agentManager.loadAgents(settings, templateVariables);
|
|
435
|
+
const swarmWorkerSettings = (_a = settings == null ? void 0 : settings.agents) == null ? void 0 : _a["iloom-swarm-worker"];
|
|
436
|
+
const swarmAgentOverrides = swarmWorkerSettings == null ? void 0 : swarmWorkerSettings.agents;
|
|
437
|
+
const swarmWorkerModel = swarmWorkerSettings == null ? void 0 : swarmWorkerSettings.model;
|
|
438
|
+
const swarmAgentDefaults = {
|
|
439
|
+
"iloom-issue-implementer": "sonnet"
|
|
440
|
+
};
|
|
441
|
+
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
442
|
+
const swarmOverrideModel = (_b = swarmAgentOverrides == null ? void 0 : swarmAgentOverrides[agentName]) == null ? void 0 : _b.model;
|
|
443
|
+
if (swarmOverrideModel) {
|
|
444
|
+
agents[agentName] = { ...agentConfig, model: swarmOverrideModel };
|
|
445
|
+
} else if (swarmWorkerModel) {
|
|
446
|
+
agents[agentName] = { ...agentConfig, model: swarmWorkerModel };
|
|
447
|
+
} else if (swarmAgentDefaults[agentName]) {
|
|
448
|
+
agents[agentName] = { ...agentConfig, model: swarmAgentDefaults[agentName] };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const renderedFiles = [];
|
|
452
|
+
const metadata = {};
|
|
453
|
+
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
454
|
+
const swarmFileName = agentName.startsWith("iloom-") ? `iloom-swarm-${agentName.slice("iloom-".length)}.md` : `iloom-swarm-${agentName}.md`;
|
|
455
|
+
const agentKey = swarmFileName.replace(".md", "");
|
|
456
|
+
metadata[agentKey] = {
|
|
457
|
+
model: agentConfig.model,
|
|
458
|
+
...agentConfig.tools && { tools: agentConfig.tools }
|
|
459
|
+
};
|
|
460
|
+
const outputPath = path2.join(claudeAgentsDir, swarmFileName);
|
|
461
|
+
await fs2.writeFile(outputPath, agentConfig.prompt + "\n", "utf-8");
|
|
462
|
+
renderedFiles.push(swarmFileName);
|
|
463
|
+
getLogger().debug(`Rendered swarm agent: ${swarmFileName}`);
|
|
464
|
+
}
|
|
465
|
+
getLogger().success(`Rendered ${renderedFiles.length} swarm agents to ${claudeAgentsDir}`);
|
|
466
|
+
return { renderedFiles, metadata };
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Render the swarm worker agent file to the epic worktree's .claude/agents/ directory.
|
|
470
|
+
*
|
|
471
|
+
* This creates an agent file at `.claude/agents/iloom-swarm-worker.md` containing
|
|
472
|
+
* the full iloom workflow instructions (rendered from issue-prompt.txt with SWARM_MODE=true).
|
|
473
|
+
* The orchestrator spawns children with `subagent_type: "iloom-swarm-worker"` so these
|
|
474
|
+
* instructions become the agent's system prompt (high authority), rather than arriving
|
|
475
|
+
* as a skill invocation (low authority user message).
|
|
476
|
+
*
|
|
477
|
+
* The agent file is shared across all children. Issue-specific context (number, title,
|
|
478
|
+
* worktree path, body) is provided per-child via the Task prompt from the orchestrator.
|
|
479
|
+
*/
|
|
480
|
+
async renderSwarmWorkerAgent(epicWorktreePath, agentMetadata) {
|
|
481
|
+
var _a, _b, _c;
|
|
482
|
+
const agentsDir = path2.join(epicWorktreePath, ".claude", "agents");
|
|
483
|
+
const agentOutputPath = path2.join(agentsDir, "iloom-swarm-worker.md");
|
|
484
|
+
await fs2.ensureDir(agentsDir);
|
|
485
|
+
try {
|
|
486
|
+
const settings = await this.settingsManager.loadSettings();
|
|
487
|
+
const providerType = ((_a = settings == null ? void 0 : settings.issueManagement) == null ? void 0 : _a.provider) ?? "github";
|
|
488
|
+
const issuePrefix = IssueManagementProviderFactory.create(providerType, settings ?? void 0).issuePrefix;
|
|
489
|
+
const variables = {
|
|
490
|
+
SWARM_MODE: true,
|
|
491
|
+
ONE_SHOT_MODE: true,
|
|
492
|
+
EPIC_WORKTREE_PATH: epicWorktreePath,
|
|
493
|
+
ISSUE_PREFIX: issuePrefix,
|
|
494
|
+
...agentMetadata && { SWARM_AGENT_METADATA: JSON.stringify(agentMetadata) },
|
|
495
|
+
...buildReviewTemplateVariables(settings == null ? void 0 : settings.agents)
|
|
496
|
+
};
|
|
497
|
+
const agentBody = await this.templateManager.getPrompt("issue", variables);
|
|
498
|
+
const workerModel = ((_c = (_b = settings == null ? void 0 : settings.agents) == null ? void 0 : _b["iloom-swarm-worker"]) == null ? void 0 : _c.model) ?? "sonnet";
|
|
499
|
+
const frontmatter = [
|
|
500
|
+
"---",
|
|
501
|
+
"name: iloom-swarm-worker",
|
|
502
|
+
"description: Swarm worker agent that implements a child issue following the full iloom workflow.",
|
|
503
|
+
`model: ${workerModel}`,
|
|
504
|
+
"---"
|
|
505
|
+
].join("\n");
|
|
506
|
+
const content = `${frontmatter}
|
|
507
|
+
|
|
508
|
+
${agentBody}
|
|
509
|
+
`;
|
|
510
|
+
await fs2.writeFile(agentOutputPath, content, "utf-8");
|
|
511
|
+
getLogger().success(`Rendered swarm worker agent to ${agentOutputPath}`);
|
|
512
|
+
return true;
|
|
513
|
+
} catch (error) {
|
|
514
|
+
getLogger().warn(
|
|
515
|
+
`Failed to render swarm worker agent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
516
|
+
);
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Run the full swarm setup: child worktrees, agents, and worker agent.
|
|
522
|
+
*
|
|
523
|
+
* The epic worktree already exists (created by `il start`).
|
|
524
|
+
*/
|
|
525
|
+
async setupSwarm(epicIssueNumber, epicBranch, epicWorktreePath, childIssues, mainWorktreePath, issueTrackerName, settings) {
|
|
526
|
+
const childWorktrees = await this.createChildWorktrees(
|
|
527
|
+
childIssues,
|
|
528
|
+
epicBranch,
|
|
529
|
+
epicWorktreePath,
|
|
530
|
+
mainWorktreePath,
|
|
531
|
+
epicIssueNumber,
|
|
532
|
+
issueTrackerName,
|
|
533
|
+
settings
|
|
534
|
+
);
|
|
535
|
+
const { renderedFiles: agentsRendered, metadata: agentMetadata } = await this.renderSwarmAgents(epicWorktreePath);
|
|
536
|
+
const workerAgentRendered = await this.renderSwarmWorkerAgent(
|
|
537
|
+
epicWorktreePath,
|
|
538
|
+
agentMetadata
|
|
539
|
+
);
|
|
540
|
+
const successCount = childWorktrees.filter((c) => c.success).length;
|
|
541
|
+
const failCount = childWorktrees.filter((c) => !c.success).length;
|
|
542
|
+
getLogger().success(
|
|
543
|
+
`Swarm setup complete: ${successCount} child worktrees` + (failCount > 0 ? ` (${failCount} failed)` : "")
|
|
544
|
+
);
|
|
545
|
+
return {
|
|
546
|
+
epicWorktreePath,
|
|
547
|
+
epicBranch,
|
|
548
|
+
childWorktrees,
|
|
549
|
+
agentsRendered,
|
|
550
|
+
workerAgentRendered
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/utils/language-detector.ts
|
|
556
|
+
import fs3 from "fs-extra";
|
|
557
|
+
import path3 from "path";
|
|
558
|
+
import fg from "fast-glob";
|
|
559
|
+
async function detectProjectLanguage(projectPath) {
|
|
560
|
+
try {
|
|
561
|
+
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
562
|
+
if (await fs3.pathExists(packageJsonPath)) {
|
|
563
|
+
try {
|
|
564
|
+
const pkg = await fs3.readJson(packageJsonPath);
|
|
565
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
566
|
+
return "typescript" in allDeps ? "typescript" : "javascript";
|
|
567
|
+
} catch {
|
|
568
|
+
return "javascript";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (await fs3.pathExists(path3.join(projectPath, "Cargo.toml"))) {
|
|
572
|
+
return "rust";
|
|
573
|
+
}
|
|
574
|
+
if (await fs3.pathExists(path3.join(projectPath, "go.mod"))) {
|
|
575
|
+
return "go";
|
|
576
|
+
}
|
|
577
|
+
for (const file of ["pyproject.toml", "setup.py", "requirements.txt"]) {
|
|
578
|
+
if (await fs3.pathExists(path3.join(projectPath, file))) {
|
|
579
|
+
return "python";
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (await fs3.pathExists(path3.join(projectPath, "Gemfile"))) {
|
|
583
|
+
return "ruby";
|
|
584
|
+
}
|
|
585
|
+
for (const file of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
|
|
586
|
+
if (await fs3.pathExists(path3.join(projectPath, file))) {
|
|
587
|
+
return "java";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const csprojFiles = await fg("*.csproj", { cwd: projectPath, dot: false });
|
|
591
|
+
if (csprojFiles.length > 0) {
|
|
592
|
+
return "csharp";
|
|
593
|
+
}
|
|
594
|
+
return "unknown";
|
|
595
|
+
} catch {
|
|
596
|
+
return "unknown";
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/commands/ignite.ts
|
|
601
|
+
var WorktreeValidationError = class extends Error {
|
|
602
|
+
constructor(message, suggestion) {
|
|
603
|
+
super(message);
|
|
604
|
+
this.suggestion = suggestion;
|
|
605
|
+
this.name = "WorktreeValidationError";
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
var IgniteCommand = class {
|
|
609
|
+
constructor(templateManager, gitWorktreeManager, agentManager, settingsManager, firstRunManager, hookManager) {
|
|
610
|
+
this.templateManager = templateManager ?? new PromptTemplateManager();
|
|
611
|
+
this.gitWorktreeManager = gitWorktreeManager ?? new GitWorktreeManager();
|
|
612
|
+
this.agentManager = agentManager ?? new AgentManager();
|
|
613
|
+
this.settingsManager = settingsManager ?? new SettingsManager();
|
|
614
|
+
this.firstRunManager = firstRunManager ?? new FirstRunManager("spin");
|
|
615
|
+
this.hookManager = hookManager ?? new ClaudeHookManager();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Validate that we're not running from the main worktree
|
|
619
|
+
* @throws WorktreeValidationError if running from main worktree
|
|
620
|
+
*/
|
|
621
|
+
async validateNotMainWorktree() {
|
|
622
|
+
const currentDir = process.cwd();
|
|
623
|
+
const isGitRepo = await isValidGitRepo(currentDir);
|
|
624
|
+
if (!isGitRepo) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const worktreeRoot = await getWorktreeRoot(currentDir);
|
|
628
|
+
if (!worktreeRoot) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const worktrees = await this.gitWorktreeManager.listWorktrees();
|
|
632
|
+
const currentWorktree = worktrees.find((wt) => wt.path === worktreeRoot);
|
|
633
|
+
if (!currentWorktree) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const isMain = await this.gitWorktreeManager.isMainWorktree(currentWorktree, this.settingsManager);
|
|
637
|
+
if (isMain) {
|
|
638
|
+
throw new WorktreeValidationError(
|
|
639
|
+
"You cannot run the command from the main worktree.",
|
|
640
|
+
"Navigate to a feature worktree created by 'il start <issue>' and run 'il spin' from there."
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Main entry point for spin command
|
|
646
|
+
* @param oneShot - One-shot automation mode
|
|
647
|
+
* @param printOptions - Print mode options for headless/CI execution
|
|
648
|
+
*/
|
|
649
|
+
async execute(oneShot, printOptions, skipCleanup) {
|
|
650
|
+
var _a, _b;
|
|
651
|
+
this.printOptions = printOptions;
|
|
652
|
+
const isJsonMode = (((_a = this.printOptions) == null ? void 0 : _a.json) ?? false) || (((_b = this.printOptions) == null ? void 0 : _b.jsonStream) ?? false);
|
|
653
|
+
if (isJsonMode) {
|
|
654
|
+
const jsonLogger = createStderrLogger();
|
|
655
|
+
return withLogger(jsonLogger, () => this.executeInternal(oneShot, skipCleanup));
|
|
656
|
+
}
|
|
657
|
+
return this.executeInternal(oneShot, skipCleanup);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Internal execution method (separated for withLogger wrapping)
|
|
661
|
+
*/
|
|
662
|
+
async executeInternal(oneShot, skipCleanup) {
|
|
663
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
|
|
664
|
+
process.env.ILOOM = "1";
|
|
665
|
+
try {
|
|
666
|
+
await this.validateNotMainWorktree();
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (error instanceof WorktreeValidationError) {
|
|
669
|
+
logger.error(error.message);
|
|
670
|
+
logger.info(error.suggestion);
|
|
671
|
+
throw error;
|
|
672
|
+
}
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
logger.info("\u{1F680} Your loom is spinning up, please wait...");
|
|
677
|
+
const isFirstRun = await this.firstRunManager.isFirstRun();
|
|
678
|
+
if (isFirstRun) {
|
|
679
|
+
logger.success("Welcome to iloom! Preparing first-time experience...");
|
|
680
|
+
}
|
|
681
|
+
await this.hookManager.installHooks();
|
|
682
|
+
const context = await this.detectWorkspaceContext();
|
|
683
|
+
logger.debug("Auto-detected workspace context", { context });
|
|
684
|
+
this.logDetectedContext(context);
|
|
685
|
+
logger.info("\u{1F4DD} Loading prompt template and preparing Claude...");
|
|
686
|
+
const metadataManager = new MetadataManager();
|
|
687
|
+
const metadata = await metadataManager.readMetadata(context.workspacePath);
|
|
688
|
+
const draftPrNumber = (metadata == null ? void 0 : metadata.draftPrNumber) ?? void 0;
|
|
689
|
+
const draftPrUrl = draftPrNumber && ((_a = metadata == null ? void 0 : metadata.prUrls) == null ? void 0 : _a[String(draftPrNumber)]) ? metadata.prUrls[String(draftPrNumber)] : void 0;
|
|
690
|
+
const storedOneShot = (metadata == null ? void 0 : metadata.oneShot) ?? "default";
|
|
691
|
+
const isHeadlessForOneShot = ((_b = this.printOptions) == null ? void 0 : _b.print) ?? false;
|
|
692
|
+
const effectiveOneShot = isHeadlessForOneShot ? "noReview" : oneShot ?? storedOneShot;
|
|
693
|
+
if (!this.settings) {
|
|
694
|
+
const cliOverrides = extractSettingsOverrides();
|
|
695
|
+
this.settings = await this.settingsManager.loadSettings(void 0, cliOverrides);
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const hasNeon = !!((_d = (_c = this.settings) == null ? void 0 : _c.databaseProviders) == null ? void 0 : _d.neon);
|
|
699
|
+
const language = await detectProjectLanguage(context.workspacePath);
|
|
700
|
+
TelemetryService.getInstance().track("session.started", {
|
|
701
|
+
has_neon: hasNeon,
|
|
702
|
+
language
|
|
703
|
+
});
|
|
704
|
+
} catch (error) {
|
|
705
|
+
logger.debug(`Telemetry session.started tracking failed: ${error instanceof Error ? error.message : error}`);
|
|
706
|
+
}
|
|
707
|
+
if (((_e = metadata == null ? void 0 : metadata.capabilities) == null ? void 0 : _e.includes("web")) && context.branchName) {
|
|
708
|
+
const basePort = ((_h = (_g = (_f = this.settings) == null ? void 0 : _f.capabilities) == null ? void 0 : _g.web) == null ? void 0 : _h.basePort) ?? 3e3;
|
|
709
|
+
context.port = await getWorkspacePort({
|
|
710
|
+
basePort,
|
|
711
|
+
worktreePath: context.workspacePath,
|
|
712
|
+
worktreeBranch: context.branchName
|
|
713
|
+
});
|
|
714
|
+
logger.info(`\u{1F310} Development server port: ${context.port}`);
|
|
715
|
+
}
|
|
716
|
+
const isEpicLoom = metadata && metadata.issue_numbers.length > 0 && ((((_i = metadata.childIssues) == null ? void 0 : _i.length) ?? 0) > 0 || metadata.issueType === "epic");
|
|
717
|
+
if (isEpicLoom && this.settings) {
|
|
718
|
+
await this.fetchAndStoreEpicChildData(metadataManager, metadata, context.workspacePath, this.settings);
|
|
719
|
+
}
|
|
720
|
+
if (isEpicLoom && this.settings) {
|
|
721
|
+
const freshMetadata = await metadataManager.readMetadata(context.workspacePath);
|
|
722
|
+
if (freshMetadata && freshMetadata.childIssues.length > 0) {
|
|
723
|
+
await this.executeSwarmMode(
|
|
724
|
+
freshMetadata,
|
|
725
|
+
context.workspacePath,
|
|
726
|
+
context.branchName ?? "",
|
|
727
|
+
metadataManager,
|
|
728
|
+
skipCleanup
|
|
729
|
+
);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const variables = this.buildTemplateVariables(context, effectiveOneShot, draftPrNumber, draftPrUrl);
|
|
734
|
+
if (isFirstRun) {
|
|
735
|
+
variables.FIRST_TIME_USER = true;
|
|
736
|
+
variables.README_CONTENT = await this.loadReadmeContent();
|
|
737
|
+
variables.SETTINGS_SCHEMA_CONTENT = await this.loadSettingsSchemaContent();
|
|
738
|
+
}
|
|
739
|
+
const systemInstructions = await this.templateManager.getPrompt(context.type, variables);
|
|
740
|
+
const userPrompt = this.buildUserPrompt(effectiveOneShot);
|
|
741
|
+
const model = this.settingsManager.getSpinModel(this.settings);
|
|
742
|
+
let permissionMode = this.getPermissionModeForWorkflow(context.type);
|
|
743
|
+
if (effectiveOneShot === "bypassPermissions") {
|
|
744
|
+
permissionMode = "bypassPermissions";
|
|
745
|
+
}
|
|
746
|
+
if (permissionMode === "bypassPermissions") {
|
|
747
|
+
logger.warn(
|
|
748
|
+
"\u26A0\uFE0F WARNING: Using bypassPermissions mode - Claude will execute all tool calls without confirmation. This can be dangerous. Use with caution."
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
const sessionId = metadata == null ? void 0 : metadata.sessionId;
|
|
752
|
+
if (!sessionId) {
|
|
753
|
+
throw new Error("No session ID found in loom metadata. This loom may need to be recreated with `il start`.");
|
|
754
|
+
}
|
|
755
|
+
logger.debug("Using session ID from metadata", { sessionId });
|
|
756
|
+
const isHeadless = ((_j = this.printOptions) == null ? void 0 : _j.print) ?? false;
|
|
757
|
+
const claudeOptions = {
|
|
758
|
+
headless: isHeadless,
|
|
759
|
+
addDir: context.workspacePath,
|
|
760
|
+
sessionId
|
|
761
|
+
// Enable Claude Code session resume
|
|
762
|
+
};
|
|
763
|
+
if (model !== void 0) {
|
|
764
|
+
claudeOptions.model = model;
|
|
765
|
+
}
|
|
766
|
+
if (isHeadless) {
|
|
767
|
+
permissionMode = "bypassPermissions";
|
|
768
|
+
}
|
|
769
|
+
if (permissionMode !== void 0 && permissionMode !== "default") {
|
|
770
|
+
claudeOptions.permissionMode = permissionMode;
|
|
771
|
+
}
|
|
772
|
+
if (((_k = this.printOptions) == null ? void 0 : _k.outputFormat) !== void 0) {
|
|
773
|
+
claudeOptions.outputFormat = this.printOptions.outputFormat;
|
|
774
|
+
}
|
|
775
|
+
if (((_l = this.printOptions) == null ? void 0 : _l.verbose) !== void 0) {
|
|
776
|
+
claudeOptions.verbose = this.printOptions.verbose;
|
|
777
|
+
}
|
|
778
|
+
if ((_m = this.printOptions) == null ? void 0 : _m.json) {
|
|
779
|
+
claudeOptions.jsonMode = "json";
|
|
780
|
+
claudeOptions.outputFormat = "stream-json";
|
|
781
|
+
} else if ((_n = this.printOptions) == null ? void 0 : _n.jsonStream) {
|
|
782
|
+
claudeOptions.jsonMode = "stream";
|
|
783
|
+
claudeOptions.outputFormat = "stream-json";
|
|
784
|
+
}
|
|
785
|
+
if (context.branchName !== void 0) {
|
|
786
|
+
claudeOptions.branchName = context.branchName;
|
|
787
|
+
}
|
|
788
|
+
let mcpConfig;
|
|
789
|
+
let allowedTools;
|
|
790
|
+
let disallowedTools;
|
|
791
|
+
if (context.type === "issue" || context.type === "pr") {
|
|
792
|
+
try {
|
|
793
|
+
const provider = this.settings ? IssueTrackerFactory.getProviderName(this.settings) : "github";
|
|
794
|
+
mcpConfig = await generateIssueManagementMcpConfig(context.type, void 0, provider, this.settings, draftPrNumber);
|
|
795
|
+
logger.debug("Generated MCP configuration for issue management", { provider, draftPrNumber });
|
|
796
|
+
const baseTools = [
|
|
797
|
+
"mcp__issue_management__get_issue",
|
|
798
|
+
"mcp__issue_management__get_comment",
|
|
799
|
+
"mcp__issue_management__create_comment",
|
|
800
|
+
"mcp__issue_management__update_comment",
|
|
801
|
+
"mcp__issue_management__create_issue",
|
|
802
|
+
"mcp__issue_management__close_issue",
|
|
803
|
+
"mcp__issue_management__reopen_issue",
|
|
804
|
+
"mcp__issue_management__edit_issue",
|
|
805
|
+
"mcp__recap__add_entry",
|
|
806
|
+
"mcp__recap__get_recap",
|
|
807
|
+
"mcp__recap__add_artifact",
|
|
808
|
+
"mcp__recap__set_complexity",
|
|
809
|
+
"mcp__recap__set_loom_state",
|
|
810
|
+
"mcp__recap__get_loom_state"
|
|
811
|
+
];
|
|
812
|
+
allowedTools = context.type === "pr" ? [...baseTools, "mcp__issue_management__get_pr", "mcp__issue_management__get_review_comments", "mcp__recap__set_goal"] : baseTools;
|
|
813
|
+
disallowedTools = ["Bash(gh api:*), Bash(gh issue comment:*)"];
|
|
814
|
+
logger.debug("Configured tool filtering for issue/PR workflow", { allowedTools, disallowedTools });
|
|
815
|
+
} catch (error) {
|
|
816
|
+
logger.warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
allowedTools = [
|
|
820
|
+
"mcp__recap__set_goal",
|
|
821
|
+
"mcp__recap__add_entry",
|
|
822
|
+
"mcp__recap__get_recap",
|
|
823
|
+
"mcp__recap__set_complexity",
|
|
824
|
+
"mcp__recap__set_loom_state",
|
|
825
|
+
"mcp__recap__get_loom_state"
|
|
826
|
+
];
|
|
827
|
+
logger.debug("Configured tool filtering for regular workflow", { allowedTools });
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
if (!metadata) {
|
|
831
|
+
throw new Error("No loom metadata found for this workspace");
|
|
832
|
+
}
|
|
833
|
+
const recapMcpConfig = generateRecapMcpConfig(context.workspacePath, metadata);
|
|
834
|
+
if (mcpConfig) {
|
|
835
|
+
mcpConfig.push(...recapMcpConfig);
|
|
836
|
+
} else {
|
|
837
|
+
mcpConfig = recapMcpConfig;
|
|
838
|
+
}
|
|
839
|
+
logger.debug("Generated MCP configuration for recap server");
|
|
840
|
+
} catch (error) {
|
|
841
|
+
logger.warn(`Failed to generate recap MCP config: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
842
|
+
}
|
|
843
|
+
let agents;
|
|
844
|
+
try {
|
|
845
|
+
if (((_o = this.settings) == null ? void 0 : _o.agents) && Object.keys(this.settings.agents).length > 0) {
|
|
846
|
+
logger.debug("Loaded project settings", {
|
|
847
|
+
agentOverrides: Object.keys(this.settings.agents)
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
const loadedAgents = await this.agentManager.loadAgents(
|
|
851
|
+
this.settings,
|
|
852
|
+
variables,
|
|
853
|
+
["*.md", "!iloom-framework-detector.md"]
|
|
854
|
+
);
|
|
855
|
+
agents = this.agentManager.formatForCli(loadedAgents);
|
|
856
|
+
logger.debug("Loaded agent configurations", {
|
|
857
|
+
agentCount: Object.keys(agents).length,
|
|
858
|
+
agentNames: Object.keys(agents)
|
|
859
|
+
});
|
|
860
|
+
} catch (error) {
|
|
861
|
+
logger.warn(`Failed to load agents: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
862
|
+
}
|
|
863
|
+
logger.debug("Launching Claude in current terminal", {
|
|
864
|
+
type: context.type,
|
|
865
|
+
model,
|
|
866
|
+
permissionMode,
|
|
867
|
+
workspacePath: context.workspacePath,
|
|
868
|
+
hasMcpConfig: !!mcpConfig
|
|
869
|
+
});
|
|
870
|
+
logger.info(isHeadless ? "\u2728 Launching Claude in headless mode..." : "\u2728 Launching Claude in current terminal...");
|
|
871
|
+
const claudeResult = await launchClaude(userPrompt, {
|
|
872
|
+
...claudeOptions,
|
|
873
|
+
appendSystemPrompt: systemInstructions,
|
|
874
|
+
...mcpConfig && { mcpConfig },
|
|
875
|
+
...allowedTools && { allowedTools },
|
|
876
|
+
...disallowedTools && { disallowedTools },
|
|
877
|
+
...agents && { agents }
|
|
878
|
+
});
|
|
879
|
+
if ((_p = this.printOptions) == null ? void 0 : _p.json) {
|
|
880
|
+
console.log(JSON.stringify({
|
|
881
|
+
success: true,
|
|
882
|
+
output: claudeResult ?? ""
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
if (isFirstRun) {
|
|
886
|
+
await this.firstRunManager.markAsRun();
|
|
887
|
+
}
|
|
888
|
+
} catch (error) {
|
|
889
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
890
|
+
if ((_q = this.printOptions) == null ? void 0 : _q.json) {
|
|
891
|
+
console.log(JSON.stringify({
|
|
892
|
+
success: false,
|
|
893
|
+
error: errorMessage
|
|
894
|
+
}));
|
|
895
|
+
} else {
|
|
896
|
+
logger.error(`Failed to launch Claude: ${errorMessage}`);
|
|
897
|
+
}
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Log user-friendly information about detected context
|
|
903
|
+
*/
|
|
904
|
+
logDetectedContext(context) {
|
|
905
|
+
if (context.type === "issue") {
|
|
906
|
+
logger.info(`\u{1F3AF} Detected issue workflow: Issue #${context.issueNumber}`);
|
|
907
|
+
} else if (context.type === "pr") {
|
|
908
|
+
logger.info(`\u{1F504} Detected PR workflow: PR #${context.prNumber}`);
|
|
909
|
+
} else {
|
|
910
|
+
logger.info("\u{1F31F} Detected regular workflow");
|
|
911
|
+
}
|
|
912
|
+
if (context.branchName) {
|
|
913
|
+
logger.info(`\u{1F33F} Working on branch: ${context.branchName}`);
|
|
914
|
+
}
|
|
915
|
+
if (context.port) {
|
|
916
|
+
logger.info(`\u{1F310} Development server port: ${context.port}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Build template variables from context
|
|
921
|
+
*/
|
|
922
|
+
buildTemplateVariables(context, oneShot, draftPrNumber, draftPrUrl) {
|
|
923
|
+
var _a, _b, _c, _d, _e;
|
|
924
|
+
const variables = {
|
|
925
|
+
WORKSPACE_PATH: context.workspacePath
|
|
926
|
+
};
|
|
927
|
+
if (context.issueNumber !== void 0) {
|
|
928
|
+
variables.ISSUE_NUMBER = context.issueNumber;
|
|
929
|
+
}
|
|
930
|
+
if (context.prNumber !== void 0) {
|
|
931
|
+
variables.PR_NUMBER = context.prNumber;
|
|
932
|
+
}
|
|
933
|
+
if (context.title !== void 0) {
|
|
934
|
+
if (context.type === "issue") {
|
|
935
|
+
variables.ISSUE_TITLE = context.title;
|
|
936
|
+
} else if (context.type === "pr") {
|
|
937
|
+
variables.PR_TITLE = context.title;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (context.port !== void 0) {
|
|
941
|
+
variables.PORT = context.port;
|
|
942
|
+
}
|
|
943
|
+
if (oneShot === "noReview" || oneShot === "bypassPermissions") {
|
|
944
|
+
variables.ONE_SHOT_MODE = true;
|
|
945
|
+
} else {
|
|
946
|
+
variables.INTERACTIVE_MODE = true;
|
|
947
|
+
}
|
|
948
|
+
Object.assign(variables, buildReviewTemplateVariables((_a = this.settings) == null ? void 0 : _a.agents));
|
|
949
|
+
if (draftPrNumber !== void 0) {
|
|
950
|
+
variables.DRAFT_PR_MODE = true;
|
|
951
|
+
variables.DRAFT_PR_NUMBER = draftPrNumber;
|
|
952
|
+
if (draftPrUrl) {
|
|
953
|
+
variables.DRAFT_PR_URL = draftPrUrl;
|
|
954
|
+
}
|
|
955
|
+
const autoCommitPushEnabled = ((_c = (_b = this.settings) == null ? void 0 : _b.mergeBehavior) == null ? void 0 : _c.autoCommitPush) !== false;
|
|
956
|
+
variables.AUTO_COMMIT_PUSH = autoCommitPushEnabled;
|
|
957
|
+
const remote = ((_e = (_d = this.settings) == null ? void 0 : _d.mergeBehavior) == null ? void 0 : _e.remote) ?? "origin";
|
|
958
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(remote)) {
|
|
959
|
+
throw new Error(`Invalid git remote name: "${remote}". Remote names can only contain alphanumeric characters, underscores, and hyphens.`);
|
|
960
|
+
}
|
|
961
|
+
variables.GIT_REMOTE = remote;
|
|
962
|
+
} else if (context.type === "regular") {
|
|
963
|
+
variables.STANDARD_BRANCH_MODE = true;
|
|
964
|
+
} else {
|
|
965
|
+
variables.STANDARD_ISSUE_MODE = true;
|
|
966
|
+
}
|
|
967
|
+
const isVscodeMode = process.env.ILOOM_VSCODE === "1";
|
|
968
|
+
variables.IS_VSCODE_MODE = isVscodeMode;
|
|
969
|
+
return variables;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Get the appropriate permission mode for a workflow type
|
|
973
|
+
* Same logic as ClaudeService.getPermissionModeForWorkflow()
|
|
974
|
+
*/
|
|
975
|
+
getPermissionModeForWorkflow(type) {
|
|
976
|
+
var _a;
|
|
977
|
+
if ((_a = this.settings) == null ? void 0 : _a.workflows) {
|
|
978
|
+
const workflowConfig = type === "issue" ? this.settings.workflows.issue : type === "pr" ? this.settings.workflows.pr : this.settings.workflows.regular;
|
|
979
|
+
if (workflowConfig == null ? void 0 : workflowConfig.permissionMode) {
|
|
980
|
+
return workflowConfig.permissionMode;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (type === "issue") {
|
|
984
|
+
return "acceptEdits";
|
|
985
|
+
}
|
|
986
|
+
return "default";
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Auto-detect workspace context from current directory and git branch
|
|
990
|
+
*
|
|
991
|
+
* Detection priority:
|
|
992
|
+
* 1. Directory name patterns (_pr_N, issue-N)
|
|
993
|
+
* 2. Git branch name patterns
|
|
994
|
+
* 3. Fallback to 'regular' workflow
|
|
995
|
+
*
|
|
996
|
+
* This leverages the same logic as FinishCommand.autoDetectFromCurrentDirectory()
|
|
997
|
+
*/
|
|
998
|
+
async detectWorkspaceContext() {
|
|
999
|
+
const workspacePath = process.cwd();
|
|
1000
|
+
const currentDir = path4.basename(workspacePath);
|
|
1001
|
+
const prPattern = /_pr_(\d+)$/;
|
|
1002
|
+
const prMatch = currentDir.match(prPattern);
|
|
1003
|
+
if (prMatch == null ? void 0 : prMatch[1]) {
|
|
1004
|
+
const prNumber = parseInt(prMatch[1], 10);
|
|
1005
|
+
logger.debug(`Auto-detected PR #${prNumber} from directory: ${currentDir}`);
|
|
1006
|
+
return this.buildContextForPR(prNumber, workspacePath);
|
|
1007
|
+
}
|
|
1008
|
+
const issueNumber = extractIssueNumber(currentDir);
|
|
1009
|
+
if (issueNumber !== null) {
|
|
1010
|
+
logger.debug(`Auto-detected issue #${issueNumber} from directory: ${currentDir}`);
|
|
1011
|
+
return this.buildContextForIssue(issueNumber, workspacePath);
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
const repoInfo = await this.gitWorktreeManager.getRepoInfo();
|
|
1015
|
+
const currentBranch = repoInfo.currentBranch;
|
|
1016
|
+
if (currentBranch) {
|
|
1017
|
+
const branchIssueNumber = extractIssueNumber(currentBranch);
|
|
1018
|
+
if (branchIssueNumber !== null) {
|
|
1019
|
+
logger.debug(`Auto-detected issue #${branchIssueNumber} from branch: ${currentBranch}`);
|
|
1020
|
+
return this.buildContextForIssue(branchIssueNumber, workspacePath, currentBranch);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
logger.debug("Could not detect from git branch", { error });
|
|
1025
|
+
}
|
|
1026
|
+
logger.debug("No specific context detected, using regular workflow");
|
|
1027
|
+
return this.buildContextForRegular(workspacePath);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Build context for issue workflow
|
|
1031
|
+
*/
|
|
1032
|
+
async buildContextForIssue(issueNumber, workspacePath, branchName) {
|
|
1033
|
+
if (!branchName) {
|
|
1034
|
+
try {
|
|
1035
|
+
const repoInfo = await this.gitWorktreeManager.getRepoInfo();
|
|
1036
|
+
branchName = repoInfo.currentBranch ?? void 0;
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
const context = {
|
|
1041
|
+
type: "issue",
|
|
1042
|
+
issueNumber,
|
|
1043
|
+
workspacePath,
|
|
1044
|
+
headless: false
|
|
1045
|
+
// Interactive mode
|
|
1046
|
+
};
|
|
1047
|
+
if (branchName !== void 0) {
|
|
1048
|
+
context.branchName = branchName;
|
|
1049
|
+
}
|
|
1050
|
+
return context;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Build context for PR workflow
|
|
1054
|
+
*/
|
|
1055
|
+
async buildContextForPR(prNumber, workspacePath) {
|
|
1056
|
+
let branchName;
|
|
1057
|
+
try {
|
|
1058
|
+
const repoInfo = await this.gitWorktreeManager.getRepoInfo();
|
|
1059
|
+
branchName = repoInfo.currentBranch ?? void 0;
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
const context = {
|
|
1063
|
+
type: "pr",
|
|
1064
|
+
prNumber,
|
|
1065
|
+
workspacePath,
|
|
1066
|
+
headless: false
|
|
1067
|
+
// Interactive mode
|
|
1068
|
+
};
|
|
1069
|
+
if (branchName !== void 0) {
|
|
1070
|
+
context.branchName = branchName;
|
|
1071
|
+
}
|
|
1072
|
+
return context;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Build context for regular workflow
|
|
1076
|
+
*/
|
|
1077
|
+
async buildContextForRegular(workspacePath) {
|
|
1078
|
+
let branchName;
|
|
1079
|
+
try {
|
|
1080
|
+
const repoInfo = await this.gitWorktreeManager.getRepoInfo();
|
|
1081
|
+
branchName = repoInfo.currentBranch ?? void 0;
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
const context = {
|
|
1085
|
+
type: "regular",
|
|
1086
|
+
workspacePath,
|
|
1087
|
+
headless: false
|
|
1088
|
+
// Interactive mode
|
|
1089
|
+
};
|
|
1090
|
+
if (branchName !== void 0) {
|
|
1091
|
+
context.branchName = branchName;
|
|
1092
|
+
}
|
|
1093
|
+
return context;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Fetch and store epic child issue data and dependency map in metadata
|
|
1097
|
+
*
|
|
1098
|
+
* Called during spin setup for epic looms. Fetches child issue details
|
|
1099
|
+
* and dependency relationships from the issue tracker, then persists
|
|
1100
|
+
* them in the loom metadata for use by the orchestrator.
|
|
1101
|
+
*/
|
|
1102
|
+
async fetchAndStoreEpicChildData(metadataManager, metadata, worktreePath, settings) {
|
|
1103
|
+
const parentIssueNumber = metadata.issue_numbers[0];
|
|
1104
|
+
if (!parentIssueNumber) return;
|
|
1105
|
+
logger.info("Fetching child issue data for epic...");
|
|
1106
|
+
try {
|
|
1107
|
+
const issueTracker = IssueTrackerFactory.create(settings);
|
|
1108
|
+
const childIssueDetails = await fetchChildIssueDetails(
|
|
1109
|
+
parentIssueNumber,
|
|
1110
|
+
issueTracker
|
|
1111
|
+
);
|
|
1112
|
+
if (childIssueDetails.length === 0) {
|
|
1113
|
+
logger.debug("No child issues found for epic");
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const childIds = childIssueDetails.map((child) => child.number.replace(/^#/, ""));
|
|
1117
|
+
const dependencyMap = await buildDependencyMap(childIds, settings);
|
|
1118
|
+
await metadataManager.updateMetadata(worktreePath, {
|
|
1119
|
+
childIssues: childIssueDetails,
|
|
1120
|
+
dependencyMap
|
|
1121
|
+
});
|
|
1122
|
+
logger.info(`Stored ${childIssueDetails.length} child issues and dependency map in metadata`);
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
logger.warn(`Failed to fetch epic child data: ${error instanceof Error ? error.message : String(error)}`);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Execute swarm mode for an epic loom.
|
|
1129
|
+
*
|
|
1130
|
+
* Creates child worktrees, renders swarm agents/skill, builds the
|
|
1131
|
+
* orchestrator prompt, and launches Claude with agent teams enabled.
|
|
1132
|
+
*/
|
|
1133
|
+
async executeSwarmMode(metadata, epicWorktreePath, epicBranch, metadataManager, skipCleanup) {
|
|
1134
|
+
var _a, _b, _c;
|
|
1135
|
+
if (!this.settings) {
|
|
1136
|
+
throw new Error("Settings not loaded. Cannot enter swarm mode.");
|
|
1137
|
+
}
|
|
1138
|
+
const settings = this.settings;
|
|
1139
|
+
const epicIssueNumber = metadata.issue_numbers[0];
|
|
1140
|
+
if (!epicIssueNumber) {
|
|
1141
|
+
throw new Error("Epic loom has no issue number in metadata");
|
|
1142
|
+
}
|
|
1143
|
+
logger.info("Epic loom detected - entering swarm mode...");
|
|
1144
|
+
const mainWorktreePath = await findMainWorktreePathWithSettings();
|
|
1145
|
+
const providerName = IssueTrackerFactory.getProviderName(settings);
|
|
1146
|
+
const swarmSetup = new SwarmSetupService(
|
|
1147
|
+
this.gitWorktreeManager,
|
|
1148
|
+
metadataManager,
|
|
1149
|
+
this.agentManager,
|
|
1150
|
+
this.settingsManager,
|
|
1151
|
+
this.templateManager
|
|
1152
|
+
);
|
|
1153
|
+
try {
|
|
1154
|
+
const epicMcpConfigPath = await generateAndWriteMcpConfigFile(
|
|
1155
|
+
epicWorktreePath,
|
|
1156
|
+
metadata,
|
|
1157
|
+
providerName,
|
|
1158
|
+
settings
|
|
1159
|
+
);
|
|
1160
|
+
await metadataManager.updateMetadata(epicWorktreePath, { mcpConfigPath: epicMcpConfigPath });
|
|
1161
|
+
const epicClaudeDir = path4.join(epicWorktreePath, ".claude");
|
|
1162
|
+
await fs4.ensureDir(epicClaudeDir);
|
|
1163
|
+
await fs4.writeFile(
|
|
1164
|
+
path4.join(epicClaudeDir, "iloom-swarm-mcp-config-path"),
|
|
1165
|
+
epicMcpConfigPath,
|
|
1166
|
+
"utf-8"
|
|
1167
|
+
);
|
|
1168
|
+
logger.debug("Wrote MCP config for epic loom", { epicMcpConfigPath });
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
logger.warn(`Failed to write MCP config for epic loom: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1171
|
+
}
|
|
1172
|
+
const mcpConfigs = [];
|
|
1173
|
+
try {
|
|
1174
|
+
const issueMcpConfigs = await generateIssueManagementMcpConfig(
|
|
1175
|
+
"issue",
|
|
1176
|
+
void 0,
|
|
1177
|
+
providerName,
|
|
1178
|
+
settings
|
|
1179
|
+
);
|
|
1180
|
+
mcpConfigs.push(...issueMcpConfigs);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
logger.warn(`Failed to generate issue management MCP config: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1183
|
+
}
|
|
1184
|
+
try {
|
|
1185
|
+
const recapMcpConfigs = generateRecapMcpConfig(epicWorktreePath, metadata);
|
|
1186
|
+
mcpConfigs.push(...recapMcpConfigs);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
logger.warn(`Failed to generate recap MCP config: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1189
|
+
}
|
|
1190
|
+
const swarmResult = await swarmSetup.setupSwarm(
|
|
1191
|
+
epicIssueNumber,
|
|
1192
|
+
epicBranch,
|
|
1193
|
+
epicWorktreePath,
|
|
1194
|
+
metadata.childIssues,
|
|
1195
|
+
mainWorktreePath,
|
|
1196
|
+
providerName,
|
|
1197
|
+
settings
|
|
1198
|
+
);
|
|
1199
|
+
const successfulWorktrees = swarmResult.childWorktrees.filter((c) => c.success);
|
|
1200
|
+
const worktreeMap = new Map(successfulWorktrees.map((cw) => [cw.issueId, cw]));
|
|
1201
|
+
const childIssuesData = metadata.childIssues.filter((ci) => worktreeMap.has(ci.number.replace(/^#/, ""))).map((ci) => {
|
|
1202
|
+
const rawId = ci.number.replace(/^#/, "");
|
|
1203
|
+
const wt = worktreeMap.get(rawId);
|
|
1204
|
+
return {
|
|
1205
|
+
number: rawId,
|
|
1206
|
+
title: ci.title,
|
|
1207
|
+
body: ci.body,
|
|
1208
|
+
worktreePath: (wt == null ? void 0 : wt.worktreePath) ?? "",
|
|
1209
|
+
branchName: (wt == null ? void 0 : wt.branch) ?? ""
|
|
1210
|
+
};
|
|
1211
|
+
});
|
|
1212
|
+
const epicMetadataPath = metadataManager.getMetadataFilePath(epicWorktreePath);
|
|
1213
|
+
const issuePrefix = providerName === "github" ? "#" : "";
|
|
1214
|
+
const variables = {
|
|
1215
|
+
EPIC_ISSUE_NUMBER: epicIssueNumber,
|
|
1216
|
+
EPIC_WORKTREE_PATH: epicWorktreePath,
|
|
1217
|
+
EPIC_METADATA_PATH: epicMetadataPath,
|
|
1218
|
+
CHILD_ISSUES: JSON.stringify(childIssuesData, null, 2),
|
|
1219
|
+
DEPENDENCY_MAP: JSON.stringify(metadata.dependencyMap, null, 2),
|
|
1220
|
+
ISSUE_PREFIX: issuePrefix,
|
|
1221
|
+
...skipCleanup && { NO_CLEANUP: true }
|
|
1222
|
+
};
|
|
1223
|
+
const draftPrNumber = metadata.draftPrNumber ?? void 0;
|
|
1224
|
+
if (draftPrNumber !== void 0) {
|
|
1225
|
+
variables.DRAFT_PR_MODE = true;
|
|
1226
|
+
variables.DRAFT_PR_NUMBER = draftPrNumber;
|
|
1227
|
+
const draftPrUrl = (_a = metadata.prUrls) == null ? void 0 : _a[String(draftPrNumber)];
|
|
1228
|
+
if (draftPrUrl) {
|
|
1229
|
+
variables.DRAFT_PR_URL = draftPrUrl;
|
|
1230
|
+
}
|
|
1231
|
+
const autoCommitPushEnabled = ((_b = settings.mergeBehavior) == null ? void 0 : _b.autoCommitPush) !== false;
|
|
1232
|
+
variables.AUTO_COMMIT_PUSH = autoCommitPushEnabled;
|
|
1233
|
+
const remote = ((_c = settings.mergeBehavior) == null ? void 0 : _c.remote) ?? "origin";
|
|
1234
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(remote)) {
|
|
1235
|
+
throw new Error(`Invalid git remote name: "${remote}". Remote names can only contain alphanumeric characters, underscores, and hyphens.`);
|
|
1236
|
+
}
|
|
1237
|
+
variables.GIT_REMOTE = remote;
|
|
1238
|
+
}
|
|
1239
|
+
const orchestratorPrompt = await this.templateManager.getPrompt("swarm-orchestrator", variables);
|
|
1240
|
+
const allowedTools = [
|
|
1241
|
+
"mcp__issue_management__get_issue",
|
|
1242
|
+
"mcp__issue_management__get_comment",
|
|
1243
|
+
"mcp__issue_management__create_comment",
|
|
1244
|
+
"mcp__issue_management__update_comment",
|
|
1245
|
+
"mcp__issue_management__create_issue",
|
|
1246
|
+
"mcp__issue_management__close_issue",
|
|
1247
|
+
"mcp__issue_management__reopen_issue",
|
|
1248
|
+
"mcp__issue_management__edit_issue",
|
|
1249
|
+
"mcp__recap__add_entry",
|
|
1250
|
+
"mcp__recap__get_recap",
|
|
1251
|
+
"mcp__recap__add_artifact",
|
|
1252
|
+
"mcp__recap__set_complexity",
|
|
1253
|
+
"mcp__recap__set_loom_state",
|
|
1254
|
+
"mcp__recap__get_loom_state"
|
|
1255
|
+
];
|
|
1256
|
+
const model = this.settingsManager.getSpinModel(settings);
|
|
1257
|
+
logger.info("Launching swarm orchestrator...");
|
|
1258
|
+
logger.info(` Model: ${model ?? "default"}`);
|
|
1259
|
+
logger.info(` Permission mode: bypassPermissions`);
|
|
1260
|
+
logger.info(` Agent teams: enabled`);
|
|
1261
|
+
logger.info(` Child worktrees: ${successfulWorktrees.length}`);
|
|
1262
|
+
let agents;
|
|
1263
|
+
try {
|
|
1264
|
+
const loadedAgents = await this.agentManager.loadAgents(
|
|
1265
|
+
settings,
|
|
1266
|
+
variables,
|
|
1267
|
+
["*.md", "!iloom-framework-detector.md"]
|
|
1268
|
+
);
|
|
1269
|
+
agents = this.agentManager.formatForCli(loadedAgents);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
logger.warn(`Failed to load agents: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1272
|
+
}
|
|
1273
|
+
const swarmStartTime = Date.now();
|
|
1274
|
+
try {
|
|
1275
|
+
TelemetryService.getInstance().track("swarm.started", {
|
|
1276
|
+
child_count: successfulWorktrees.length,
|
|
1277
|
+
tracker: providerName
|
|
1278
|
+
});
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
logger.debug(`Telemetry swarm.started tracking failed: ${error instanceof Error ? error.message : error}`);
|
|
1281
|
+
}
|
|
1282
|
+
await launchClaude(
|
|
1283
|
+
`You are the swarm orchestrator for epic #${epicIssueNumber}. Begin by reading your system prompt instructions and executing the workflow.`,
|
|
1284
|
+
{
|
|
1285
|
+
model,
|
|
1286
|
+
permissionMode: "bypassPermissions",
|
|
1287
|
+
addDir: epicWorktreePath,
|
|
1288
|
+
headless: false,
|
|
1289
|
+
...metadata.sessionId && { sessionId: metadata.sessionId },
|
|
1290
|
+
appendSystemPrompt: orchestratorPrompt,
|
|
1291
|
+
mcpConfig: mcpConfigs,
|
|
1292
|
+
allowedTools,
|
|
1293
|
+
...agents && { agents },
|
|
1294
|
+
env: {
|
|
1295
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1",
|
|
1296
|
+
ILOOM_SWARM: "1",
|
|
1297
|
+
ENABLE_TOOL_SEARCH: "auto:30"
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
try {
|
|
1302
|
+
const swarmEndTime = Date.now();
|
|
1303
|
+
let succeeded = 0;
|
|
1304
|
+
let failed = 0;
|
|
1305
|
+
for (const child of successfulWorktrees) {
|
|
1306
|
+
const childMeta = await metadataManager.readMetadata(child.worktreePath);
|
|
1307
|
+
const isSuccess = (childMeta == null ? void 0 : childMeta.state) === "done";
|
|
1308
|
+
if (isSuccess) {
|
|
1309
|
+
succeeded++;
|
|
1310
|
+
} else {
|
|
1311
|
+
failed++;
|
|
1312
|
+
}
|
|
1313
|
+
const parsed = (childMeta == null ? void 0 : childMeta.created_at) ? Date.parse(childMeta.created_at) : NaN;
|
|
1314
|
+
const childCreatedAt = Number.isNaN(parsed) ? swarmStartTime : parsed;
|
|
1315
|
+
const childDuration = Math.max(0, Math.round((swarmEndTime - childCreatedAt) / 6e4));
|
|
1316
|
+
TelemetryService.getInstance().track("swarm.child_completed", {
|
|
1317
|
+
success: isSuccess,
|
|
1318
|
+
duration_minutes: childDuration
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
TelemetryService.getInstance().track("swarm.completed", {
|
|
1322
|
+
total_children: successfulWorktrees.length,
|
|
1323
|
+
succeeded,
|
|
1324
|
+
failed,
|
|
1325
|
+
duration_minutes: Math.round((swarmEndTime - swarmStartTime) / 6e4)
|
|
1326
|
+
});
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
logger.debug(`Telemetry swarm completion tracking failed: ${error instanceof Error ? error.message : error}`);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Build user prompt based on one-shot mode
|
|
1333
|
+
*/
|
|
1334
|
+
buildUserPrompt(oneShot = "default") {
|
|
1335
|
+
if (oneShot === "noReview" || oneShot === "bypassPermissions") {
|
|
1336
|
+
return "Guide the user through the iloom workflow! The user has requested you move through the workflow without awaiting confirmation. This supersedes any other guidance.";
|
|
1337
|
+
}
|
|
1338
|
+
return "Guide the user through the iloom workflow!";
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Load README.md content for first-time users
|
|
1342
|
+
* Walks up from dist directory to find README.md in project root
|
|
1343
|
+
*/
|
|
1344
|
+
async loadReadmeContent() {
|
|
1345
|
+
try {
|
|
1346
|
+
let currentDir = path4.dirname(new URL(import.meta.url).pathname);
|
|
1347
|
+
while (currentDir !== path4.dirname(currentDir)) {
|
|
1348
|
+
const readmePath = path4.join(currentDir, "README.md");
|
|
1349
|
+
try {
|
|
1350
|
+
const content = await readFile(readmePath, "utf-8");
|
|
1351
|
+
logger.debug("Loaded README.md for first-time user", { readmePath });
|
|
1352
|
+
return content;
|
|
1353
|
+
} catch {
|
|
1354
|
+
currentDir = path4.dirname(currentDir);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
logger.debug("README.md not found, returning empty string");
|
|
1358
|
+
return "";
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
logger.debug(`Failed to load README.md: ${error}`);
|
|
1361
|
+
return "";
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Load settings schema content for first-time users
|
|
1366
|
+
* Walks up from dist directory to find .iloom/README.md
|
|
1367
|
+
*/
|
|
1368
|
+
async loadSettingsSchemaContent() {
|
|
1369
|
+
try {
|
|
1370
|
+
let currentDir = path4.dirname(new URL(import.meta.url).pathname);
|
|
1371
|
+
while (currentDir !== path4.dirname(currentDir)) {
|
|
1372
|
+
const schemaPath = path4.join(currentDir, ".iloom", "README.md");
|
|
1373
|
+
try {
|
|
1374
|
+
const content = await readFile(schemaPath, "utf-8");
|
|
1375
|
+
logger.debug("Loaded .iloom/README.md for first-time user", { schemaPath });
|
|
1376
|
+
return content;
|
|
1377
|
+
} catch {
|
|
1378
|
+
currentDir = path4.dirname(currentDir);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
logger.debug(".iloom/README.md not found, returning empty string");
|
|
1382
|
+
return "";
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
logger.debug(`Failed to load .iloom/README.md: ${error}`);
|
|
1385
|
+
return "";
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
export {
|
|
1390
|
+
IgniteCommand,
|
|
1391
|
+
WorktreeValidationError
|
|
1392
|
+
};
|
|
1393
|
+
//# sourceMappingURL=ignite-CGOV3TD4.js.map
|