@iloom/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +234 -667
  2. package/dist/BranchNamingService-OMWKUYMM.js +13 -0
  3. package/dist/ClaudeContextManager-3VXA6UPR.js +13 -0
  4. package/dist/ClaudeService-6CPK43N4.js +12 -0
  5. package/dist/GitHubService-EBOETDIW.js +11 -0
  6. package/dist/{LoomLauncher-CTSWJL35.js → LoomLauncher-JF7JZMTZ.js} +63 -32
  7. package/dist/LoomLauncher-JF7JZMTZ.js.map +1 -0
  8. package/dist/ProjectCapabilityDetector-34LU7JJ4.js +9 -0
  9. package/dist/{PromptTemplateManager-WII75TKH.js → PromptTemplateManager-A52RUAMS.js} +2 -2
  10. package/dist/README.md +234 -667
  11. package/dist/{SettingsManager-XOYCLH3D.js → SettingsManager-ZCWJ56WP.js} +12 -4
  12. package/dist/SettingsMigrationManager-AGIIIPDQ.js +10 -0
  13. package/dist/agents/iloom-issue-analyze-and-plan.md +125 -35
  14. package/dist/agents/iloom-issue-analyzer.md +284 -32
  15. package/dist/agents/iloom-issue-complexity-evaluator.md +40 -21
  16. package/dist/agents/iloom-issue-enhancer.md +69 -48
  17. package/dist/agents/iloom-issue-implementer.md +36 -25
  18. package/dist/agents/iloom-issue-planner.md +35 -24
  19. package/dist/agents/iloom-issue-reviewer.md +62 -9
  20. package/dist/{chunk-SWCRXDZC.js → chunk-3RUPPQRG.js} +1 -18
  21. package/dist/chunk-3RUPPQRG.js.map +1 -0
  22. package/dist/{chunk-HBVFXN7R.js → chunk-4BGK7T6X.js} +26 -3
  23. package/dist/chunk-4BGK7T6X.js.map +1 -0
  24. package/dist/{chunk-6LEQW46Y.js → chunk-4E4LD3QR.js} +72 -2
  25. package/dist/{chunk-6LEQW46Y.js.map → chunk-4E4LD3QR.js.map} +1 -1
  26. package/dist/{chunk-CWR2SANQ.js → chunk-EBISESAP.js} +1 -1
  27. package/dist/{chunk-TS6DL67T.js → chunk-G2IEYOLQ.js} +11 -38
  28. package/dist/chunk-G2IEYOLQ.js.map +1 -0
  29. package/dist/chunk-HBYZH6GD.js +1989 -0
  30. package/dist/chunk-HBYZH6GD.js.map +1 -0
  31. package/dist/chunk-INW24J2W.js +55 -0
  32. package/dist/chunk-INW24J2W.js.map +1 -0
  33. package/dist/{chunk-ZMNQBJUI.js → chunk-IP7SMKIF.js} +61 -22
  34. package/dist/chunk-IP7SMKIF.js.map +1 -0
  35. package/dist/{chunk-4IV6W4U5.js → chunk-IXKLYTWO.js} +12 -12
  36. package/dist/chunk-IXKLYTWO.js.map +1 -0
  37. package/dist/{chunk-JNKJ7NJV.js → chunk-JKXJ7BGL.js} +6 -2
  38. package/dist/{chunk-JNKJ7NJV.js.map → chunk-JKXJ7BGL.js.map} +1 -1
  39. package/dist/{chunk-LAPY6NAE.js → chunk-JQFO7QQN.js} +68 -12
  40. package/dist/{chunk-LAPY6NAE.js.map → chunk-JQFO7QQN.js.map} +1 -1
  41. package/dist/{SettingsMigrationManager-MTQIMI54.js → chunk-KLBYVHPK.js} +3 -2
  42. package/dist/{chunk-USVVV3FP.js → chunk-MKWYLDFK.js} +5 -5
  43. package/dist/chunk-O5OH5MRX.js +396 -0
  44. package/dist/chunk-O5OH5MRX.js.map +1 -0
  45. package/dist/{chunk-DJUGYNQE.js → chunk-PA6Q6AWM.js} +16 -3
  46. package/dist/chunk-PA6Q6AWM.js.map +1 -0
  47. package/dist/chunk-RO26VS3W.js +444 -0
  48. package/dist/chunk-RO26VS3W.js.map +1 -0
  49. package/dist/{chunk-VETG35MF.js → chunk-TSKY3JI7.js} +3 -3
  50. package/dist/{chunk-VETG35MF.js.map → chunk-TSKY3JI7.js.map} +1 -1
  51. package/dist/{chunk-LHP6ROUM.js → chunk-U5QDY7ZD.js} +4 -16
  52. package/dist/chunk-U5QDY7ZD.js.map +1 -0
  53. package/dist/{chunk-SPYPLHMK.js → chunk-VU3QMIP2.js} +34 -2
  54. package/dist/chunk-VU3QMIP2.js.map +1 -0
  55. package/dist/{chunk-PVAVNJKS.js → chunk-WEN5C5DM.js} +10 -1
  56. package/dist/chunk-WEN5C5DM.js.map +1 -0
  57. package/dist/{chunk-2PLUQT6J.js → chunk-XPKDPZ5D.js} +2 -2
  58. package/dist/{chunk-RF2YI2XJ.js → chunk-ZBQVSHVT.js} +5 -5
  59. package/dist/chunk-ZBQVSHVT.js.map +1 -0
  60. package/dist/{chunk-GZP4UGGM.js → chunk-ZM3CFL5L.js} +2 -2
  61. package/dist/{chunk-BLCTGFZN.js → chunk-ZT3YZB4K.js} +3 -4
  62. package/dist/chunk-ZT3YZB4K.js.map +1 -0
  63. package/dist/{chunk-MFU53H6J.js → chunk-ZWFBBPJI.js} +6 -6
  64. package/dist/{chunk-MFU53H6J.js.map → chunk-ZWFBBPJI.js.map} +1 -1
  65. package/dist/{claude-ZIWDG4XG.js → claude-LUZ35IMK.js} +2 -2
  66. package/dist/{cleanup-FEIVZSIV.js → cleanup-3MONU4PU.js} +88 -27
  67. package/dist/cleanup-3MONU4PU.js.map +1 -0
  68. package/dist/cli.js +2511 -62
  69. package/dist/cli.js.map +1 -1
  70. package/dist/{contribute-EMZKCAC6.js → contribute-UWJAGIG7.js} +6 -6
  71. package/dist/{feedback-LFNMQBAZ.js → feedback-W3BXTGIM.js} +15 -14
  72. package/dist/{feedback-LFNMQBAZ.js.map → feedback-W3BXTGIM.js.map} +1 -1
  73. package/dist/{git-WC6HZLOT.js → git-34Z6QVDS.js} +4 -2
  74. package/dist/{ignite-MQWVJEAB.js → ignite-KVJEFXNO.js} +32 -27
  75. package/dist/ignite-KVJEFXNO.js.map +1 -0
  76. package/dist/index.d.ts +359 -45
  77. package/dist/index.js +1267 -503
  78. package/dist/index.js.map +1 -1
  79. package/dist/{init-GJDYN2IK.js → init-L55Q73H4.js} +104 -40
  80. package/dist/init-L55Q73H4.js.map +1 -0
  81. package/dist/mcp/issue-management-server.js +934 -0
  82. package/dist/mcp/issue-management-server.js.map +1 -0
  83. package/dist/{neon-helpers-ZVIRPKCI.js → neon-helpers-WPUACUVC.js} +3 -3
  84. package/dist/neon-helpers-WPUACUVC.js.map +1 -0
  85. package/dist/{open-NXSN7XOC.js → open-LNRZL3UU.js} +39 -36
  86. package/dist/open-LNRZL3UU.js.map +1 -0
  87. package/dist/{prompt-ANTQWHUF.js → prompt-7INJ7YRU.js} +4 -2
  88. package/dist/prompt-7INJ7YRU.js.map +1 -0
  89. package/dist/prompts/init-prompt.txt +541 -98
  90. package/dist/prompts/issue-prompt.txt +27 -27
  91. package/dist/{rebase-DUNFOJVS.js → rebase-C4WNCVGM.js} +6 -6
  92. package/dist/{remote-ZCXJVVNW.js → remote-VUNCQZ6J.js} +3 -2
  93. package/dist/remote-VUNCQZ6J.js.map +1 -0
  94. package/dist/{run-O7ZK7CKA.js → run-IOGNIOYN.js} +39 -36
  95. package/dist/run-IOGNIOYN.js.map +1 -0
  96. package/dist/schema/settings.schema.json +59 -3
  97. package/dist/{test-git-T76HOTIA.js → test-git-J7I5MFYH.js} +3 -3
  98. package/dist/{test-prefix-6HJUVQMH.js → test-prefix-ZCONBCBX.js} +3 -3
  99. package/dist/{test-webserver-M2I3EV4J.js → test-webserver-DAHONWCS.js} +4 -4
  100. package/dist/test-webserver-DAHONWCS.js.map +1 -0
  101. package/package.json +3 -2
  102. package/dist/ClaudeContextManager-LVCYRM6Q.js +0 -13
  103. package/dist/ClaudeService-WVTWB3DK.js +0 -12
  104. package/dist/GitHubService-7E2S5NNZ.js +0 -11
  105. package/dist/LoomLauncher-CTSWJL35.js.map +0 -1
  106. package/dist/add-issue-OBI325W7.js +0 -69
  107. package/dist/add-issue-OBI325W7.js.map +0 -1
  108. package/dist/chunk-4IV6W4U5.js.map +0 -1
  109. package/dist/chunk-BLCTGFZN.js.map +0 -1
  110. package/dist/chunk-CVLAZRNB.js +0 -54
  111. package/dist/chunk-CVLAZRNB.js.map +0 -1
  112. package/dist/chunk-DJUGYNQE.js.map +0 -1
  113. package/dist/chunk-H4E4THUZ.js +0 -55
  114. package/dist/chunk-H4E4THUZ.js.map +0 -1
  115. package/dist/chunk-H5LDRGVK.js +0 -642
  116. package/dist/chunk-H5LDRGVK.js.map +0 -1
  117. package/dist/chunk-HBVFXN7R.js.map +0 -1
  118. package/dist/chunk-LHP6ROUM.js.map +0 -1
  119. package/dist/chunk-PVAVNJKS.js.map +0 -1
  120. package/dist/chunk-RF2YI2XJ.js.map +0 -1
  121. package/dist/chunk-SPYPLHMK.js.map +0 -1
  122. package/dist/chunk-SWCRXDZC.js.map +0 -1
  123. package/dist/chunk-SYOSCMIT.js +0 -545
  124. package/dist/chunk-SYOSCMIT.js.map +0 -1
  125. package/dist/chunk-T3KEIB4D.js +0 -243
  126. package/dist/chunk-T3KEIB4D.js.map +0 -1
  127. package/dist/chunk-TS6DL67T.js.map +0 -1
  128. package/dist/chunk-ZMNQBJUI.js.map +0 -1
  129. package/dist/cleanup-FEIVZSIV.js.map +0 -1
  130. package/dist/enhance-MNA4ZGXW.js +0 -176
  131. package/dist/enhance-MNA4ZGXW.js.map +0 -1
  132. package/dist/finish-TX5CJICB.js +0 -1749
  133. package/dist/finish-TX5CJICB.js.map +0 -1
  134. package/dist/ignite-MQWVJEAB.js.map +0 -1
  135. package/dist/init-GJDYN2IK.js.map +0 -1
  136. package/dist/mcp/chunk-6SDFJ42P.js +0 -62
  137. package/dist/mcp/chunk-6SDFJ42P.js.map +0 -1
  138. package/dist/mcp/claude-NDFOCQQQ.js +0 -249
  139. package/dist/mcp/claude-NDFOCQQQ.js.map +0 -1
  140. package/dist/mcp/color-QS5BFCNN.js +0 -168
  141. package/dist/mcp/color-QS5BFCNN.js.map +0 -1
  142. package/dist/mcp/github-comment-server.js +0 -168
  143. package/dist/mcp/github-comment-server.js.map +0 -1
  144. package/dist/mcp/terminal-OMNRFWB3.js +0 -227
  145. package/dist/mcp/terminal-OMNRFWB3.js.map +0 -1
  146. package/dist/open-NXSN7XOC.js.map +0 -1
  147. package/dist/run-O7ZK7CKA.js.map +0 -1
  148. package/dist/start-73I5W7WW.js +0 -983
  149. package/dist/start-73I5W7WW.js.map +0 -1
  150. package/dist/test-webserver-M2I3EV4J.js.map +0 -1
  151. /package/dist/{ClaudeContextManager-LVCYRM6Q.js.map → BranchNamingService-OMWKUYMM.js.map} +0 -0
  152. /package/dist/{ClaudeService-WVTWB3DK.js.map → ClaudeContextManager-3VXA6UPR.js.map} +0 -0
  153. /package/dist/{GitHubService-7E2S5NNZ.js.map → ClaudeService-6CPK43N4.js.map} +0 -0
  154. /package/dist/{PromptTemplateManager-WII75TKH.js.map → GitHubService-EBOETDIW.js.map} +0 -0
  155. /package/dist/{SettingsManager-XOYCLH3D.js.map → ProjectCapabilityDetector-34LU7JJ4.js.map} +0 -0
  156. /package/dist/{claude-ZIWDG4XG.js.map → PromptTemplateManager-A52RUAMS.js.map} +0 -0
  157. /package/dist/{git-WC6HZLOT.js.map → SettingsManager-ZCWJ56WP.js.map} +0 -0
  158. /package/dist/{neon-helpers-ZVIRPKCI.js.map → SettingsMigrationManager-AGIIIPDQ.js.map} +0 -0
  159. /package/dist/{chunk-CWR2SANQ.js.map → chunk-EBISESAP.js.map} +0 -0
  160. /package/dist/{SettingsMigrationManager-MTQIMI54.js.map → chunk-KLBYVHPK.js.map} +0 -0
  161. /package/dist/{chunk-USVVV3FP.js.map → chunk-MKWYLDFK.js.map} +0 -0
  162. /package/dist/{chunk-2PLUQT6J.js.map → chunk-XPKDPZ5D.js.map} +0 -0
  163. /package/dist/{chunk-GZP4UGGM.js.map → chunk-ZM3CFL5L.js.map} +0 -0
  164. /package/dist/{prompt-ANTQWHUF.js.map → claude-LUZ35IMK.js.map} +0 -0
  165. /package/dist/{contribute-EMZKCAC6.js.map → contribute-UWJAGIG7.js.map} +0 -0
  166. /package/dist/{remote-ZCXJVVNW.js.map → git-34Z6QVDS.js.map} +0 -0
  167. /package/dist/{rebase-DUNFOJVS.js.map → rebase-C4WNCVGM.js.map} +0 -0
  168. /package/dist/{test-git-T76HOTIA.js.map → test-git-J7I5MFYH.js.map} +0 -0
  169. /package/dist/{test-prefix-6HJUVQMH.js.map → test-prefix-ZCONBCBX.js.map} +0 -0
@@ -1,243 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- logger
4
- } from "./chunk-GEHQXLEI.js";
5
-
6
- // src/lib/SettingsManager.ts
7
- import { readFile } from "fs/promises";
8
- import path from "path";
9
- import { z } from "zod";
10
- import deepmerge from "deepmerge";
11
- var AgentSettingsSchema = z.object({
12
- model: z.enum(["sonnet", "opus", "haiku"]).optional().describe("Claude model shorthand: sonnet, opus, or haiku")
13
- // Future: could add other per-agent overrides
14
- });
15
- var WorkflowPermissionSchema = z.object({
16
- permissionMode: z.enum(["plan", "acceptEdits", "bypassPermissions", "default"]).optional().describe("Permission mode for Claude CLI in this workflow type"),
17
- noVerify: z.boolean().optional().describe("Skip pre-commit hooks (--no-verify) when committing during finish workflow"),
18
- startIde: z.boolean().default(true).describe("Launch IDE (code) when starting this workflow type"),
19
- startDevServer: z.boolean().default(true).describe("Launch development server when starting this workflow type"),
20
- startAiAgent: z.boolean().default(true).describe("Launch Claude AI agent when starting this workflow type"),
21
- startTerminal: z.boolean().default(false).describe("Launch terminal window without dev server when starting this workflow type")
22
- });
23
- var WorkflowsSettingsSchema = z.object({
24
- issue: WorkflowPermissionSchema.optional(),
25
- pr: WorkflowPermissionSchema.optional(),
26
- regular: WorkflowPermissionSchema.optional()
27
- }).optional();
28
- var CapabilitiesSettingsSchema = z.object({
29
- web: z.object({
30
- basePort: z.number().min(1, "Base port must be >= 1").max(65535, "Base port must be <= 65535").optional().describe("Base port for web workspace port calculations (default: 3000)")
31
- }).optional(),
32
- database: z.object({
33
- databaseUrlEnvVarName: z.string().min(1, "Database URL variable name cannot be empty").regex(/^[A-Z_][A-Z0-9_]*$/, "Must be valid env var name (uppercase, underscores)").optional().default("DATABASE_URL").describe("Name of environment variable for database connection URL")
34
- }).optional()
35
- }).optional();
36
- var NeonSettingsSchema = z.object({
37
- projectId: z.string().min(1).regex(/^[a-zA-Z0-9-]+$/, "Neon project ID must contain only letters, numbers, and hyphens").describe('Neon project ID found in your project URL (e.g., "fantastic-fox-3566354")'),
38
- parentBranch: z.string().min(1).describe("Branch from which new database branches are created")
39
- });
40
- var DatabaseProvidersSettingsSchema = z.object({
41
- neon: NeonSettingsSchema.optional().describe(
42
- "Neon database configuration. Requires Neon CLI installed and authenticated for database branching."
43
- )
44
- }).optional();
45
- var IloomSettingsSchema = z.object({
46
- mainBranch: z.string().min(1, "Settings 'mainBranch' cannot be empty").optional().describe("Name of the main/primary branch for the repository"),
47
- worktreePrefix: z.string().optional().refine(
48
- (val) => {
49
- if (val === void 0) return true;
50
- if (val === "") return true;
51
- const allowedChars = /^[a-zA-Z0-9\-_/]+$/;
52
- if (!allowedChars.test(val)) return false;
53
- if (/^[-_/]+$/.test(val)) return false;
54
- const segments = val.split("/");
55
- for (const segment of segments) {
56
- if (segment && /^[-_]+$/.test(segment)) {
57
- return false;
58
- }
59
- }
60
- return true;
61
- },
62
- {
63
- message: "worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories."
64
- }
65
- ).describe(
66
- "Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set."
67
- ),
68
- protectedBranches: z.array(z.string().min(1, "Protected branch name cannot be empty")).optional().describe('List of branches that cannot be deleted (defaults to [mainBranch, "main", "master", "develop"])'),
69
- workflows: WorkflowsSettingsSchema.describe("Per-workflow-type permission configurations"),
70
- agents: z.record(z.string(), AgentSettingsSchema).optional().nullable().describe(
71
- "Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-issue-reviewer (reviews code changes against requirements)"
72
- ),
73
- capabilities: CapabilitiesSettingsSchema.describe("Project capability configurations"),
74
- databaseProviders: DatabaseProvidersSettingsSchema.describe("Database provider configurations"),
75
- issueManagement: z.object({
76
- github: z.object({
77
- remote: z.string().min(1, "Remote name cannot be empty").describe("Git remote name to use for GitHub operations")
78
- }).optional()
79
- }).optional().describe("Issue management configuration"),
80
- mergeBehavior: z.object({
81
- mode: z.enum(["local", "github-pr"]).default("local"),
82
- remote: z.string().optional()
83
- }).optional().describe("Merge behavior configuration: local (merge locally) or github-pr (create PR)")
84
- });
85
- var SettingsManager = class {
86
- /**
87
- * Load settings from <PROJECT_ROOT>/.iloom/settings.json and settings.local.json
88
- * Merges settings.local.json over settings.json with priority
89
- * CLI overrides have highest priority if provided
90
- * Returns empty object if both files don't exist (not an error)
91
- */
92
- async loadSettings(projectRoot, cliOverrides) {
93
- const root = this.getProjectRoot(projectRoot);
94
- const baseSettings = await this.loadSettingsFile(root, "settings.json");
95
- const baseSettingsPath = path.join(root, ".iloom", "settings.json");
96
- logger.debug(`\u{1F4C4} Base settings from ${baseSettingsPath}:`, JSON.stringify(baseSettings, null, 2));
97
- const localSettings = await this.loadSettingsFile(root, "settings.local.json");
98
- const localSettingsPath = path.join(root, ".iloom", "settings.local.json");
99
- logger.debug(`\u{1F4C4} Local settings from ${localSettingsPath}:`, JSON.stringify(localSettings, null, 2));
100
- let merged = this.mergeSettings(baseSettings, localSettings);
101
- logger.debug("\u{1F504} After merging base + local settings:", JSON.stringify(merged, null, 2));
102
- if (cliOverrides && Object.keys(cliOverrides).length > 0) {
103
- logger.debug("\u2699\uFE0F CLI overrides to apply:", JSON.stringify(cliOverrides, null, 2));
104
- merged = this.mergeSettings(merged, cliOverrides);
105
- logger.debug("\u{1F504} After applying CLI overrides:", JSON.stringify(merged, null, 2));
106
- }
107
- try {
108
- const finalSettings = IloomSettingsSchema.parse(merged);
109
- this.logFinalConfiguration(finalSettings);
110
- return finalSettings;
111
- } catch (error) {
112
- if (error instanceof z.ZodError) {
113
- const errorMsg = this.formatAllZodErrors(error, "<merged settings>");
114
- if (cliOverrides && Object.keys(cliOverrides).length > 0) {
115
- throw new Error(`${errorMsg.message}
116
-
117
- Note: CLI overrides were applied. Check your --set arguments.`);
118
- }
119
- throw errorMsg;
120
- }
121
- throw error;
122
- }
123
- }
124
- /**
125
- * Log the final merged configuration for debugging
126
- */
127
- logFinalConfiguration(settings) {
128
- logger.debug("\u{1F4CB} Final merged configuration:", JSON.stringify(settings, null, 2));
129
- }
130
- /**
131
- * Load and parse a single settings file
132
- * Returns empty object if file doesn't exist (not an error)
133
- */
134
- async loadSettingsFile(projectRoot, filename) {
135
- const settingsPath = path.join(projectRoot, ".iloom", filename);
136
- try {
137
- const content = await readFile(settingsPath, "utf-8");
138
- let parsed;
139
- try {
140
- parsed = JSON.parse(content);
141
- } catch (error) {
142
- throw new Error(
143
- `Failed to parse settings file at ${settingsPath}: ${error instanceof Error ? error.message : "Invalid JSON"}`
144
- );
145
- }
146
- try {
147
- const validated = IloomSettingsSchema.strict().parse(parsed);
148
- return validated;
149
- } catch (error) {
150
- if (error instanceof z.ZodError) {
151
- const errorMsg = this.formatAllZodErrors(error, filename);
152
- throw errorMsg;
153
- }
154
- throw error;
155
- }
156
- } catch (error) {
157
- if (error.code === "ENOENT") {
158
- logger.debug(`No settings file found at ${settingsPath}, using defaults`);
159
- return {};
160
- }
161
- throw error;
162
- }
163
- }
164
- /**
165
- * Deep merge two settings objects with priority to override
166
- * Uses deepmerge library with array replacement strategy
167
- */
168
- mergeSettings(base, override) {
169
- return deepmerge(base, override, {
170
- // Replace arrays instead of concatenating them
171
- arrayMerge: (_destinationArray, sourceArray) => sourceArray
172
- });
173
- }
174
- /**
175
- * Format all Zod validation errors into a single error message
176
- */
177
- formatAllZodErrors(error, settingsPath) {
178
- const errorMessages = error.issues.map((issue) => {
179
- const path2 = issue.path.length > 0 ? issue.path.join(".") : "root";
180
- return ` - ${path2}: ${issue.message}`;
181
- });
182
- return new Error(
183
- `Settings validation failed at ${settingsPath}:
184
- ${errorMessages.join("\n")}`
185
- );
186
- }
187
- /**
188
- * Validate settings structure and model names using Zod schema
189
- * This method is kept for testing purposes but uses Zod internally
190
- * @internal - Only used in tests via bracket notation
191
- */
192
- // @ts-expect-error - Used in tests via bracket notation, TypeScript can't detect this usage
193
- validateSettings(settings) {
194
- try {
195
- IloomSettingsSchema.parse(settings);
196
- } catch (error) {
197
- if (error instanceof z.ZodError) {
198
- throw this.formatAllZodErrors(error, "<validation>");
199
- }
200
- throw error;
201
- }
202
- }
203
- /**
204
- * Get project root (defaults to process.cwd())
205
- */
206
- getProjectRoot(projectRoot) {
207
- return projectRoot ?? process.cwd();
208
- }
209
- /**
210
- * Get effective protected branches list with mainBranch always included
211
- *
212
- * This method provides a single source of truth for protected branches logic:
213
- * 1. Use configured protectedBranches if provided
214
- * 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']
215
- * 3. ALWAYS ensure mainBranch is included even if user configured custom list
216
- *
217
- * @param projectRoot - Optional project root directory (defaults to process.cwd())
218
- * @returns Array of protected branch names with mainBranch guaranteed to be included
219
- */
220
- async getProtectedBranches(projectRoot) {
221
- const settings = await this.loadSettings(projectRoot);
222
- const mainBranch = settings.mainBranch ?? "main";
223
- let protectedBranches;
224
- if (settings.protectedBranches) {
225
- protectedBranches = settings.protectedBranches.includes(mainBranch) ? settings.protectedBranches : [mainBranch, ...settings.protectedBranches];
226
- } else {
227
- protectedBranches = [mainBranch, "main", "master", "develop"];
228
- }
229
- return protectedBranches;
230
- }
231
- };
232
-
233
- export {
234
- AgentSettingsSchema,
235
- WorkflowPermissionSchema,
236
- WorkflowsSettingsSchema,
237
- CapabilitiesSettingsSchema,
238
- NeonSettingsSchema,
239
- DatabaseProvidersSettingsSchema,
240
- IloomSettingsSchema,
241
- SettingsManager
242
- };
243
- //# sourceMappingURL=chunk-T3KEIB4D.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/SettingsManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport path from 'path'\nimport { z } from 'zod'\nimport deepmerge from 'deepmerge'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Zod schema for agent settings\n */\nexport const AgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.optional()\n\t\t.describe('Claude model shorthand: sonnet, opus, or haiku'),\n\t// Future: could add other per-agent overrides\n})\n\n/**\n * Zod schema for workflow permission configuration\n */\nexport const WorkflowPermissionSchema = z.object({\n\tpermissionMode: z\n\t\t.enum(['plan', 'acceptEdits', 'bypassPermissions', 'default'])\n\t\t.optional()\n\t\t.describe('Permission mode for Claude CLI in this workflow type'),\n\tnoVerify: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Skip pre-commit hooks (--no-verify) when committing during finish workflow'),\n\tstartIde: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch Claude AI agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe('Launch terminal window without dev server when starting this workflow type'),\n})\n\n/**\n * Zod schema for workflows settings\n */\nexport const WorkflowsSettingsSchema = z\n\t.object({\n\t\tissue: WorkflowPermissionSchema.optional(),\n\t\tpr: WorkflowPermissionSchema.optional(),\n\t\tregular: WorkflowPermissionSchema.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for capabilities settings\n */\nexport const CapabilitiesSettingsSchema = z\n\t.object({\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t})\n\t\t\t.optional(),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.default('DATABASE_URL')\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for Neon database provider settings\n */\nexport const NeonSettingsSchema = z.object({\n\tprojectId: z\n\t\t.string()\n\t\t.min(1)\n\t\t.regex(/^[a-zA-Z0-9-]+$/, 'Neon project ID must contain only letters, numbers, and hyphens')\n\t\t.describe('Neon project ID found in your project URL (e.g., \"fantastic-fox-3566354\")'),\n\tparentBranch: z\n\t\t.string()\n\t\t.min(1)\n\t\t.describe('Branch from which new database branches are created'),\n})\n\n/**\n * Zod schema for database provider settings\n */\nexport const DatabaseProvidersSettingsSchema = z\n\t.object({\n\t\tneon: NeonSettingsSchema.optional().describe(\n\t\t\t'Neon database configuration. Requires Neon CLI installed and authenticated for database branching.',\n\t\t),\n\t})\n\t.optional()\n\n/**\n * Zod schema for iloom settings\n */\nexport const IloomSettingsSchema = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tworkflows: WorkflowsSettingsSchema.describe('Per-workflow-type permission configurations'),\n\tagents: z\n\t\t.record(z.string(), AgentSettingsSchema)\n\t\t.optional()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Per-agent configuration overrides. Available agents: ' +\n\t\t\t\t'iloom-issue-analyzer (analyzes issues), ' +\n\t\t\t\t'iloom-issue-planner (creates implementation plans), ' +\n\t\t\t\t'iloom-issue-analyze-and-plan (combined analysis and planning), ' +\n\t\t\t\t'iloom-issue-complexity-evaluator (evaluates complexity), ' +\n\t\t\t\t'iloom-issue-enhancer (enhances issue descriptions), ' +\n\t\t\t\t'iloom-issue-implementer (implements code changes), ' +\n\t\t\t\t'iloom-issue-reviewer (reviews code changes against requirements)',\n\t\t),\n\tcapabilities: CapabilitiesSettingsSchema.describe('Project capability configurations'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\tgithub: z\n\t\t\t\t.object({\n\t\t\t\t\tremote: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Remote name cannot be empty')\n\t\t\t\t\t\t.describe('Git remote name to use for GitHub operations'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Issue management configuration'),\n\tmergeBehavior: z\n\t\t.object({\n\t\t\tmode: z.enum(['local', 'github-pr']).default('local'),\n\t\t\tremote: z.string().optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally) or github-pr (create PR)'),\n})\n\n/**\n * TypeScript type for Neon settings derived from Zod schema\n */\nexport type NeonSettings = z.infer<typeof NeonSettingsSchema>\n\n/**\n * TypeScript type for database providers settings derived from Zod schema\n */\nexport type DatabaseProvidersSettings = z.infer<typeof DatabaseProvidersSettingsSchema>\n\n/**\n * TypeScript type for agent settings derived from Zod schema\n */\nexport type AgentSettings = z.infer<typeof AgentSettingsSchema>\n\n/**\n * TypeScript type for workflow permission configuration derived from Zod schema\n */\nexport type WorkflowPermission = z.infer<typeof WorkflowPermissionSchema>\n\n/**\n * TypeScript type for workflows settings derived from Zod schema\n */\nexport type WorkflowsSettings = z.infer<typeof WorkflowsSettingsSchema>\n\n/**\n * TypeScript type for capabilities settings derived from Zod schema\n */\nexport type CapabilitiesSettings = z.infer<typeof CapabilitiesSettingsSchema>\n\n/**\n * TypeScript type for iloom settings derived from Zod schema\n */\nexport type IloomSettings = z.infer<typeof IloomSettingsSchema>\n\n/**\n * Manages project-level settings from .iloom/settings.json\n */\nexport class SettingsManager {\n\t/**\n\t * Load settings from <PROJECT_ROOT>/.iloom/settings.json and settings.local.json\n\t * Merges settings.local.json over settings.json with priority\n\t * CLI overrides have highest priority if provided\n\t * Returns empty object if both files don't exist (not an error)\n\t */\n\tasync loadSettings(\n\t\tprojectRoot?: string,\n\t\tcliOverrides?: Partial<IloomSettings>,\n\t): Promise<IloomSettings> {\n\t\tconst root = this.getProjectRoot(projectRoot)\n\n\t\t// Load base settings from settings.json\n\t\tconst baseSettings = await this.loadSettingsFile(root, 'settings.json')\n\t\tconst baseSettingsPath = path.join(root, '.iloom', 'settings.json')\n\t\tlogger.debug(`📄 Base settings from ${baseSettingsPath}:`, JSON.stringify(baseSettings, null, 2))\n\n\t\t// Load local overrides from settings.local.json\n\t\tconst localSettings = await this.loadSettingsFile(root, 'settings.local.json')\n\t\tconst localSettingsPath = path.join(root, '.iloom', 'settings.local.json')\n\t\tlogger.debug(`📄 Local settings from ${localSettingsPath}:`, JSON.stringify(localSettings, null, 2))\n\n\t\t// Deep merge with priority: cliOverrides > localSettings > baseSettings\n\t\tlet merged = this.mergeSettings(baseSettings, localSettings)\n\t\tlogger.debug('🔄 After merging base + local settings:', JSON.stringify(merged, null, 2))\n\n\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\tlogger.debug('⚙️ CLI overrides to apply:', JSON.stringify(cliOverrides, null, 2))\n\t\t\tmerged = this.mergeSettings(merged, cliOverrides)\n\t\t\tlogger.debug('🔄 After applying CLI overrides:', JSON.stringify(merged, null, 2))\n\t\t}\n\n\t\t// Validate merged result\n\t\ttry {\n\t\t\tconst finalSettings = IloomSettingsSchema.parse(merged)\n\n\t\t\t// Debug: Log final merged configuration\n\t\t\tthis.logFinalConfiguration(finalSettings)\n\n\t\t\treturn finalSettings\n\t\t} catch (error) {\n\t\t\t// Show all Zod validation errors\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, '<merged settings>')\n\t\t\t\t// Enhance error message if CLI overrides were applied\n\t\t\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\t\t\tthrow new Error(`${errorMsg.message}\\n\\nNote: CLI overrides were applied. Check your --set arguments.`)\n\t\t\t\t}\n\t\t\t\tthrow errorMsg\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Log the final merged configuration for debugging\n\t */\n\tprivate logFinalConfiguration(settings: IloomSettings): void {\n\t\tlogger.debug('📋 Final merged configuration:', JSON.stringify(settings, null, 2))\n\t}\n\n\t/**\n\t * Load and parse a single settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t */\n\tprivate async loadSettingsFile(\n\t\tprojectRoot: string,\n\t\tfilename: string,\n\t): Promise<Partial<IloomSettings>> {\n\t\tconst settingsPath = path.join(projectRoot, '.iloom', filename)\n\n\t\ttry {\n\t\t\tconst content = await readFile(settingsPath, 'utf-8')\n\t\t\tlet parsed: unknown\n\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(content)\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to parse settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}`,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Validate individual file with strict mode to catch unknown keys\n\t\t\t// Note: Schema already has all fields as optional, so no need for .partial()\n\t\t\ttry {\n\t\t\t\tconst validated = IloomSettingsSchema.strict().parse(parsed)\n\t\t\t\treturn validated\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, filename)\n\t\t\t\t\tthrow errorMsg\n\t\t\t\t}\n\t\t\t\tthrow error\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// File not found is not an error - return empty settings\n\t\t\tif ((error as { code?: string }).code === 'ENOENT') {\n\t\t\t\tlogger.debug(`No settings file found at ${settingsPath}, using defaults`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Re-throw parsing errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Deep merge two settings objects with priority to override\n\t * Uses deepmerge library with array replacement strategy\n\t */\n\tprivate mergeSettings(\n\t\tbase: Partial<IloomSettings>,\n\t\toverride: Partial<IloomSettings>,\n\t): IloomSettings {\n\t\t// Use deepmerge with array replacement (not concatenation)\n\t\treturn deepmerge(base, override, {\n\t\t\t// Replace arrays instead of concatenating them\n\t\t\tarrayMerge: (_destinationArray, sourceArray) => sourceArray,\n\t\t}) as IloomSettings\n\t}\n\n\t/**\n\t * Format all Zod validation errors into a single error message\n\t */\n\tprivate formatAllZodErrors(error: z.ZodError, settingsPath: string): Error {\n\t\tconst errorMessages = error.issues.map(issue => {\n\t\t\tconst path = issue.path.length > 0 ? issue.path.join('.') : 'root'\n\t\t\treturn ` - ${path}: ${issue.message}`\n\t\t})\n\n\t\treturn new Error(\n\t\t\t`Settings validation failed at ${settingsPath}:\\n${errorMessages.join('\\n')}`,\n\t\t)\n\t}\n\n\t/**\n\t * Validate settings structure and model names using Zod schema\n\t * This method is kept for testing purposes but uses Zod internally\n\t * @internal - Only used in tests via bracket notation\n\t */\n\t// @ts-expect-error - Used in tests via bracket notation, TypeScript can't detect this usage\n\tprivate validateSettings(settings: IloomSettings): void {\n\t\ttry {\n\t\t\tIloomSettingsSchema.parse(settings)\n\t\t} catch (error) {\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tthrow this.formatAllZodErrors(error, '<validation>')\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Get project root (defaults to process.cwd())\n\t */\n\tprivate getProjectRoot(projectRoot?: string): string {\n\t\treturn projectRoot ?? process.cwd()\n\t}\n\n\t/**\n\t * Get effective protected branches list with mainBranch always included\n\t *\n\t * This method provides a single source of truth for protected branches logic:\n\t * 1. Use configured protectedBranches if provided\n\t * 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t * 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t *\n\t * @param projectRoot - Optional project root directory (defaults to process.cwd())\n\t * @returns Array of protected branch names with mainBranch guaranteed to be included\n\t */\n\tasync getProtectedBranches(projectRoot?: string): Promise<string[]> {\n\t\tconst settings = await this.loadSettings(projectRoot)\n\t\tconst mainBranch = settings.mainBranch ?? 'main'\n\n\t\t// Build protected branches list:\n\t\t// 1. Use configured protectedBranches if provided\n\t\t// 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t\t// 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t\tlet protectedBranches: string[]\n\t\tif (settings.protectedBranches) {\n\t\t\t// Use configured list but ensure mainBranch is always included\n\t\t\tprotectedBranches = settings.protectedBranches.includes(mainBranch)\n\t\t\t\t? settings.protectedBranches\n\t\t\t\t: [mainBranch, ...settings.protectedBranches]\n\t\t} else {\n\t\t\t// Use defaults with current mainBranch\n\t\t\tprotectedBranches = [mainBranch, 'main', 'master', 'develop']\n\t\t}\n\n\t\treturn protectedBranches\n\t}\n}\n"],"mappings":"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","names":["path"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types/github.ts","../src/lib/GitHubService.ts"],"sourcesContent":["// Core GitHub response types\nexport interface GitHubIssue {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' // GitHub GraphQL format\n\tlabels: { name: string }[]\n\tassignees: { login: string }[]\n\turl: string\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// Pull Request types\nexport interface GitHubPullRequest {\n\tnumber: number\n\ttitle: string\n\tbody: string\n\tstate: 'OPEN' | 'CLOSED' | 'MERGED'\n\theadRefName: string // source branch\n\tbaseRefName: string // target branch\n\turl: string\n\tisDraft: boolean\n\tmergeable: 'CONFLICTING' | 'MERGEABLE' | 'UNKNOWN'\n\tcreatedAt: string\n\tupdatedAt: string\n}\n\n// GitHub Projects types\nexport interface GitHubProject {\n\tnumber: number\n\tid: string\n\tname: string\n\tfields: ProjectField[]\n}\n\nexport interface ProjectField {\n\tid: string\n\tname: string\n\tdataType: 'SINGLE_SELECT' | 'TEXT' | 'NUMBER' | 'DATE'\n\toptions?: ProjectFieldOption[]\n}\n\nexport interface ProjectFieldOption {\n\tid: string\n\tname: string\n}\n\nexport interface ProjectItem {\n\tid: string\n\tcontent: {\n\t\ttype: 'Issue' | 'PullRequest' | 'DraftIssue'\n\t\tnumber: number\n\t}\n\tfieldValues: Record<string, unknown>\n}\n\n// Command result types\nexport interface GitHubCommandResult<T = unknown> {\n\tsuccess: boolean\n\tdata?: T\n\terror?: string\n\trateLimitRemaining?: number\n\trateLimitReset?: Date\n}\n\nexport interface GitHubAuthStatus {\n\thasAuth: boolean\n\tscopes: string[]\n\tusername?: string\n}\n\n// Input detection types\nexport interface GitHubInputDetection {\n\ttype: 'issue' | 'pr' | 'unknown'\n\tnumber: number | null\n\trawInput: string\n}\n\n// Branch name generation strategy interface\nexport interface BranchNameStrategy {\n\tgenerate(issueNumber: number, title: string): Promise<string>\n}\n\nexport interface BranchGenerationOptions {\n\tissueNumber: number\n\ttitle: string\n\tstrategy?: BranchNameStrategy\n}\n\n// Context and error types\nexport interface GitHubContext {\n\tissue?: GitHubIssue\n\tpullRequest?: GitHubPullRequest\n\tformattedContext: string\n}\n\nexport enum GitHubErrorCode {\n\tNOT_FOUND = 'NOT_FOUND',\n\tUNAUTHORIZED = 'UNAUTHORIZED',\n\tRATE_LIMITED = 'RATE_LIMITED',\n\tNETWORK_ERROR = 'NETWORK_ERROR',\n\tINVALID_STATE = 'INVALID_STATE',\n\tMISSING_SCOPE = 'MISSING_SCOPE',\n}\n\nexport class GitHubError extends Error {\n\tconstructor(\n\t\tpublic code: GitHubErrorCode,\n\t\tmessage: string,\n\t\tpublic details?: unknown\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'GitHubError'\n\t}\n}\n","import type { Issue, PullRequest } from '../types/index.js'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubInputDetection,\n\tBranchGenerationOptions,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { GitHubError, GitHubErrorCode } from '../types/github.js'\nimport {\n\texecuteGhCommand,\n\thasProjectScope,\n\tfetchGhIssue,\n\tfetchGhPR,\n\tfetchProjectList,\n\tfetchProjectItems,\n\tfetchProjectFields,\n\tupdateProjectItemField,\n\tcreateIssue,\n\tSimpleBranchNameStrategy,\n\tClaudeBranchNameStrategy,\n} from '../utils/github.js'\nimport { logger } from '../utils/logger.js'\nimport { promptConfirmation } from '../utils/prompt.js'\n\nexport class GitHubService {\n\tprivate defaultBranchNameStrategy: BranchNameStrategy\n\tprivate prompter: (message: string) => Promise<boolean>\n\n\tconstructor(options?: {\n\t\tbranchNameStrategy?: BranchNameStrategy\n\t\tuseClaude?: boolean\n\t\tclaudeModel?: string\n\t\tprompter?: (message: string) => Promise<boolean>\n\t}) {\n\t\t// Set up default strategy based on options\n\t\tif (options?.branchNameStrategy) {\n\t\t\tthis.defaultBranchNameStrategy = options.branchNameStrategy\n\t\t} else if (options?.useClaude !== false) {\n\t\t\tthis.defaultBranchNameStrategy = new ClaudeBranchNameStrategy(\n\t\t\t\toptions?.claudeModel\n\t\t\t)\n\t\t} else {\n\t\t\tthis.defaultBranchNameStrategy = new SimpleBranchNameStrategy()\n\t\t}\n\n\t\t// Set up prompter (use provided or default to promptConfirmation)\n\t\tthis.prompter = options?.prompter ?? promptConfirmation\n\t}\n\n\t// Input detection\n\tpublic async detectInputType(input: string, repo?: string): Promise<GitHubInputDetection> {\n\t\t// Pattern: #123 or just 123\n\t\tconst numberMatch = input.match(/^#?(\\d+)$/)\n\n\t\tif (!numberMatch?.[1]) {\n\t\t\treturn { type: 'unknown', number: null, rawInput: input }\n\t\t}\n\n\t\tconst number = parseInt(numberMatch[1], 10)\n\n\t\t// Try PR first (based on bash script logic at lines 500-533)\n\t\tlogger.debug('Checking if input is a PR', { number })\n\t\tconst pr = await this.isValidPR(number, repo)\n\t\tif (pr) {\n\t\t\treturn { type: 'pr', number, rawInput: input }\n\t\t}\n\n\t\t// Try issue next (lines 536-575 in bash)\n\t\tlogger.debug('Checking if input is an issue', { number })\n\t\tconst issue = await this.isValidIssue(number, repo)\n\t\tif (issue) {\n\t\t\treturn { type: 'issue', number, rawInput: input }\n\t\t}\n\n\t\t// Neither PR nor issue found\n\t\treturn { type: 'unknown', number: null, rawInput: input }\n\t}\n\n\t// Issue fetching with validation\n\tpublic async fetchIssue(issueNumber: number, repo?: string): Promise<Issue> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`Issue #${issueNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent issue validation (for detection phase)\n\tpublic async isValidIssue(issueNumber: number, repo?: string): Promise<Issue | false> {\n\t\ttry {\n\t\t\treturn await this.fetchIssueInternal(issueNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal issue fetching logic (shared by fetchIssue and isValidIssue)\n\tprivate async fetchIssueInternal(issueNumber: number, repo?: string): Promise<Issue> {\n\t\tconst ghIssue = await fetchGhIssue(issueNumber, repo)\n\t\treturn this.mapGitHubIssueToIssue(ghIssue)\n\t}\n\n\tpublic async validateIssueState(issue: Issue): Promise<void> {\n\t\tif (issue.state === 'closed') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`Issue #${issue.number} is closed. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t'User cancelled due to closed issue'\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// PR fetching with validation\n\tpublic async fetchPR(prNumber: number, repo?: string): Promise<PullRequest> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Only throw NOT_FOUND for actual \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.NOT_FOUND,\n\t\t\t\t\t`PR #${prNumber} not found`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t\t// Re-throw all other errors unchanged\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Silent PR validation (for detection phase)\n\tpublic async isValidPR(prNumber: number, repo?: string): Promise<PullRequest | false> {\n\t\ttry {\n\t\t\treturn await this.fetchPRInternal(prNumber, repo)\n\t\t} catch (error) {\n\t\t\t// Silently return false for \"not found\" errors\n\t\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('Could not resolve')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Internal PR fetching logic (shared by fetchPR and isValidPR)\n\tprivate async fetchPRInternal(prNumber: number, repo?: string): Promise<PullRequest> {\n\t\tconst ghPR = await fetchGhPR(prNumber, repo)\n\t\treturn this.mapGitHubPRToPullRequest(ghPR)\n\t}\n\n\tpublic async validatePRState(pr: PullRequest): Promise<void> {\n\t\tif (pr.state === 'closed' || pr.state === 'merged') {\n\t\t\tconst response = await this.promptUserConfirmation(\n\t\t\t\t`PR #${pr.number} is ${pr.state}. Continue anyway?`\n\t\t\t)\n\t\t\tif (!response) {\n\t\t\t\tthrow new GitHubError(\n\t\t\t\t\tGitHubErrorCode.INVALID_STATE,\n\t\t\t\t\t`User cancelled due to ${pr.state} PR`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Branch name generation using strategy pattern\n\tpublic async generateBranchName(\n\t\toptions: BranchGenerationOptions\n\t): Promise<string> {\n\t\tconst { issueNumber, title, strategy } = options\n\n\t\t// Use provided strategy or fall back to default\n\t\tconst nameStrategy = strategy ?? this.defaultBranchNameStrategy\n\n\t\tlogger.debug('Generating branch name', {\n\t\t\tissueNumber,\n\t\t\ttitle,\n\t\t\tstrategy: nameStrategy.constructor.name,\n\t\t})\n\n\t\treturn nameStrategy.generate(issueNumber, title)\n\t}\n\n\t// Issue creation\n\tpublic async createIssue(\n\t\ttitle: string,\n\t\tbody: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: number; url: string }> {\n\t\t// logger.info('Creating GitHub issue', { title })\n\t\treturn createIssue(title, body, { repo: repository, labels })\n\t}\n\n\tpublic async getIssueUrl(issueNumber: number, repo?: string): Promise<string> {\n\t\tlogger.debug('Fetching issue URL', { issueNumber, repo })\n\t\tconst issue = await fetchGhIssue(issueNumber, repo)\n\t\treturn issue.url\n\t}\n\n\t// GitHub Projects integration\n\tpublic async moveIssueToInProgress(issueNumber: number): Promise<void> {\n\t\t// Based on bash script lines 374-463\n\t\tlogger.info('Moving issue to In Progress in GitHub Projects', {\n\t\t\tissueNumber,\n\t\t})\n\n\t\t// Check for project scope\n\t\tif (!(await hasProjectScope())) {\n\t\t\tlogger.warn('Missing project scope in GitHub CLI auth')\n\t\t\tthrow new GitHubError(\n\t\t\t\tGitHubErrorCode.MISSING_SCOPE,\n\t\t\t\t'GitHub CLI lacks project scope. Run: gh auth refresh -s project'\n\t\t\t)\n\t\t}\n\n\t\t// Get repository info\n\t\tlet owner: string\n\t\ttry {\n\t\t\tconst repoInfo = await executeGhCommand<{\n\t\t\t\towner: { login: string }\n\t\t\t\tname: string\n\t\t\t}>(['repo', 'view', '--json', 'owner,name'])\n\t\t\towner = repoInfo.owner.login\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not determine repository info', { error })\n\t\t\treturn\n\t\t}\n\n\t\t// List all projects\n\t\tlet projects: GitHubProject[]\n\t\ttry {\n\t\t\tprojects = await fetchProjectList(owner)\n\t\t} catch (error) {\n\t\t\tlogger.warn('Could not fetch projects', { owner, error })\n\t\t\treturn\n\t\t}\n\n\t\tif (!projects.length) {\n\t\t\tlogger.warn('No projects found', { owner })\n\t\t\treturn\n\t\t}\n\n\t\t// Process each project (lines 404-460 in bash)\n\t\tfor (const project of projects) {\n\t\t\tawait this.updateIssueStatusInProject(project, issueNumber, owner)\n\t\t}\n\t}\n\n\tprivate async updateIssueStatusInProject(\n\t\tproject: GitHubProject,\n\t\tissueNumber: number,\n\t\towner: string\n\t): Promise<void> {\n\t\t// Check if issue is in project\n\t\tlet items: ProjectItem[]\n\t\ttry {\n\t\t\titems = await fetchProjectItems(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project items', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find issue item\n\t\tconst item = items.find(\n\t\t\t(i: ProjectItem) =>\n\t\t\t\ti.content.type === 'Issue' && i.content.number === issueNumber\n\t\t)\n\n\t\tif (!item) {\n\t\t\tlogger.debug('Issue not found in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\t// Fetch project fields separately (like bash script does)\n\t\tlet fieldsData: { fields: ProjectField[] }\n\t\ttry {\n\t\t\tfieldsData = await fetchProjectFields(project.number, owner)\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not fetch project fields', { project: project.number, error })\n\t\t\treturn\n\t\t}\n\n\t\t// Find Status field and In Progress option\n\t\tconst statusField = fieldsData.fields.find((f) => f.name === 'Status')\n\t\tif (!statusField) {\n\t\t\tlogger.debug('No Status field found in project', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\tconst inProgressOption = statusField.options?.find(\n\t\t\t(o: { id: string; name: string }) => o.name === 'In Progress' || o.name === 'In progress'\n\t\t)\n\n\t\tif (!inProgressOption) {\n\t\t\tlogger.debug('No In Progress option found in Status field', { projectNumber: project.number })\n\t\t\treturn\n\t\t}\n\n\t\t// Update status\n\t\ttry {\n\t\t\tawait updateProjectItemField(\n\t\t\t\titem.id,\n\t\t\t\tproject.id,\n\t\t\t\tstatusField.id,\n\t\t\t\tinProgressOption.id\n\t\t\t)\n\n\t\t\tlogger.info('Updated issue status in project', {\n\t\t\t\tissueNumber,\n\t\t\t\tprojectNumber: project.number,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tlogger.debug('Could not update project item', { item: item.id, error })\n\t\t}\n\t}\n\n\t// Utility methods\n\tpublic extractContext(entity: Issue | PullRequest): string {\n\t\tif ('branch' in entity) {\n\t\t\t// It's a PullRequest\n\t\t\treturn `Pull Request #${entity.number}: ${entity.title}\\nBranch: ${entity.branch}\\nState: ${entity.state}`\n\t\t} else {\n\t\t\t// It's an Issue\n\t\t\treturn `GitHub Issue #${entity.number}: ${entity.title}\\nState: ${entity.state}`\n\t\t}\n\t}\n\n\tprivate mapGitHubIssueToIssue(ghIssue: GitHubIssue): Issue {\n\t\treturn {\n\t\t\tnumber: ghIssue.number,\n\t\t\ttitle: ghIssue.title,\n\t\t\tbody: ghIssue.body,\n\t\t\tstate: ghIssue.state.toLowerCase() as 'open' | 'closed',\n\t\t\tlabels: ghIssue.labels.map((l) => l.name),\n\t\t\tassignees: ghIssue.assignees.map((a) => a.login),\n\t\t\turl: ghIssue.url,\n\t\t}\n\t}\n\n\tprivate mapGitHubPRToPullRequest(ghPR: GitHubPullRequest): PullRequest {\n\t\treturn {\n\t\t\tnumber: ghPR.number,\n\t\t\ttitle: ghPR.title,\n\t\t\tbody: ghPR.body,\n\t\t\tstate: ghPR.state.toLowerCase() as 'open' | 'closed' | 'merged',\n\t\t\tbranch: ghPR.headRefName,\n\t\t\tbaseBranch: ghPR.baseRefName,\n\t\t\turl: ghPR.url,\n\t\t\tisDraft: ghPR.isDraft,\n\t\t}\n\t}\n\n\tprivate async promptUserConfirmation(message: string): Promise<boolean> {\n\t\treturn this.prompter(message)\n\t}\n\n\t// Allow setting strategy at runtime for specific operations\n\tpublic setDefaultBranchNameStrategy(strategy: BranchNameStrategy): void {\n\t\tthis.defaultBranchNameStrategy = strategy\n\t}\n\n\t// Get current strategy for testing\n\tpublic getBranchNameStrategy(): BranchNameStrategy {\n\t\treturn this.defaultBranchNameStrategy\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/env.ts","../src/utils/port.ts"],"sourcesContent":["import dotenvFlow, { type DotenvFlowConfigOptions } from 'dotenv-flow'\nimport { logger } from './logger.js'\n\n/**\n * Parse .env file content into key-value map\n * Handles comments, empty lines, quoted/unquoted values, multiline values\n */\nexport function parseEnvFile(content: string): Map<string, string> {\n const envMap = new Map<string, string>()\n const lines = content.split('\\n')\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Skip empty lines and comments\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Find the first equals sign\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex === -1) {\n continue\n }\n\n const key = cleanLine.substring(0, equalsIndex).trim()\n let value = cleanLine.substring(equalsIndex + 1)\n\n // Handle quoted values\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.substring(1, value.length - 1)\n // Unescape quotes\n value = value.replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\")\n // Unescape newlines\n value = value.replace(/\\\\n/g, '\\n')\n }\n\n if (key) {\n envMap.set(key, value)\n }\n }\n\n return envMap\n}\n\n/**\n * Format environment variable as line for .env file\n * Always quotes values and escapes internal quotes\n */\nexport function formatEnvLine(key: string, value: string): string {\n // Escape quotes and newlines in the value\n const escapedValue = value\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n\n return `${key}=\"${escapedValue}\"`\n}\n\n/**\n * Validate environment variable name and value\n */\nexport function validateEnvVariable(\n key: string,\n _value?: string\n): { valid: boolean; error?: string } {\n if (!key || key.length === 0) {\n return {\n valid: false,\n error: 'Environment variable key cannot be empty',\n }\n }\n\n if (!isValidEnvKey(key)) {\n return {\n valid: false,\n error: `Invalid environment variable name: ${key}. Must start with a letter or underscore and contain only letters, numbers, and underscores.`,\n }\n }\n\n // Values can be any string, including empty\n return { valid: true }\n}\n\n/**\n * Normalize line endings for cross-platform compatibility\n */\nexport function normalizeLineEndings(content: string): string {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n}\n\n/**\n * Extract port from .env file if present\n */\nexport function extractPort(envContent: Map<string, string>): number | null {\n const portValue = envContent.get('PORT')\n if (!portValue) {\n return null\n }\n\n const port = parseInt(portValue, 10)\n if (isNaN(port)) {\n return null\n }\n\n return port\n}\n\n/**\n * Check if environment variable key is valid\n */\nexport function isValidEnvKey(key: string): boolean {\n if (!key || key.length === 0) {\n return false\n }\n\n // Must start with letter or underscore, followed by letters, numbers, or underscores\n const validKeyRegex = /^[A-Za-z_][A-Za-z0-9_]*$/\n return validKeyRegex.test(key)\n}\n\n/**\n * Load environment variables using dotenv-flow\n * Supports environment-specific files (.env.development, .env.production, etc.)\n * and local overrides (.env.local, .env.development.local)\n */\nexport function loadEnvIntoProcess(options?: {\n path?: string\n nodeEnv?: string\n defaultNodeEnv?: string\n}): { parsed?: Record<string, string>; error?: Error } {\n logger.debug('Loading environment variables with dotenv-flow', {\n options: {\n path: options?.path ?? 'current working directory',\n nodeEnv: options?.nodeEnv ?? 'not specified',\n defaultNodeEnv: options?.defaultNodeEnv ?? 'development (default)'\n }\n })\n\n const configOptions: Partial<DotenvFlowConfigOptions> = {\n silent: true, // Don't throw errors if .env files are missing\n }\n\n // Only add defined values to avoid TypeScript strict type issues\n if (options?.path !== undefined) {\n configOptions.path = options.path\n logger.debug(`Using custom path: ${options.path}`)\n }\n if (options?.nodeEnv !== undefined) {\n configOptions.node_env = options.nodeEnv\n logger.debug(`Using NODE_ENV: ${options.nodeEnv}`)\n }\n if (options?.defaultNodeEnv !== undefined) {\n configOptions.default_node_env = options.defaultNodeEnv\n logger.debug(`Using default NODE_ENV: ${options.defaultNodeEnv}`)\n } else {\n configOptions.default_node_env = 'development'\n logger.debug('Using default NODE_ENV: development')\n }\n\n logger.debug('dotenv-flow config options:', configOptions)\n\n const result = dotenvFlow.config(configOptions)\n\n const returnValue: { parsed?: Record<string, string>; error?: Error } = {}\n\n if (result.parsed) {\n returnValue.parsed = result.parsed as Record<string, string>\n const variableCount = Object.keys(result.parsed).length\n logger.debug(`Successfully loaded ${variableCount} environment variables`)\n } else {\n logger.debug('No environment variables were parsed')\n }\n\n if (result.error) {\n returnValue.error = result.error\n logger.debug('dotenv-flow returned an error', {\n error: result.error.message,\n name: result.error.name\n })\n } else {\n logger.debug('dotenv-flow completed without errors')\n }\n\n return returnValue\n}\n\n/**\n * Load environment variables for a specific workspace\n * Automatically determines environment based on NODE_ENV or defaults to development\n */\nexport function loadWorkspaceEnv(workspacePath: string): {\n parsed?: Record<string, string>\n error?: Error\n} {\n const nodeEnv = process.env.NODE_ENV ?? 'development'\n\n logger.debug('Loading workspace environment variables', {\n workspacePath,\n detectedNodeEnv: nodeEnv,\n processNodeEnv: process.env.NODE_ENV ?? 'not set'\n })\n\n return loadEnvIntoProcess({\n path: workspacePath,\n nodeEnv: nodeEnv,\n defaultNodeEnv: 'development'\n })\n}\n","import { createHash } from 'crypto'\n\n/**\n * Generate deterministic port offset from branch name using SHA256 hash\n * Range: 1-999 (matches existing random range for branches)\n *\n * @param branchName - Branch name to generate port offset from\n * @returns Port offset in range [1, 999]\n * @throws Error if branchName is empty\n */\nexport function generatePortOffsetFromBranchName(branchName: string): number {\n\t// Validate input\n\tif (!branchName || branchName.trim().length === 0) {\n\t\tthrow new Error('Branch name cannot be empty')\n\t}\n\n\t// Generate SHA256 hash of branch name (same pattern as color.ts)\n\tconst hash = createHash('sha256').update(branchName).digest('hex')\n\n\t// Take first 8 hex characters and convert to port offset (1-999)\n\tconst hashPrefix = hash.slice(0, 8)\n\tconst hashAsInt = parseInt(hashPrefix, 16)\n\tconst portOffset = (hashAsInt % 999) + 1 // +1 ensures range is 1-999, not 0-998\n\n\treturn portOffset\n}\n\n/**\n * Calculate deterministic port for branch-based workspace\n *\n * @param branchName - Branch name\n * @param basePort - Base port (default: 3000)\n * @returns Port number\n * @throws Error if calculated port exceeds 65535 or branchName is empty\n */\nexport function calculatePortForBranch(branchName: string, basePort: number = 3000): number {\n\tconst offset = generatePortOffsetFromBranchName(branchName)\n\tconst port = basePort + offset\n\n\t// Validate port range (same as EnvironmentManager.calculatePort)\n\tif (port > 65535) {\n\t\tthrow new Error(\n\t\t\t`Calculated port ${port} exceeds maximum (65535). Use a lower base port (current: ${basePort}).`\n\t\t)\n\t}\n\n\treturn port\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/cleanup.ts"],"sourcesContent":["import { logger } from '../utils/logger.js'\nimport { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { ResourceCleanup } from '../lib/ResourceCleanup.js'\nimport { ProcessManager } from '../lib/process/ProcessManager.js'\nimport { DatabaseManager } from '../lib/DatabaseManager.js'\nimport { EnvironmentManager } from '../lib/EnvironmentManager.js'\nimport { CLIIsolationManager } from '../lib/CLIIsolationManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { promptConfirmation } from '../utils/prompt.js'\nimport { IdentifierParser } from '../utils/IdentifierParser.js'\nimport { loadEnvIntoProcess } from '../utils/env.js'\nimport { createNeonProviderFromSettings } from '../utils/neon-helpers.js'\nimport type { CleanupOptions } from '../types/index.js'\nimport type { CleanupResult } from '../types/cleanup.js'\nimport type { ParsedInput } from './start.js'\n\n/**\n * Input structure for CleanupCommand.execute()\n */\nexport interface CleanupCommandInput {\n identifier?: string\n options: CleanupOptions\n}\n\n/**\n * Parsed and validated cleanup command input\n * Mode determines which cleanup operation to perform\n */\nexport interface ParsedCleanupInput {\n mode: 'list' | 'single' | 'issue' | 'all'\n identifier?: string\n issueNumber?: number\n branchName?: string\n originalInput?: string\n options: CleanupOptions\n}\n\n/**\n * Manages cleanup command execution with option parsing and validation\n * Follows the command pattern established by StartCommand\n *\n * This implementation handles ONLY parsing, validation, and mode determination.\n * Actual cleanup operations are deferred to subsequent sub-issues.\n */\nexport class CleanupCommand {\n private readonly gitWorktreeManager: GitWorktreeManager\n private resourceCleanup?: ResourceCleanup\n private readonly identifierParser: IdentifierParser\n\n constructor(\n gitWorktreeManager?: GitWorktreeManager,\n resourceCleanup?: ResourceCleanup\n ) {\n // Load environment variables first\n const envResult = loadEnvIntoProcess()\n if (envResult.error) {\n logger.debug(`Environment loading warning: ${envResult.error.message}`)\n }\n if (envResult.parsed) {\n logger.debug(`Loaded ${Object.keys(envResult.parsed).length} environment variables`)\n }\n\n this.gitWorktreeManager = gitWorktreeManager ?? new GitWorktreeManager()\n\n // Initialize ResourceCleanup with DatabaseManager and CLIIsolationManager\n // ResourceCleanup will be initialized lazily with proper configuration\n if (resourceCleanup) {\n this.resourceCleanup = resourceCleanup\n }\n\n // Initialize IdentifierParser for pattern-based detection\n this.identifierParser = new IdentifierParser(this.gitWorktreeManager)\n }\n\n /**\n * Lazy initialization of ResourceCleanup with properly configured DatabaseManager\n */\n private async ensureResourceCleanup(): Promise<void> {\n if (this.resourceCleanup) {\n return\n }\n\n const settingsManager = new SettingsManager()\n const settings = await settingsManager.loadSettings()\n const databaseUrlEnvVarName = settings.capabilities?.database?.databaseUrlEnvVarName ?? 'DATABASE_URL'\n\n const environmentManager = new EnvironmentManager()\n const neonProvider = createNeonProviderFromSettings(settings)\n const databaseManager = new DatabaseManager(neonProvider, environmentManager, databaseUrlEnvVarName)\n const cliIsolationManager = new CLIIsolationManager()\n\n this.resourceCleanup = new ResourceCleanup(\n this.gitWorktreeManager,\n new ProcessManager(),\n databaseManager,\n cliIsolationManager\n )\n }\n\n /**\n * Main entry point for the cleanup command\n * Parses input, validates options, and determines operation mode\n */\n public async execute(input: CleanupCommandInput): Promise<void> {\n try {\n // Step 1: Parse input and determine mode\n const parsed = this.parseInput(input)\n\n // Step 2: Validate option combinations\n this.validateInput(parsed)\n\n // Step 3: Execute based on mode\n logger.info(`Cleanup mode: ${parsed.mode}`)\n\n if (parsed.mode === 'single') {\n await this.executeSingleCleanup(parsed)\n } else if (parsed.mode === 'list') {\n logger.info('Would list all worktrees') // TODO: Implement in Sub-issue #2\n logger.success('Command parsing and validation successful')\n } else if (parsed.mode === 'all') {\n logger.info('Would remove all worktrees') // TODO: Implement in Sub-issue #5\n logger.success('Command parsing and validation successful')\n } else if (parsed.mode === 'issue') {\n await this.executeIssueCleanup(parsed)\n }\n } catch (error) {\n if (error instanceof Error) {\n logger.error(`${error.message}`)\n } else {\n logger.error('An unknown error occurred')\n }\n throw error\n }\n }\n\n /**\n * Parse input to determine cleanup mode and extract relevant data\n * Implements auto-detection: numeric input = issue number, non-numeric = branch name\n *\n * @private\n */\n private parseInput(input: CleanupCommandInput): ParsedCleanupInput {\n const { identifier, options } = input\n\n // Trim identifier if present\n const trimmedIdentifier = identifier?.trim() ?? undefined\n\n // Mode: List (takes priority - it's informational only)\n if (options.list) {\n const result: ParsedCleanupInput = {\n mode: 'list',\n options\n }\n if (trimmedIdentifier) {\n result.identifier = trimmedIdentifier\n }\n return result\n }\n\n // Mode: All (remove everything)\n if (options.all) {\n const result: ParsedCleanupInput = {\n mode: 'all',\n options\n }\n if (trimmedIdentifier) {\n result.identifier = trimmedIdentifier\n }\n if (options.issue !== undefined) {\n result.issueNumber = options.issue\n }\n return result\n }\n\n // Mode: Explicit issue number via --issue flag\n if (options.issue !== undefined) {\n // Need to determine if identifier is branch or numeric to set branchName\n if (trimmedIdentifier) {\n const numericPattern = /^[0-9]+$/\n if (!numericPattern.test(trimmedIdentifier)) {\n // Identifier is a branch name with explicit --issue flag\n return {\n mode: 'issue',\n issueNumber: options.issue,\n branchName: trimmedIdentifier,\n identifier: trimmedIdentifier,\n originalInput: trimmedIdentifier,\n options\n }\n }\n }\n const result: ParsedCleanupInput = {\n mode: 'issue',\n issueNumber: options.issue,\n options\n }\n if (trimmedIdentifier) {\n result.identifier = trimmedIdentifier\n }\n return result\n }\n\n // Mode: Auto-detect from identifier\n if (!trimmedIdentifier) {\n throw new Error('Missing required argument: identifier. Use --all to remove all worktrees or --list to list them.')\n }\n\n // Auto-detection: Check if identifier is purely numeric\n // Pattern from bash script line 364: ^[0-9]+$\n const numericPattern = /^[0-9]+$/\n if (numericPattern.test(trimmedIdentifier)) {\n // Numeric input = issue number\n return {\n mode: 'issue',\n issueNumber: parseInt(trimmedIdentifier, 10),\n identifier: trimmedIdentifier,\n originalInput: trimmedIdentifier,\n options\n }\n } else {\n // Non-numeric = branch name\n return {\n mode: 'single',\n branchName: trimmedIdentifier,\n identifier: trimmedIdentifier,\n originalInput: trimmedIdentifier,\n options\n }\n }\n }\n\n /**\n * Validate parsed input for option conflicts\n * Throws descriptive errors for invalid option combinations\n *\n * @private\n */\n private validateInput(parsed: ParsedCleanupInput): void {\n const { mode, options, branchName } = parsed\n\n // Conflict: --list is informational only, incompatible with destructive operations\n if (mode === 'list') {\n if (options.all) {\n throw new Error('Cannot use --list with --all (list is informational only)')\n }\n if (options.issue !== undefined) {\n throw new Error('Cannot use --list with --issue (list is informational only)')\n }\n if (parsed.identifier) {\n throw new Error('Cannot use --list with a specific identifier (list shows all worktrees)')\n }\n }\n\n // Conflict: --all removes everything, can't combine with specific identifier or --issue\n if (mode === 'all') {\n if (parsed.identifier) {\n throw new Error('Cannot use --all with a specific identifier. Use one or the other.')\n }\n if (parsed.issueNumber !== undefined) {\n throw new Error('Cannot use --all with a specific identifier. Use one or the other.')\n }\n }\n\n // Conflict: explicit --issue flag with branch name identifier\n // (This prevents confusion when user provides both)\n if (options.issue !== undefined && branchName) {\n throw new Error('Cannot use --issue flag with branch name identifier. Use numeric identifier or --issue flag alone.')\n }\n\n // Note: --force and --dry-run are compatible with all modes (no conflicts)\n }\n\n /**\n * Execute cleanup for single worktree\n * Implements two-stage confirmation: worktree removal, then branch deletion\n * Uses IdentifierParser for pattern-based detection without GitHub API calls\n */\n private async executeSingleCleanup(parsed: ParsedCleanupInput): Promise<void> {\n const identifier = parsed.branchName ?? parsed.identifier ?? ''\n if (!identifier) {\n throw new Error('No identifier found for cleanup')\n }\n const { force, dryRun } = parsed.options\n\n // Step 1: Parse identifier using pattern-based detection\n const parsedInput: ParsedInput = await this.identifierParser.parseForPatternDetection(identifier)\n\n // Step 2: Display worktree details\n logger.info(`Preparing to cleanup worktree: ${identifier}`)\n\n // Step 3: Confirmation - worktree removal\n if (!force) {\n const confirmWorktree = await promptConfirmation('Remove this worktree?', true)\n if (!confirmWorktree) {\n logger.info('Cleanup cancelled')\n return\n }\n }\n\n // Step 4: Execute worktree cleanup (includes safety validation)\n // With --force, delete branch automatically; otherwise handle separately\n await this.ensureResourceCleanup()\n if (!this.resourceCleanup) {\n throw new Error('Failed to initialize ResourceCleanup')\n }\n const cleanupResult = await this.resourceCleanup.cleanupWorktree(parsedInput, {\n dryRun: dryRun ?? false,\n force: force ?? false,\n deleteBranch: force ?? false, // Delete branch immediately if --force, otherwise prompt later\n keepDatabase: false,\n })\n\n // Step 5: Report cleanup results\n this.reportCleanupResults(cleanupResult)\n\n // Step 6: Second confirmation - branch deletion (only if not forced and worktree cleanup succeeded)\n if (cleanupResult.success && !force && cleanupResult.branchName) {\n const confirmBranch = await promptConfirmation('Also delete the git branch?', true)\n if (confirmBranch) {\n await this.deleteBranchForCleanup(cleanupResult.branchName, { force: force ?? false, dryRun: dryRun ?? false })\n }\n }\n\n // Final success message\n if (cleanupResult.success) {\n logger.success('Cleanup completed successfully')\n } else {\n logger.warn('Cleanup completed with errors - see details above')\n }\n }\n\n /**\n * Delete branch as part of cleanup operation\n */\n private async deleteBranchForCleanup(\n branchName: string,\n options: { force?: boolean; dryRun?: boolean }\n ): Promise<void> {\n try {\n await this.ensureResourceCleanup()\n if (!this.resourceCleanup) {\n throw new Error('Failed to initialize ResourceCleanup')\n }\n await this.resourceCleanup.deleteBranch(branchName, options)\n logger.success(`Branch deleted: ${branchName}`)\n } catch (error) {\n if (error instanceof Error) {\n logger.error(`Failed to delete branch: ${error.message}`)\n }\n // Don't throw - branch deletion is optional/secondary operation\n }\n }\n\n /**\n * Report cleanup operation results to user\n */\n private reportCleanupResults(result: CleanupResult): void {\n logger.info('Cleanup operations:')\n\n result.operations.forEach(op => {\n const status = op.success ? '✓' : '✗'\n const message = op.error ? `${op.message}: ${op.error}` : op.message\n\n if (op.success) {\n logger.info(` ${status} ${message}`)\n } else {\n logger.error(` ${status} ${message}`)\n }\n })\n\n if (result.errors.length > 0) {\n logger.warn(`${result.errors.length} error(s) occurred during cleanup`)\n }\n }\n\n /**\n * Execute cleanup for all worktrees associated with an issue or PR number\n * Searches for worktrees by their path patterns (e.g., issue-25, pr-25, 25-feature, _pr_25)\n * Implements bash cleanup-worktree.sh remove_worktrees_by_issue() (lines 157-242)\n */\n private async executeIssueCleanup(parsed: ParsedCleanupInput): Promise<void> {\n const issueNumber = parsed.issueNumber\n if (issueNumber === undefined) {\n throw new Error('No issue/PR number provided for cleanup')\n }\n\n const { force, dryRun } = parsed.options\n\n logger.info(`Finding worktrees related to GitHub issue/PR #${issueNumber}...`)\n\n // Step 1: Get all worktrees and filter by path pattern\n const worktrees = await this.gitWorktreeManager.listWorktrees()\n const matchingWorktrees = worktrees.filter(wt => {\n const path = wt.path.toLowerCase()\n const numStr = String(issueNumber)\n\n // Check if path contains the number with proper word boundaries\n // Matches: issue-25, pr-25, 25-feature, _pr_25, etc.\n // Does NOT match: issue-250, 125-feature (where 25 is part of a larger number)\n const pattern = new RegExp(`(?<!\\\\d)${numStr}(?!\\\\d)`)\n return pattern.test(path)\n })\n\n if (matchingWorktrees.length === 0) {\n logger.warn(`No worktrees found for GitHub issue/PR #${issueNumber}`)\n logger.info(`Searched for worktree paths containing: ${issueNumber}, _pr_${issueNumber}, issue-${issueNumber}, etc.`)\n return\n }\n\n // Step 2: Build targets list from matching worktrees\n const targets: Array<{ branchName: string; hasWorktree: boolean; worktreePath?: string }> =\n matchingWorktrees.map(wt => ({\n branchName: wt.branch,\n hasWorktree: true,\n worktreePath: wt.path\n }))\n\n // Step 3: Display preview\n logger.info(`Found ${targets.length} worktree(s) related to issue/PR #${issueNumber}:`)\n for (const target of targets) {\n logger.info(` 🌿 ${target.branchName} (${target.worktreePath})`)\n }\n\n // Step 4: Batch confirmation (unless --force)\n if (!force) {\n const confirmCleanup = await promptConfirmation(\n `Remove ${targets.length} worktree(s)?`,\n true\n )\n if (!confirmCleanup) {\n logger.info('Cleanup cancelled')\n return\n }\n }\n\n // Step 5: Process each target sequentially\n let worktreesRemoved = 0\n let branchesDeleted = 0\n const databaseBranchesDeletedList: string[] = []\n let failed = 0\n\n for (const target of targets) {\n logger.info(`Processing worktree: ${target.branchName}`)\n\n // Cleanup worktree using ResourceCleanup with ParsedInput\n try {\n // Parse the branch name using IdentifierParser\n const parsedInput: ParsedInput = await this.identifierParser.parseForPatternDetection(target.branchName)\n\n await this.ensureResourceCleanup()\n if (!this.resourceCleanup) {\n throw new Error('Failed to initialize ResourceCleanup')\n }\n const result = await this.resourceCleanup.cleanupWorktree(parsedInput, {\n dryRun: dryRun ?? false,\n force: force ?? false,\n deleteBranch: false, // Handle branch deletion separately\n keepDatabase: false\n })\n\n if (result.success) {\n worktreesRemoved++\n logger.success(` Worktree removed: ${target.branchName}`)\n\n // Check if database branch was actually deleted (use explicit deleted field)\n const dbOperation = result.operations.find(op => op.type === 'database')\n if (dbOperation?.deleted) {\n // Get branch name from result or use the target branch name\n const deletedBranchName = target.branchName\n databaseBranchesDeletedList.push(deletedBranchName)\n }\n } else {\n failed++\n logger.error(` Failed to remove worktree: ${target.branchName}`)\n }\n } catch (error) {\n failed++\n const errMsg = error instanceof Error ? error.message : 'Unknown error'\n logger.error(` Failed to remove worktree: ${errMsg}`)\n continue // Continue with next worktree even if this one failed\n }\n\n // Step 6: Delete branch\n try {\n await this.ensureResourceCleanup()\n if (!this.resourceCleanup) {\n throw new Error('Failed to initialize ResourceCleanup')\n }\n await this.resourceCleanup.deleteBranch(target.branchName, {\n force: force ?? false,\n dryRun: dryRun ?? false\n })\n branchesDeleted++\n logger.success(` Branch deleted: ${target.branchName}`)\n } catch (error) {\n // Don't count unmerged branch as failure - it's a safety feature\n const errMsg = error instanceof Error ? error.message : String(error)\n if (errMsg.includes('not fully merged')) {\n logger.warn(` Branch not fully merged, skipping deletion`)\n logger.warn(` Use --force to delete anyway`)\n } else {\n // Other errors are real failures\n logger.error(` Failed to delete branch: ${errMsg}`)\n }\n }\n }\n\n // Step 7: Report statistics\n logger.success(`Completed cleanup for issue/PR #${issueNumber}:`)\n logger.info(` 📁 Worktrees removed: ${worktreesRemoved}`)\n logger.info(` 🌿 Branches deleted: ${branchesDeleted}`)\n if (databaseBranchesDeletedList.length > 0) {\n // Display branch names in the format requested\n logger.info(` 🗂️ Database branches deleted: ${databaseBranchesDeletedList.join(', ')}`)\n }\n if (failed > 0) {\n logger.warn(` ❌ Failed operations: ${failed}`)\n }\n }\n}\n"],"mappings":"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","names":["numericPattern"]}
@@ -1,176 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- generateGitHubCommentMcpConfig
4
- } from "./chunk-CVLAZRNB.js";
5
- import {
6
- AgentManager
7
- } from "./chunk-OC4H6HJD.js";
8
- import {
9
- openBrowser
10
- } from "./chunk-YETJNRQM.js";
11
- import {
12
- getConfiguredRepoFromSettings,
13
- hasMultipleRemotes
14
- } from "./chunk-DJUGYNQE.js";
15
- import {
16
- GitHubService
17
- } from "./chunk-TS6DL67T.js";
18
- import "./chunk-SWCRXDZC.js";
19
- import {
20
- launchClaude
21
- } from "./chunk-MFU53H6J.js";
22
- import {
23
- SettingsManager
24
- } from "./chunk-T3KEIB4D.js";
25
- import {
26
- waitForKeypress
27
- } from "./chunk-JNKJ7NJV.js";
28
- import {
29
- logger
30
- } from "./chunk-GEHQXLEI.js";
31
-
32
- // src/commands/enhance.ts
33
- var EnhanceCommand = class {
34
- constructor(gitHubService, agentManager, settingsManager) {
35
- this.gitHubService = gitHubService ?? new GitHubService();
36
- this.agentManager = agentManager ?? new AgentManager();
37
- this.settingsManager = settingsManager ?? new SettingsManager();
38
- }
39
- /**
40
- * Execute the enhance command workflow:
41
- * 1. Validate issue number
42
- * 2. Fetch issue to verify it exists
43
- * 3. Load agent configurations
44
- * 4. Invoke Claude CLI with enhancer agent
45
- * 5. Parse response to determine outcome
46
- * 6. Handle browser interaction based on outcome
47
- */
48
- async execute(input) {
49
- const { issueNumber, options } = input;
50
- const { author } = options;
51
- const settings = await this.settingsManager.loadSettings();
52
- let repo;
53
- const multipleRemotes = await hasMultipleRemotes();
54
- if (multipleRemotes) {
55
- repo = await getConfiguredRepoFromSettings(settings);
56
- logger.info(`Using GitHub repository: ${repo}`);
57
- }
58
- this.validateIssueNumber(issueNumber);
59
- logger.info(`Fetching issue #${issueNumber}...`);
60
- const issue = await this.gitHubService.fetchIssue(issueNumber, repo);
61
- logger.debug("Issue fetched successfully", { number: issue.number, title: issue.title });
62
- logger.debug("Loading agent configurations...");
63
- const loadedAgents = await this.agentManager.loadAgents(settings);
64
- const agents = this.agentManager.formatForCli(loadedAgents);
65
- let mcpConfig;
66
- let allowedTools;
67
- let disallowedTools;
68
- try {
69
- mcpConfig = await generateGitHubCommentMcpConfig("issue", repo);
70
- logger.debug("Generated MCP configuration for GitHub comment broker");
71
- allowedTools = [
72
- "mcp__github_comment__create_comment",
73
- "mcp__github_comment__update_comment"
74
- ];
75
- disallowedTools = ["Bash(gh api:*)"];
76
- logger.debug("Configured tool filtering for issue workflow", { allowedTools, disallowedTools });
77
- } catch (error) {
78
- logger.warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : "Unknown error"}`);
79
- }
80
- logger.info("Invoking enhancer agent. This may take a moment...");
81
- const prompt = this.constructPrompt(issueNumber, author);
82
- const response = await launchClaude(prompt, {
83
- headless: true,
84
- model: "sonnet",
85
- agents,
86
- ...mcpConfig && { mcpConfig },
87
- ...allowedTools && { allowedTools },
88
- ...disallowedTools && { disallowedTools }
89
- });
90
- const result = this.parseEnhancerResponse(response);
91
- if (!result.enhanced) {
92
- logger.success("Issue already has thorough description. No enhancement needed.");
93
- return;
94
- }
95
- logger.success(`Issue #${issueNumber} enhanced successfully!`);
96
- logger.info(`Enhanced specification available at: ${result.url}`);
97
- if (!options.noBrowser && result.url) {
98
- await this.promptAndOpenBrowser(result.url);
99
- }
100
- }
101
- /**
102
- * Validate that issue number is a valid positive integer
103
- */
104
- validateIssueNumber(issueNumber) {
105
- if (issueNumber === void 0 || issueNumber === null) {
106
- throw new Error("Issue number is required");
107
- }
108
- if (typeof issueNumber !== "number" || Number.isNaN(issueNumber) || issueNumber <= 0 || !Number.isInteger(issueNumber)) {
109
- throw new Error("Issue number must be a valid positive integer");
110
- }
111
- }
112
- /**
113
- * Construct the prompt for the orchestrating Claude instance.
114
- * This prompt is very clear about expected output format to ensure reliable parsing.
115
- */
116
- constructPrompt(issueNumber, author) {
117
- const authorInstruction = author ? `
118
- IMPORTANT: When you create your analysis comment, tag @${author} in the "Questions for Reporter" section if you have questions.
119
- ` : "";
120
- return `Execute @agent-iloom-issue-enhancer ${issueNumber}${authorInstruction}
121
-
122
- ## OUTPUT REQUIREMENTS
123
- * If the issue was not enhanced, return ONLY: "No enhancement needed"
124
- * If the issue WAS enhanced, return ONLY: <FULL URL OF THE COMMENT INCLUDING COMMENT ID>
125
- * If you encounter permission/authentication/access errors, return ONLY: "Permission denied: <specific error description>"
126
- * IMPORTANT: Return ONLY one of the above - DO NOT include commentary such as "I created a comment at <URL>" or "I examined the issue and found no enhancement was necessary"
127
- * CONTEXT: Your output is going to be parsed programmatically, so adherence to the output requirements is CRITICAL.`;
128
- }
129
- /**
130
- * Parse the response from the enhancer agent.
131
- * Returns either { enhanced: false } or { enhanced: true, url: "..." }
132
- * Throws specific errors for permission issues.
133
- */
134
- parseEnhancerResponse(response) {
135
- if (!response || typeof response !== "string") {
136
- throw new Error("No response from enhancer agent");
137
- }
138
- const trimmed = response.trim();
139
- logger.debug(`RESPONSE FROM ENHANCER AGENT: '${trimmed}'`);
140
- if (trimmed.toLowerCase().startsWith("permission denied:")) {
141
- const errorMessage = trimmed.substring("permission denied:".length).trim();
142
- throw new Error(`Permission denied: ${errorMessage}`);
143
- }
144
- if (trimmed.toLowerCase().includes("no enhancement needed")) {
145
- return { enhanced: false };
146
- }
147
- const urlPattern = /https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+#issuecomment-\d+/;
148
- const match = trimmed.match(urlPattern);
149
- if (match) {
150
- return { enhanced: true, url: match[0] };
151
- }
152
- throw new Error(`Unexpected response from enhancer agent: ${trimmed}`);
153
- }
154
- /**
155
- * Prompt user and open browser to view enhanced issue.
156
- * Matches the pattern from the issue specification.
157
- */
158
- async promptAndOpenBrowser(commentUrl) {
159
- try {
160
- const key = await waitForKeypress(
161
- "Press q to quit or any other key to view the enhanced issue in a web browser..."
162
- );
163
- if (key.toLowerCase() === "q") {
164
- logger.info("Skipping browser opening");
165
- return;
166
- }
167
- await openBrowser(commentUrl);
168
- } catch (error) {
169
- logger.warn(`Failed to open browser: ${error instanceof Error ? error.message : "Unknown error"}`);
170
- }
171
- }
172
- };
173
- export {
174
- EnhanceCommand
175
- };
176
- //# sourceMappingURL=enhance-MNA4ZGXW.js.map