@iloom/cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -882
- package/dist/{BranchNamingService-3OQPRSWT.js → BranchNamingService-JYO746H7.js} +2 -2
- package/dist/ClaudeContextManager-5WPRJIIW.js +15 -0
- package/dist/ClaudeService-Z4KA7QOW.js +14 -0
- package/dist/{GitHubService-EBOETDIW.js → GitHubService-UAMH7DMF.js} +2 -2
- package/dist/{LoomLauncher-FLEMBCSQ.js → LoomLauncher-FVZECY3C.js} +24 -13
- package/dist/LoomLauncher-FVZECY3C.js.map +1 -0
- package/dist/README.md +207 -882
- package/dist/{SettingsManager-WHHFGSL7.js → SettingsManager-MTVX57WR.js} +2 -2
- package/dist/{chunk-MAVL6PJF.js → chunk-5NTD4MCZ.js} +23 -7
- package/dist/chunk-5NTD4MCZ.js.map +1 -0
- package/dist/{chunk-FIAT22G7.js → chunk-A2P7NZTB.js} +7 -7
- package/dist/{chunk-C5QCTEQK.js → chunk-C7ASXK6J.js} +2 -2
- package/dist/{chunk-G2IEYOLQ.js → chunk-F4ENT6AC.js} +16 -1
- package/dist/chunk-F4ENT6AC.js.map +1 -0
- package/dist/{chunk-AWOFAD5O.js → chunk-FOV7RRQ2.js} +8 -8
- package/dist/{chunk-RO26VS3W.js → chunk-FXMLNKLT.js} +2 -2
- package/dist/{chunk-47KSHUCR.js → chunk-GOG3ZB7O.js} +16 -10
- package/dist/chunk-GOG3ZB7O.js.map +1 -0
- package/dist/{chunk-JQFO7QQN.js → chunk-GVICXJHW.js} +2 -2
- package/dist/{chunk-JQFO7QQN.js.map → chunk-GVICXJHW.js.map} +1 -1
- package/dist/{chunk-5EF7Z346.js → chunk-KG343GSG.js} +93 -48
- package/dist/chunk-KG343GSG.js.map +1 -0
- package/dist/{chunk-4HHRTA7Q.js → chunk-OAP6SASD.js} +3 -3
- package/dist/{chunk-4HHRTA7Q.js.map → chunk-OAP6SASD.js.map} +1 -1
- package/dist/chunk-OXAM2WVC.js +68 -0
- package/dist/chunk-OXAM2WVC.js.map +1 -0
- package/dist/{chunk-VAYCCUXW.js → chunk-PRE7KTM4.js} +2 -2
- package/dist/{chunk-ZE74H5BR.js → chunk-RW54ZMBM.js} +26 -20
- package/dist/chunk-RW54ZMBM.js.map +1 -0
- package/dist/{chunk-ML3NRPNB.js → chunk-UCZ24SUE.js} +5 -5
- package/dist/chunk-UCZ24SUE.js.map +1 -0
- package/dist/{chunk-3KATJIKO.js → chunk-UERERX6M.js} +2 -2
- package/dist/chunk-UERERX6M.js.map +1 -0
- package/dist/{chunk-IP7SMKIF.js → chunk-UJL4HI2R.js} +59 -60
- package/dist/chunk-UJL4HI2R.js.map +1 -0
- package/dist/{chunk-XXV3UFZL.js → chunk-ZLIHIUDQ.js} +8 -6
- package/dist/chunk-ZLIHIUDQ.js.map +1 -0
- package/dist/{claude-GOP6PFC7.js → claude-KIZYXTSG.js} +4 -2
- package/dist/{cleanup-7RWLBSLE.js → cleanup-6WYUD5SN.js} +19 -17
- package/dist/{cleanup-7RWLBSLE.js.map → cleanup-6WYUD5SN.js.map} +1 -1
- package/dist/cli.js +209 -73
- package/dist/cli.js.map +1 -1
- package/dist/{contribute-BS2L4FZR.js → contribute-7YJHZTO7.js} +3 -2
- package/dist/{contribute-BS2L4FZR.js.map → contribute-7YJHZTO7.js.map} +1 -1
- package/dist/{feedback-N4ECWIPF.js → feedback-752MGDPG.js} +10 -8
- package/dist/{feedback-N4ECWIPF.js.map → feedback-752MGDPG.js.map} +1 -1
- package/dist/{git-TDXKRTXM.js → git-TAFVDB3J.js} +5 -2
- package/dist/{ignite-VM64QO3J.js → ignite-OAMDUN27.js} +13 -11
- package/dist/{ignite-VM64QO3J.js.map → ignite-OAMDUN27.js.map} +1 -1
- package/dist/index.d.ts +20 -7
- package/dist/index.js +279 -297
- package/dist/index.js.map +1 -1
- package/dist/{init-G3T64SC4.js → init-7IIM35LQ.js} +12 -9
- package/dist/{init-G3T64SC4.js.map → init-7IIM35LQ.js.map} +1 -1
- package/dist/mcp/issue-management-server.js +8 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{neon-helpers-WPUACUVC.js → neon-helpers-5HBYO2VP.js} +2 -2
- package/dist/{open-KXDXEKRZ.js → open-FCHKQ77R.js} +27 -14
- package/dist/open-FCHKQ77R.js.map +1 -0
- package/dist/prompts/init-prompt.txt +10 -4
- package/dist/{rebase-Q7GMM7EI.js → rebase-Q7WXK566.js} +10 -8
- package/dist/{rebase-Q7GMM7EI.js.map → rebase-Q7WXK566.js.map} +1 -1
- package/dist/{run-PAWJJCSX.js → run-SJPM6YRI.js} +27 -14
- package/dist/run-SJPM6YRI.js.map +1 -0
- package/dist/schema/settings.schema.json +4 -4
- package/dist/{test-git-3WDLNQCA.js → test-git-DEUE656D.js} +5 -5
- package/dist/{test-prefix-EVGAWAJW.js → test-prefix-Y6Z6ZHSF.js} +5 -5
- package/dist/{test-tabs-RXDBZ6J7.js → test-tabs-PRMRSHKI.js} +3 -2
- package/dist/{test-tabs-RXDBZ6J7.js.map → test-tabs-PRMRSHKI.js.map} +1 -1
- package/package.json +2 -2
- package/dist/ClaudeContextManager-MUQSDY2E.js +0 -13
- package/dist/ClaudeService-HG4VQ7AW.js +0 -12
- package/dist/LoomLauncher-FLEMBCSQ.js.map +0 -1
- package/dist/chunk-3KATJIKO.js.map +0 -1
- package/dist/chunk-47KSHUCR.js.map +0 -1
- package/dist/chunk-5EF7Z346.js.map +0 -1
- package/dist/chunk-G2IEYOLQ.js.map +0 -1
- package/dist/chunk-IP7SMKIF.js.map +0 -1
- package/dist/chunk-MAVL6PJF.js.map +0 -1
- package/dist/chunk-ML3NRPNB.js.map +0 -1
- package/dist/chunk-XXV3UFZL.js.map +0 -1
- package/dist/chunk-ZE74H5BR.js.map +0 -1
- package/dist/open-KXDXEKRZ.js.map +0 -1
- package/dist/run-PAWJJCSX.js.map +0 -1
- package/dist/terminal-BIRBZ4AZ.js +0 -16
- package/dist/terminal-BIRBZ4AZ.js.map +0 -1
- /package/dist/{BranchNamingService-3OQPRSWT.js.map → BranchNamingService-JYO746H7.js.map} +0 -0
- /package/dist/{ClaudeContextManager-MUQSDY2E.js.map → ClaudeContextManager-5WPRJIIW.js.map} +0 -0
- /package/dist/{ClaudeService-HG4VQ7AW.js.map → ClaudeService-Z4KA7QOW.js.map} +0 -0
- /package/dist/{GitHubService-EBOETDIW.js.map → GitHubService-UAMH7DMF.js.map} +0 -0
- /package/dist/{SettingsManager-WHHFGSL7.js.map → SettingsManager-MTVX57WR.js.map} +0 -0
- /package/dist/{chunk-FIAT22G7.js.map → chunk-A2P7NZTB.js.map} +0 -0
- /package/dist/{chunk-C5QCTEQK.js.map → chunk-C7ASXK6J.js.map} +0 -0
- /package/dist/{chunk-AWOFAD5O.js.map → chunk-FOV7RRQ2.js.map} +0 -0
- /package/dist/{chunk-RO26VS3W.js.map → chunk-FXMLNKLT.js.map} +0 -0
- /package/dist/{chunk-VAYCCUXW.js.map → chunk-PRE7KTM4.js.map} +0 -0
- /package/dist/{claude-GOP6PFC7.js.map → claude-KIZYXTSG.js.map} +0 -0
- /package/dist/{git-TDXKRTXM.js.map → git-TAFVDB3J.js.map} +0 -0
- /package/dist/{neon-helpers-WPUACUVC.js.map → neon-helpers-5HBYO2VP.js.map} +0 -0
- /package/dist/{test-git-3WDLNQCA.js.map → test-git-DEUE656D.js.map} +0 -0
- /package/dist/{test-prefix-EVGAWAJW.js.map → test-prefix-Y6Z6ZHSF.js.map} +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-GEHQXLEI.js";
|
|
5
5
|
|
|
6
6
|
// src/utils/env.ts
|
|
7
|
+
import path from "path";
|
|
7
8
|
import dotenvFlow from "dotenv-flow";
|
|
8
9
|
function parseEnvFile(content) {
|
|
9
10
|
const envMap = /* @__PURE__ */ new Map();
|
|
@@ -116,68 +117,62 @@ function loadEnvIntoProcess(options) {
|
|
|
116
117
|
}
|
|
117
118
|
return returnValue;
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
120
|
+
var DOTENV_FLOW_NODE_ENV = process.env.DOTENV_FLOW_NODE_ENV ?? "development";
|
|
121
|
+
function getDotenvFlowFiles() {
|
|
122
|
+
return [
|
|
123
|
+
".env",
|
|
124
|
+
".env.local",
|
|
125
|
+
`.env.${DOTENV_FLOW_NODE_ENV}`,
|
|
126
|
+
`.env.${DOTENV_FLOW_NODE_ENV}.local`
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
function getLocalEquivalent(filename) {
|
|
130
|
+
if (filename.endsWith(".local")) {
|
|
131
|
+
return filename;
|
|
132
|
+
}
|
|
133
|
+
return `${filename}.local`;
|
|
134
|
+
}
|
|
135
|
+
async function findEnvFileForDatabaseUrl(workspacePath, variableName, isFileTracked, fileExists, getEnvVariable) {
|
|
136
|
+
const file = await findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable);
|
|
137
|
+
if (file === null) {
|
|
138
|
+
return ".env.local";
|
|
139
|
+
}
|
|
140
|
+
const isTracked = await isFileTracked(file, workspacePath);
|
|
141
|
+
if (isTracked) {
|
|
142
|
+
return getLocalEquivalent(file);
|
|
143
|
+
}
|
|
144
|
+
return file;
|
|
145
|
+
}
|
|
146
|
+
async function buildEnvSourceCommands(workspacePath, fileExists) {
|
|
147
|
+
const files = getDotenvFlowFiles();
|
|
148
|
+
const commands = [];
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
const fullPath = path.join(workspacePath, file);
|
|
151
|
+
const exists = await fileExists(fullPath);
|
|
152
|
+
if (exists) {
|
|
153
|
+
commands.push(`source ${file}`);
|
|
156
154
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
155
|
+
}
|
|
156
|
+
return commands;
|
|
157
|
+
}
|
|
158
|
+
async function findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable) {
|
|
159
|
+
const files = getDotenvFlowFiles().reverse();
|
|
160
|
+
for (const file of files) {
|
|
161
|
+
const fullPath = path.join(workspacePath, file);
|
|
162
|
+
if (!await fileExists(fullPath)) {
|
|
163
|
+
continue;
|
|
169
164
|
}
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
173
|
-
type: "branch",
|
|
174
|
-
branchName: cleanId,
|
|
175
|
-
originalInput
|
|
176
|
-
};
|
|
165
|
+
const value = await getEnvVariable(fullPath, variableName);
|
|
166
|
+
if (value !== null) {
|
|
167
|
+
return file;
|
|
177
168
|
}
|
|
178
|
-
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
179
169
|
}
|
|
180
|
-
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
async function hasVariableInAnyEnvFile(workspacePath, variableName, fileExists, getEnvVariable) {
|
|
173
|
+
const file = await findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable);
|
|
174
|
+
return file !== null;
|
|
175
|
+
}
|
|
181
176
|
|
|
182
177
|
export {
|
|
183
178
|
parseEnvFile,
|
|
@@ -185,6 +180,10 @@ export {
|
|
|
185
180
|
validateEnvVariable,
|
|
186
181
|
extractPort,
|
|
187
182
|
loadEnvIntoProcess,
|
|
188
|
-
|
|
183
|
+
getDotenvFlowFiles,
|
|
184
|
+
findEnvFileForDatabaseUrl,
|
|
185
|
+
buildEnvSourceCommands,
|
|
186
|
+
findEnvFileContainingVariable,
|
|
187
|
+
hasVariableInAnyEnvFile
|
|
189
188
|
};
|
|
190
|
-
//# sourceMappingURL=chunk-
|
|
189
|
+
//# sourceMappingURL=chunk-UJL4HI2R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/env.ts"],"sourcesContent":["import path from 'path'\nimport 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\n// CONSTANT: Always use 'development' per critical constraint, unless overridden\nconst DOTENV_FLOW_NODE_ENV = process.env.DOTENV_FLOW_NODE_ENV ?? 'development'\n\n/**\n * Get dotenv-flow files in precedence order (lowest to highest)\n * Always uses 'development' as NODE_ENV per constraint\n */\nexport function getDotenvFlowFiles(): string[] {\n return [\n '.env',\n '.env.local',\n `.env.${DOTENV_FLOW_NODE_ENV}`,\n `.env.${DOTENV_FLOW_NODE_ENV}.local`\n ]\n}\n\n/**\n * Map a file to its \"local\" equivalent for git-safe writes\n * .env -> .env.local\n * .env.{NODE_ENV} -> .env.{NODE_ENV}.local\n * Already local files return unchanged\n */\nexport function getLocalEquivalent(filename: string): string {\n // Already a .local file\n if (filename.endsWith('.local')) {\n return filename\n }\n return `${filename}.local`\n}\n\n/**\n * Find the appropriate env file to write a database URL variable to\n * Considers dotenv-flow precedence and git tracking status\n * Returns path relative to workspacePath\n *\n * Algorithm:\n * 1. Search files in reverse precedence order (highest first)\n * 2. Find first file containing the variable\n * 3. If tracked by git, return its .local equivalent\n * 4. If not tracked, return the file itself\n * 5. If not found anywhere, return '.env.local' (safe default)\n */\nexport async function findEnvFileForDatabaseUrl(\n workspacePath: string,\n variableName: string,\n isFileTracked: (filePath: string, cwd: string) => Promise<boolean>,\n fileExists: (filePath: string) => Promise<boolean>,\n getEnvVariable: (filePath: string, varName: string) => Promise<string | null>\n): Promise<string> {\n // Find the highest-precedence file containing the variable\n const file = await findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable)\n\n if (file === null) {\n // Variable not found anywhere - use safe default\n return '.env.local'\n }\n\n // Found the variable - check git tracking\n const isTracked = await isFileTracked(file, workspacePath)\n if (isTracked) {\n // Return .local equivalent for git safety\n return getLocalEquivalent(file)\n }\n\n return file\n}\n\n/**\n * Build shell source commands for all existing dotenv-flow files\n * Returns commands in precedence order (later overrides earlier)\n */\nexport async function buildEnvSourceCommands(\n workspacePath: string,\n fileExists: (filePath: string) => Promise<boolean>\n): Promise<string[]> {\n const files = getDotenvFlowFiles()\n const commands: string[] = []\n\n for (const file of files) {\n const fullPath = path.join(workspacePath, file)\n const exists = await fileExists(fullPath)\n if (exists) {\n commands.push(`source ${file}`)\n }\n }\n\n return commands\n}\n\n/**\n * Find the highest-precedence env file containing a variable\n * Searches all dotenv-flow files in reverse precedence order (highest first)\n * Returns the relative filename if found, null otherwise\n */\nexport async function findEnvFileContainingVariable(\n workspacePath: string,\n variableName: string,\n fileExists: (filePath: string) => Promise<boolean>,\n getEnvVariable: (filePath: string, varName: string) => Promise<string | null>\n): Promise<string | null> {\n const files = getDotenvFlowFiles().reverse() // highest precedence first\n\n for (const file of files) {\n const fullPath = path.join(workspacePath, file)\n\n // Skip if file doesn't exist\n if (!(await fileExists(fullPath))) {\n continue\n }\n\n // Check if file contains the variable\n const value = await getEnvVariable(fullPath, variableName)\n if (value !== null) {\n return file\n }\n }\n\n return null\n}\n\n/**\n * Check if a variable exists in any dotenv-flow file\n * Searches all dotenv-flow files (.env, .env.local, .env.{NODE_ENV}, .env.{NODE_ENV}.local)\n * Returns true if variable is found in any file, false otherwise\n */\nexport async function hasVariableInAnyEnvFile(\n workspacePath: string,\n variableName: string,\n fileExists: (filePath: string) => Promise<boolean>,\n getEnvVariable: (filePath: string, varName: string) => Promise<string | null>\n): Promise<boolean> {\n const file = await findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable)\n return file !== null\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
openTerminalWindow
|
|
4
|
+
} from "./chunk-RW54ZMBM.js";
|
|
2
5
|
import {
|
|
3
6
|
logger
|
|
4
7
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -161,7 +164,6 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
|
|
|
161
164
|
if (!workspacePath) {
|
|
162
165
|
throw new Error("workspacePath is required for terminal window launch");
|
|
163
166
|
}
|
|
164
|
-
const { openTerminalWindow } = await import("./terminal-BIRBZ4AZ.js");
|
|
165
167
|
const executable = executablePath ?? "iloom";
|
|
166
168
|
let launchCommand = `${executable} spin`;
|
|
167
169
|
if (oneShot !== "default") {
|
|
@@ -223,20 +225,20 @@ Generate a git branch name for the following issue:
|
|
|
223
225
|
model,
|
|
224
226
|
headless: true
|
|
225
227
|
});
|
|
226
|
-
const branchName = result.trim();
|
|
228
|
+
const branchName = result.trim().toLowerCase();
|
|
227
229
|
logger.debug("Claude returned branch name", { branchName, issueNumber });
|
|
228
230
|
if (!branchName || !isValidBranchName(branchName, issueNumber)) {
|
|
229
231
|
logger.warn("Invalid branch name from Claude, using fallback", { branchName });
|
|
230
|
-
return `feat/issue-${issueNumber}
|
|
232
|
+
return `feat/issue-${issueNumber}`.toLowerCase();
|
|
231
233
|
}
|
|
232
234
|
return branchName;
|
|
233
235
|
} catch (error) {
|
|
234
236
|
logger.warn("Failed to generate branch name with Claude", { error });
|
|
235
|
-
return `feat/issue-${issueNumber}
|
|
237
|
+
return `feat/issue-${issueNumber}`.toLowerCase();
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
function isValidBranchName(name, issueNumber) {
|
|
239
|
-
const pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]
|
|
241
|
+
const pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`, "i");
|
|
240
242
|
return pattern.test(name) && name.length <= 50;
|
|
241
243
|
}
|
|
242
244
|
|
|
@@ -247,4 +249,4 @@ export {
|
|
|
247
249
|
launchClaudeInNewTerminalWindow,
|
|
248
250
|
generateBranchName
|
|
249
251
|
};
|
|
250
|
-
//# sourceMappingURL=chunk-
|
|
252
|
+
//# sourceMappingURL=chunk-ZLIHIUDQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/claude.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { logger } from './logger.js'\nimport { openTerminalWindow } from './terminal.js'\n\nexport interface ClaudeCliOptions {\n\tmodel?: string\n\tpermissionMode?: 'plan' | 'acceptEdits' | 'bypassPermissions' | 'default'\n\taddDir?: string\n\theadless?: boolean\n\tbranchName?: string // Optional branch name for terminal coloring\n\tport?: number // Optional port for terminal window export\n\ttimeout?: number // Timeout in milliseconds\n\tappendSystemPrompt?: string // System instructions to append to system prompt\n\tmcpConfig?: Record<string, unknown>[] // Array of MCP server configurations\n\tallowedTools?: string[] // Tools to allow via --allowed-tools flag\n\tdisallowedTools?: string[] // Tools to disallow via --disallowed-tools flag\n\tagents?: Record<string, unknown> // Agent configurations for --agents flag\n\toneShot?: import('../types/index.js').OneShotMode // One-shot automation mode\n\tsetArguments?: string[] // Raw --set arguments to forward (e.g., ['workflows.issue.startIde=false'])\n\texecutablePath?: string // Executable path to use for spin command (e.g., 'il', 'il-125', or '/path/to/dist/cli.js')\n}\n\n/**\n * Detect if Claude CLI is available on the system\n */\nexport async function detectClaudeCli(): Promise<boolean> {\n\ttry {\n\t\t// Use 'command -v' for cross-platform compatibility (works on macOS/Linux)\n\t\tawait execa('command', ['-v', 'claude'], {\n\t\t\tshell: true,\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn true\n\t} catch (error) {\n\t\t// Claude CLI not found\n\t\tlogger.debug('Claude CLI not available', { error })\n\t\treturn false\n\t}\n}\n\n/**\n * Get Claude CLI version\n */\nexport async function getClaudeVersion(): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa('claude', ['--version'], {\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn result.stdout.trim()\n\t} catch (error) {\n\t\tlogger.warn('Failed to get Claude version', { error })\n\t\treturn null\n\t}\n}\n\n/**\n * Parse JSON stream output and extract result from last JSON object with type:\"result\"\n */\nfunction parseJsonStreamOutput(output: string): string {\n\ttry {\n\t\t// Split by newlines and filter out empty lines\n\t\tconst lines = output.split('\\n').filter(line => line.trim())\n\n\t\t// Find the last valid JSON object with type:\"result\"\n\t\tlet lastResult = ''\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst jsonObj = JSON.parse(line)\n\t\t\t\tif (jsonObj && typeof jsonObj === 'object' && jsonObj.type === 'result' && 'result' in jsonObj) {\n\t\t\t\t\tlastResult = jsonObj.result\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip invalid JSON lines\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\treturn lastResult || output // Fallback to original output if no valid result found\n\t} catch {\n\t\t// If parsing fails completely, return original output\n\t\treturn output\n\t}\n}\n\n/**\n * Launch Claude CLI with specified options\n * In headless mode, returns stdout. In interactive mode, returns void.\n */\nexport async function launchClaude(\n\tprompt: string,\n\toptions: ClaudeCliOptions = {}\n): Promise<string | void> {\n\tconst { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents } = options\n\n\t// Build command arguments\n\tconst args: string[] = []\n\n\tif (headless) {\n\t\targs.push('-p')\n\n\t\t// Add JSON streaming output for progress tracking\n\t\targs.push('--output-format', 'stream-json')\n\t\targs.push('--verbose')\n\t}\n\n\tif (model) {\n\t\targs.push('--model', model)\n\t}\n\n\tif (permissionMode && permissionMode !== 'default') {\n\t\targs.push('--permission-mode', permissionMode)\n\t}\n\n\tif (addDir) {\n\t\targs.push('--add-dir', addDir)\n\t}\n\n\targs.push('--add-dir', '/tmp') //TODO: Won't work on Windows\n\n\t// Add --append-system-prompt flag if provided\n\tif (appendSystemPrompt) {\n\t\targs.push('--append-system-prompt', appendSystemPrompt)\n\t}\n\n\t// Add --mcp-config flags for each MCP server configuration\n\tif (mcpConfig && mcpConfig.length > 0) {\n\t\tfor (const config of mcpConfig) {\n\t\t\targs.push('--mcp-config', JSON.stringify(config))\n\t\t}\n\t}\n\n\t// Add --allowed-tools flags if provided\n\tif (allowedTools && allowedTools.length > 0) {\n\t\targs.push('--allowed-tools', ...allowedTools)\n\t}\n\n\t// Add --disallowed-tools flags if provided\n\tif (disallowedTools && disallowedTools.length > 0) {\n\t\targs.push('--disallowed-tools', ...disallowedTools)\n\t}\n\n\t// Add --agents flag if provided\n\tif (agents) {\n\t\targs.push('--agents', JSON.stringify(agents))\n\t}\n\n\ttry {\n\t\tif (headless) {\n\t\t\t// Headless mode: capture and return output\n\t\t\tconst isDebugMode = logger.isDebugEnabled()\n\n\t\t\t// Set up execa options based on debug mode\n\t\t\tconst execaOptions = {\n\t\t\t\tinput: prompt,\n\t\t\t\ttimeout: 0, // Disable timeout for long responses\n\t\t\t\t...(addDir && { cwd: addDir }), // Run Claude in the worktree directory\n\t\t\t\tverbose: isDebugMode,\n\t\t\t\t...(isDebugMode && { stdio: ['pipe', 'pipe', 'pipe'] as const }), // Enable streaming in debug mode\n\t\t\t}\n\n\t\t\tconst subprocess = execa('claude', args, execaOptions)\n\n\t\t\t// Check if JSON streaming format is enabled (always true in headless mode)\n\t\t\tconst isJsonStreamFormat = args.includes('--output-format') && args.includes('stream-json')\n\n\t\t\t// Handle real-time streaming (enabled for progress tracking)\n\t\t\tlet outputBuffer = ''\n\t\t\tlet isStreaming = false\n\t\t\tlet isFirstProgress = true\n\t\t\tif (subprocess.stdout && typeof subprocess.stdout.on === 'function') {\n\t\t\t\tisStreaming = true\n\t\t\t\tsubprocess.stdout.on('data', (chunk: Buffer) => {\n\t\t\t\t\tconst text = chunk.toString()\n\t\t\t\t\toutputBuffer += text\n\n\t\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t\tprocess.stdout.write(text) // Full JSON streaming in debug mode\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Progress dots in non-debug mode with robot emoji prefix\n\t\t\t\t\t\tif (isFirstProgress) {\n\t\t\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\t\t\tisFirstProgress = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tprocess.stdout.write('.')\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst result = await subprocess\n\n\t\t\t// Return streamed output if we were streaming, otherwise use result.stdout\n\t\t\tif (isStreaming) {\n\t\t\t\tconst rawOutput = outputBuffer.trim()\n\n\t\t\t\t// Clean up progress dots with newline in non-debug mode\n\t\t\t\tif (!isDebugMode) {\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t} else {\n\t\t\t\t// Fallback for mocked tests or when streaming not available\n\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t// In debug mode, write to stdout even if not streaming (old behavior for tests)\n\t\t\t\t\tprocess.stdout.write(result.stdout)\n\t\t\t\t\tif (result.stdout && !result.stdout.endsWith('\\n')) {\n\t\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// In non-debug mode, show a single progress dot even without streaming (for tests)\n\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\t\t\t\tconst rawOutput = result.stdout.trim()\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t}\n\t\t} else {\n\t\t\t// Simple interactive mode: run Claude in current terminal with stdio inherit\n\t\t\t// Used for conflict resolution, error fixing, etc.\n\t\t\t// This is the simple approach: claude -- \"prompt\"\n\n\t\t\t// Execute in current terminal (blocking, inherits stdio)\n\t\t\tawait execa('claude', [...args, '--', prompt], {\n\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\tstdio: 'inherit', // Let user interact directly in current terminal\n\t\t\t\ttimeout: 0, // Disable timeout\n\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t})\n\n\t\t\treturn\n\t\t}\n\t} catch (error) {\n\t\t// Check for specific Claude CLI errors\n\t\tconst execaError = error as {\n\t\t\tstderr?: string\n\t\t\tmessage?: string\n\t\t\texitCode?: number\n\t\t}\n\n\t\t// Re-throw with more context\n\t\tconst errorMessage = execaError.stderr ?? execaError.message ?? 'Unknown Claude CLI error'\n\t\tthrow new Error(`Claude CLI error: ${errorMessage}`)\n\t}\n}\n\n/**\n * Launch Claude in a new terminal window with rich context\n * This is specifically for \"end of il start\" workflow\n * Ports the terminal window opening, coloring, and .env sourcing behavior\n */\nexport async function launchClaudeInNewTerminalWindow(\n\t_prompt: string,\n\toptions: ClaudeCliOptions & {\n\t\tworkspacePath: string // Required for terminal window launch\n\t}\n): Promise<void> {\n\tconst { workspacePath, branchName, oneShot = 'default', port, setArguments, executablePath } = options\n\n\t// Verify required parameter\n\tif (!workspacePath) {\n\t\tthrow new Error('workspacePath is required for terminal window launch')\n\t}\n\n\t// Build launch command with optional --one-shot flag\n\t// Use provided executable path or fallback to 'il'\n\tconst executable = executablePath ?? 'iloom'\n\tlet launchCommand = `${executable} spin`\n\tif (oneShot !== 'default') {\n\t\tlaunchCommand += ` --one-shot=${oneShot}`\n\t}\n\n\t// Append --set arguments if provided\n\tif (setArguments && setArguments.length > 0) {\n\t\tfor (const setArg of setArguments) {\n\t\t\tlaunchCommand += ` --set ${setArg}`\n\t\t}\n\t}\n\n\t// Apply terminal background color if branch name available\n\tlet backgroundColor: { r: number; g: number; b: number } | undefined\n\tif (branchName) {\n\t\ttry {\n\t\t\tconst { generateColorFromBranchName } = await import('./color.js')\n\t\t\tconst colorData = generateColorFromBranchName(branchName)\n\t\t\tbackgroundColor = colorData.rgb\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to generate terminal color: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t// Check if .env file exists in workspace\n\tconst hasEnvFile = existsSync(join(workspacePath, '.env'))\n\n\t// Open new terminal window with Claude\n\tawait openTerminalWindow({\n\t\tworkspacePath,\n\t\tcommand: launchCommand,\n\t\t...(backgroundColor && { backgroundColor }),\n\t\tincludeEnvSetup: hasEnvFile, // source .env only if it exists\n\t\t...(port !== undefined && { port, includePortExport: true }),\n\t})\n}\n\n/**\n * Generate a branch name using Claude with fallback\n * This matches the implementation that was working in ClaudeBranchNameStrategy\n */\nexport async function generateBranchName(\n\tissueTitle: string,\n\tissueNumber: string | number,\n\tmodel: string = 'haiku'\n): Promise<string> {\n\ttry {\n\t\t// Check if Claude CLI is available\n\t\tconst isAvailable = await detectClaudeCli()\n\t\tif (!isAvailable) {\n\t\t\tlogger.warn('Claude CLI not available, using fallback branch name')\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\n\t\tlogger.debug('Generating branch name with Claude', { issueNumber, issueTitle })\n\n\t\t// Use the proven prompt format from ClaudeBranchNameStrategy\n\t\tconst prompt = `<Task>\nGenerate a git branch name for the following issue:\n<Issue>\n<IssueNumber>${issueNumber}</IssueNumber>\n<IssueTitle>${issueTitle}</IssueTitle>\n</Issue>\n\n<Requirements>\n<IssueNumber>Must use this exact issue number: ${issueNumber}</IssueNumber>\n<Format>Format must be: {prefix}/issue-${issueNumber}__{description}</Format>\n<Prefix>Prefix must be one of: feat, fix, docs, refactor, test, chore</Prefix>\n<MaxLength>Maximum 50 characters total</MaxLength>\n<Characters>Only lowercase letters, numbers, and hyphens allowed</Characters>\n<Output>Reply with ONLY the branch name, nothing else</Output>\n</Requirements>\n</Task>`\n\n\t\tlogger.debug('Sending prompt to Claude', { prompt })\n\n\t\tconst result = (await launchClaude(prompt, {\n\t\t\tmodel,\n\t\t\theadless: true,\n\t\t})) as string\n\n\t\t// Normalize to lowercase for consistency (Linear IDs are uppercase but branches should be lowercase)\n\t\tconst branchName = result.trim().toLowerCase()\n\t\tlogger.debug('Claude returned branch name', { branchName, issueNumber })\n\n\t\t// Validate generated name using same validation as ClaudeBranchNameStrategy\n\t\tif (!branchName || !isValidBranchName(branchName, issueNumber)) {\n\t\t\tlogger.warn('Invalid branch name from Claude, using fallback', { branchName })\n\t\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t\t}\n\n\t\treturn branchName\n\t} catch (error) {\n\t\tlogger.warn('Failed to generate branch name with Claude', { error })\n\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t}\n}\n\n/**\n * Validate branch name format\n * Check format: {prefix}/issue-{number}__{description}\n * Uses case-insensitive matching for issue number (Linear uses uppercase like MARK-1)\n */\nfunction isValidBranchName(name: string, issueNumber: string | number): boolean {\n\tconst pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`, 'i')\n\treturn pattern.test(name) && name.length <= 50\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
getClaudeVersion,
|
|
6
6
|
launchClaude,
|
|
7
7
|
launchClaudeInNewTerminalWindow
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZLIHIUDQ.js";
|
|
9
|
+
import "./chunk-RW54ZMBM.js";
|
|
10
|
+
import "./chunk-UJL4HI2R.js";
|
|
9
11
|
import "./chunk-GEHQXLEI.js";
|
|
10
12
|
export {
|
|
11
13
|
detectClaudeCli,
|
|
@@ -14,4 +16,4 @@ export {
|
|
|
14
16
|
launchClaude,
|
|
15
17
|
launchClaudeInNewTerminalWindow
|
|
16
18
|
};
|
|
17
|
-
//# sourceMappingURL=claude-
|
|
19
|
+
//# sourceMappingURL=claude-KIZYXTSG.js.map
|
|
@@ -5,30 +5,32 @@ import {
|
|
|
5
5
|
EnvironmentManager,
|
|
6
6
|
LoomManager,
|
|
7
7
|
ResourceCleanup
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KG343GSG.js";
|
|
9
9
|
import {
|
|
10
|
-
IdentifierParser
|
|
11
|
-
|
|
12
|
-
} from "./chunk-IP7SMKIF.js";
|
|
10
|
+
IdentifierParser
|
|
11
|
+
} from "./chunk-OXAM2WVC.js";
|
|
13
12
|
import {
|
|
14
13
|
ProcessManager
|
|
15
14
|
} from "./chunk-VU3QMIP2.js";
|
|
16
|
-
import {
|
|
17
|
-
createNeonProviderFromSettings
|
|
18
|
-
} from "./chunk-JQFO7QQN.js";
|
|
19
15
|
import {
|
|
20
16
|
GitWorktreeManager
|
|
21
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-OAP6SASD.js";
|
|
22
18
|
import "./chunk-ZT3YZB4K.js";
|
|
23
19
|
import "./chunk-2ZPFJQ3B.js";
|
|
20
|
+
import {
|
|
21
|
+
createNeonProviderFromSettings
|
|
22
|
+
} from "./chunk-GVICXJHW.js";
|
|
23
|
+
import "./chunk-5NTD4MCZ.js";
|
|
24
24
|
import {
|
|
25
25
|
SettingsManager
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-MAVL6PJF.js";
|
|
26
|
+
} from "./chunk-UCZ24SUE.js";
|
|
28
27
|
import {
|
|
29
28
|
promptConfirmation
|
|
30
29
|
} from "./chunk-JKXJ7BGL.js";
|
|
31
30
|
import "./chunk-ZZZWQGTS.js";
|
|
31
|
+
import {
|
|
32
|
+
loadEnvIntoProcess
|
|
33
|
+
} from "./chunk-UJL4HI2R.js";
|
|
32
34
|
import {
|
|
33
35
|
logger
|
|
34
36
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -71,10 +73,10 @@ var CleanupCommand = class {
|
|
|
71
73
|
cliIsolationManager
|
|
72
74
|
);
|
|
73
75
|
if (!this.loomManager) {
|
|
74
|
-
const { GitHubService } = await import("./GitHubService-
|
|
75
|
-
const { ClaudeContextManager } = await import("./ClaudeContextManager-
|
|
76
|
+
const { GitHubService } = await import("./GitHubService-UAMH7DMF.js");
|
|
77
|
+
const { ClaudeContextManager } = await import("./ClaudeContextManager-5WPRJIIW.js");
|
|
76
78
|
const { ProjectCapabilityDetector } = await import("./ProjectCapabilityDetector-34LU7JJ4.js");
|
|
77
|
-
const { DefaultBranchNamingService } = await import("./BranchNamingService-
|
|
79
|
+
const { DefaultBranchNamingService } = await import("./BranchNamingService-JYO746H7.js");
|
|
78
80
|
this.loomManager = new LoomManager(
|
|
79
81
|
this.gitWorktreeManager,
|
|
80
82
|
new GitHubService(),
|
|
@@ -268,7 +270,7 @@ var CleanupCommand = class {
|
|
|
268
270
|
const { force, dryRun } = parsed.options;
|
|
269
271
|
let parsedInput = await this.identifierParser.parseForPatternDetection(identifier);
|
|
270
272
|
if (parsedInput.type === "branch" && parsedInput.branchName) {
|
|
271
|
-
const { extractIssueNumber } = await import("./git-
|
|
273
|
+
const { extractIssueNumber } = await import("./git-TAFVDB3J.js");
|
|
272
274
|
const extractedNumber = extractIssueNumber(parsedInput.branchName);
|
|
273
275
|
if (extractedNumber !== null) {
|
|
274
276
|
parsedInput = {
|
|
@@ -360,8 +362,8 @@ var CleanupCommand = class {
|
|
|
360
362
|
const worktrees = await this.gitWorktreeManager.listWorktrees();
|
|
361
363
|
const matchingWorktrees = worktrees.filter((wt) => {
|
|
362
364
|
const path = wt.path.toLowerCase();
|
|
363
|
-
const
|
|
364
|
-
const pattern = new RegExp(`(
|
|
365
|
+
const idStr = String(issueNumber).toLowerCase();
|
|
366
|
+
const pattern = new RegExp(`(?:^|[/_-])${idStr}(?:[/_-]|$)`);
|
|
365
367
|
return pattern.test(path);
|
|
366
368
|
});
|
|
367
369
|
if (matchingWorktrees.length === 0) {
|
|
@@ -466,4 +468,4 @@ var CleanupCommand = class {
|
|
|
466
468
|
export {
|
|
467
469
|
CleanupCommand
|
|
468
470
|
};
|
|
469
|
-
//# sourceMappingURL=cleanup-
|
|
471
|
+
//# sourceMappingURL=cleanup-6WYUD5SN.js.map
|
|
@@ -1 +1 @@
|
|
|
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 { LoomManager } from '../lib/LoomManager.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?: string | 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 loomManager?: import('../lib/LoomManager.js').LoomManager\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 and LoomManager with properly configured DatabaseManager\n */\n private async ensureResourceCleanup(): Promise<void> {\n if (this.resourceCleanup && this.loomManager) {\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 // Initialize LoomManager if not provided (for child loom detection)\n if (!this.loomManager) {\n const { GitHubService } = await import('../lib/GitHubService.js')\n const { ClaudeContextManager } = await import('../lib/ClaudeContextManager.js')\n const { ProjectCapabilityDetector } = await import('../lib/ProjectCapabilityDetector.js')\n const { DefaultBranchNamingService } = await import('../lib/BranchNamingService.js')\n\n this.loomManager = new LoomManager(\n this.gitWorktreeManager,\n new GitHubService(),\n new DefaultBranchNamingService({ useClaude: true }),\n environmentManager,\n new ClaudeContextManager(),\n new ProjectCapabilityDetector(),\n cliIsolationManager,\n settingsManager,\n databaseManager\n )\n }\n }\n\n /**\n * Check for child looms and exit gracefully if any exist\n * Always checks the TARGET loom (the one being cleaned up), not the current directory's loom\n *\n * @param parsed - The parsed input identifying the loom being cleaned up\n */\n private async checkForChildLooms(parsed: ParsedCleanupInput): Promise<void> {\n await this.ensureResourceCleanup()\n if (!this.loomManager) {\n throw new Error('Failed to initialize LoomManager')\n }\n\n // Determine which branch is being cleaned up based on parsed input\n let targetBranch: string | undefined\n\n if (parsed.branchName) {\n targetBranch = parsed.branchName\n } else if (parsed.mode === 'issue' && parsed.issueNumber !== undefined) {\n // For issues, try to find the worktree by issue number to get the branch name\n const worktree = await this.gitWorktreeManager.findWorktreeForIssue(parsed.issueNumber)\n targetBranch = worktree?.branch\n }\n\n // If we can't determine the target branch, skip the check\n if (!targetBranch) {\n logger.debug(`Cannot determine target branch for child loom check`)\n return\n }\n\n // Check if the TARGET loom has any child looms\n const hasChildLooms = await this.loomManager.checkAndWarnChildLooms(targetBranch)\n if (hasChildLooms) {\n logger.error('Cannot cleanup loom while child looms exist. Please finish child looms first.')\n process.exit(1)\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 2.5: Check for child looms AFTER parsing input\n // This ensures we only block when cleaning the CURRENT loom (parent), not a child\n await this.checkForChildLooms(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 let parsedInput: ParsedInput = await this.identifierParser.parseForPatternDetection(identifier)\n\n // If type is 'branch', try to extract issue number for CLI symlink cleanup\n if (parsedInput.type === 'branch' && parsedInput.branchName) {\n const { extractIssueNumber } = await import('../utils/git.js')\n const extractedNumber = extractIssueNumber(parsedInput.branchName)\n if (extractedNumber !== null) {\n parsedInput = {\n ...parsedInput,\n number: extractedNumber // Add number for CLI symlink cleanup\n }\n }\n }\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 // Use the known issue number directly instead of parsing from branch name\n // This ensures CLI symlinks (created with issue number) are properly cleaned up\n const parsedInput: ParsedInput = {\n type: 'issue',\n number: issueNumber, // Use the known issue number, not parsed from branch\n branchName: target.branchName,\n originalInput: String(issueNumber)\n }\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
|
+
{"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 { LoomManager } from '../lib/LoomManager.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?: string | 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 loomManager?: import('../lib/LoomManager.js').LoomManager\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 and LoomManager with properly configured DatabaseManager\n */\n private async ensureResourceCleanup(): Promise<void> {\n if (this.resourceCleanup && this.loomManager) {\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 // Initialize LoomManager if not provided (for child loom detection)\n if (!this.loomManager) {\n const { GitHubService } = await import('../lib/GitHubService.js')\n const { ClaudeContextManager } = await import('../lib/ClaudeContextManager.js')\n const { ProjectCapabilityDetector } = await import('../lib/ProjectCapabilityDetector.js')\n const { DefaultBranchNamingService } = await import('../lib/BranchNamingService.js')\n\n this.loomManager = new LoomManager(\n this.gitWorktreeManager,\n new GitHubService(),\n new DefaultBranchNamingService({ useClaude: true }),\n environmentManager,\n new ClaudeContextManager(),\n new ProjectCapabilityDetector(),\n cliIsolationManager,\n settingsManager,\n databaseManager\n )\n }\n }\n\n /**\n * Check for child looms and exit gracefully if any exist\n * Always checks the TARGET loom (the one being cleaned up), not the current directory's loom\n *\n * @param parsed - The parsed input identifying the loom being cleaned up\n */\n private async checkForChildLooms(parsed: ParsedCleanupInput): Promise<void> {\n await this.ensureResourceCleanup()\n if (!this.loomManager) {\n throw new Error('Failed to initialize LoomManager')\n }\n\n // Determine which branch is being cleaned up based on parsed input\n let targetBranch: string | undefined\n\n if (parsed.branchName) {\n targetBranch = parsed.branchName\n } else if (parsed.mode === 'issue' && parsed.issueNumber !== undefined) {\n // For issues, try to find the worktree by issue number to get the branch name\n const worktree = await this.gitWorktreeManager.findWorktreeForIssue(parsed.issueNumber)\n targetBranch = worktree?.branch\n }\n\n // If we can't determine the target branch, skip the check\n if (!targetBranch) {\n logger.debug(`Cannot determine target branch for child loom check`)\n return\n }\n\n // Check if the TARGET loom has any child looms\n const hasChildLooms = await this.loomManager.checkAndWarnChildLooms(targetBranch)\n if (hasChildLooms) {\n logger.error('Cannot cleanup loom while child looms exist. Please finish child looms first.')\n process.exit(1)\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 2.5: Check for child looms AFTER parsing input\n // This ensures we only block when cleaning the CURRENT loom (parent), not a child\n await this.checkForChildLooms(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 let parsedInput: ParsedInput = await this.identifierParser.parseForPatternDetection(identifier)\n\n // If type is 'branch', try to extract issue number for CLI symlink cleanup\n if (parsedInput.type === 'branch' && parsedInput.branchName) {\n const { extractIssueNumber } = await import('../utils/git.js')\n const extractedNumber = extractIssueNumber(parsedInput.branchName)\n if (extractedNumber !== null) {\n parsedInput = {\n ...parsedInput,\n number: extractedNumber // Add number for CLI symlink cleanup\n }\n }\n }\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 // Lowercase for case-insensitive matching (Linear IDs are uppercase like MARK-1)\n const idStr = String(issueNumber).toLowerCase()\n\n // Check if path contains the identifier with proper word boundaries\n // Matches: issue-25, pr-25, 25-feature, _pr_25, issue-mark-1, etc.\n // Uses word boundary or common separators (-, _, /) for alphanumeric IDs\n const pattern = new RegExp(`(?:^|[/_-])${idStr}(?:[/_-]|$)`)\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 // Use the known issue number directly instead of parsing from branch name\n // This ensures CLI symlinks (created with issue number) are properly cleaned up\n const parsedInput: ParsedInput = {\n type: 'issue',\n number: issueNumber, // Use the known issue number, not parsed from branch\n branchName: target.branchName,\n originalInput: String(issueNumber)\n }\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"]}
|