@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
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isPRBranch,
|
|
12
12
|
isValidGitRepo,
|
|
13
13
|
parseWorktreeList
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-5NTD4MCZ.js";
|
|
15
15
|
|
|
16
16
|
// src/lib/GitWorktreeManager.ts
|
|
17
17
|
import path from "path";
|
|
@@ -321,7 +321,7 @@ var GitWorktreeManager = class {
|
|
|
321
321
|
*/
|
|
322
322
|
async findWorktreeForIssue(issueNumber) {
|
|
323
323
|
const worktrees = await this.listWorktrees({ porcelain: true });
|
|
324
|
-
const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|__|$)
|
|
324
|
+
const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|__|$)`, "i");
|
|
325
325
|
return worktrees.find((wt) => pattern.test(wt.branch)) ?? null;
|
|
326
326
|
}
|
|
327
327
|
/**
|
|
@@ -386,4 +386,4 @@ var GitWorktreeManager = class {
|
|
|
386
386
|
export {
|
|
387
387
|
GitWorktreeManager
|
|
388
388
|
};
|
|
389
|
-
//# sourceMappingURL=chunk-
|
|
389
|
+
//# sourceMappingURL=chunk-OAP6SASD.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/GitWorktreeManager.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport {\n type GitWorktree,\n type WorktreeCreateOptions,\n type WorktreeListOptions,\n type WorktreeValidation,\n type WorktreeStatus,\n type WorktreeCleanupOptions,\n} from '../types/worktree.js'\nimport {\n executeGitCommand,\n parseWorktreeList,\n isPRBranch,\n extractPRNumber,\n generateWorktreePath,\n isValidGitRepo,\n getCurrentBranch,\n getRepoRoot,\n hasUncommittedChanges,\n getDefaultBranch,\n findMainWorktreePathWithSettings,\n} from '../utils/git.js'\nimport type { SettingsManager } from './SettingsManager.js'\n\n/**\n * Manages Git worktrees for the iloom CLI\n * Ports functionality from bash scripts into TypeScript\n */\nexport class GitWorktreeManager {\n private readonly _workingDirectory: string\n\n constructor(workingDirectory: string = process.cwd()) {\n this._workingDirectory = workingDirectory\n }\n\n /**\n * Get the working directory for git operations (main worktree path)\n */\n get workingDirectory(): string {\n return this._workingDirectory\n }\n\n /**\n * List all worktrees in the repository\n * Defaults to porcelain format for reliable machine parsing\n * Equivalent to: git worktree list --porcelain\n */\n async listWorktrees(options: WorktreeListOptions = {}): Promise<GitWorktree[]> {\n const args = ['worktree', 'list']\n // Default to porcelain format for consistent parsing (can be disabled with porcelain: false)\n if (options.porcelain !== false) args.push('--porcelain')\n if (options.verbose) args.push('-v')\n\n const output = await executeGitCommand(args, { cwd: this._workingDirectory })\n return parseWorktreeList(output)\n }\n\n /**\n * Find worktree for a specific branch\n * Ports: find_worktree_for_branch() from find-worktree-for-branch.sh\n */\n async findWorktreeForBranch(branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees()\n return worktrees.find(wt => wt.branch === branchName) ?? null\n }\n\n /**\n * Check if a worktree is the main repository worktree\n * Uses findMainWorktreePathWithSettings to determine the main worktree based on settings.\n *\n * @param worktree - The worktree to check\n * @param settingsManager - SettingsManager instance for loading settings\n * @returns true if the worktree is the main worktree\n */\n async isMainWorktree(worktree: GitWorktree, settingsManager: SettingsManager): Promise<boolean> {\n const mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, settingsManager)\n return worktree.path === mainWorktreePath\n }\n\n /**\n * Check if a worktree is a PR worktree based on naming patterns\n * Ports: is_pr_worktree() from worktree-utils.sh\n */\n isPRWorktree(worktree: GitWorktree): boolean {\n return isPRBranch(worktree.branch)\n }\n\n /**\n * Get PR number from worktree branch name\n * Ports: get_pr_number_from_worktree() from worktree-utils.sh\n */\n getPRNumberFromWorktree(worktree: GitWorktree): number | null {\n return extractPRNumber(worktree.branch)\n }\n\n /**\n * Create a new worktree\n * Ports worktree creation logic from new-branch-workflow.sh\n * @returns The absolute path to the created worktree\n */\n async createWorktree(options: WorktreeCreateOptions): Promise<string> {\n // Validate inputs\n if (!options.branch) {\n throw new Error('Branch name is required')\n }\n\n // Ensure path is absolute\n const absolutePath = path.resolve(options.path)\n\n // Check if path already exists and handle force flag\n if (await fs.pathExists(absolutePath)) {\n if (!options.force) {\n throw new Error(`Path already exists: ${absolutePath}`)\n }\n // Remove existing directory if force is true\n await fs.remove(absolutePath)\n }\n\n // Build git worktree add command\n const args = ['worktree', 'add']\n\n if (options.createBranch) {\n args.push('-b', options.branch)\n }\n\n if (options.force) {\n args.push('--force')\n }\n\n args.push(absolutePath)\n\n // Add branch name if not creating new branch\n if (!options.createBranch) {\n args.push(options.branch)\n } else if (options.baseBranch) {\n args.push(options.baseBranch)\n }\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n return absolutePath\n }\n\n /**\n * Remove a worktree and optionally clean up associated files\n * Ports worktree removal logic from cleanup-worktree.sh\n * @returns A message describing what was done (for dry-run mode)\n */\n async removeWorktree(\n worktreePath: string,\n options: WorktreeCleanupOptions = {}\n ): Promise<string | void> {\n // Validate worktree exists - use porcelain format for consistent parsing\n const worktrees = await this.listWorktrees({ porcelain: true })\n const worktree = worktrees.find(wt => wt.path === worktreePath)\n\n if (!worktree) {\n // Add debug logging to help diagnose the issue\n const { logger } = await import('../utils/logger.js')\n logger.debug(`Looking for worktree path: ${worktreePath}`)\n logger.debug(`Found ${worktrees.length} worktrees:`)\n worktrees.forEach((wt, i) => {\n logger.debug(` ${i}: path=\"${wt.path}\", branch=\"${wt.branch}\"`)\n })\n throw new Error(`Worktree not found: ${worktreePath}`)\n }\n\n // Check for uncommitted changes unless force is specified\n if (!options.force && !options.dryRun) {\n const hasChanges = await hasUncommittedChanges(worktreePath)\n if (hasChanges) {\n throw new Error(`Worktree has uncommitted changes: ${worktreePath}. Use --force to override.`)\n }\n }\n\n if (options.dryRun) {\n const actions = ['Remove worktree registration']\n if (options.removeDirectory) actions.push('Remove directory from disk')\n if (options.removeBranch) actions.push(`Remove branch: ${worktree.branch}`)\n\n return `Would perform: ${actions.join(', ')}`\n }\n\n // Remove worktree registration\n const args = ['worktree', 'remove']\n if (options.force) args.push('--force')\n args.push(worktreePath)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n\n // Remove directory if requested\n if (options.removeDirectory && (await fs.pathExists(worktreePath))) {\n await fs.remove(worktreePath)\n }\n\n // Remove branch if requested and safe to do so\n if (options.removeBranch && !worktree.bare) {\n try {\n await executeGitCommand(['branch', '-D', worktree.branch], {\n cwd: this._workingDirectory,\n })\n } catch (error) {\n // Don't fail the whole operation if branch deletion fails\n // Just log a warning (caller can handle this)\n throw new Error(\n `Worktree removed but failed to delete branch ${worktree.branch}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n\n /**\n * Validate worktree state and integrity\n */\n async validateWorktree(worktreePath: string): Promise<WorktreeValidation> {\n const issues: string[] = []\n let existsOnDisk = false\n let isValidRepo = false\n let hasValidBranch = false\n\n try {\n // Check if path exists on disk\n existsOnDisk = await fs.pathExists(worktreePath)\n if (!existsOnDisk) {\n issues.push('Worktree directory does not exist on disk')\n }\n\n // Check if it's a valid Git repository\n if (existsOnDisk) {\n isValidRepo = await isValidGitRepo(worktreePath)\n if (!isValidRepo) {\n issues.push('Directory is not a valid Git repository')\n }\n }\n\n // Check if branch reference is valid\n if (isValidRepo) {\n const currentBranch = await getCurrentBranch(worktreePath)\n hasValidBranch = currentBranch !== null\n if (!hasValidBranch) {\n issues.push('Could not determine current branch')\n }\n }\n\n // Check if worktree is registered with Git\n const worktrees = await this.listWorktrees()\n const isRegistered = worktrees.some(wt => wt.path === worktreePath)\n if (!isRegistered) {\n issues.push('Worktree is not registered with Git')\n }\n } catch (error) {\n issues.push(`Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n\n return {\n isValid: issues.length === 0,\n issues,\n existsOnDisk,\n isValidRepo,\n hasValidBranch,\n }\n }\n\n /**\n * Get detailed status information for a worktree\n */\n async getWorktreeStatus(worktreePath: string): Promise<WorktreeStatus> {\n const statusOutput = await executeGitCommand(['status', '--porcelain=v1'], {\n cwd: worktreePath,\n })\n\n let modified = 0\n let staged = 0\n let deleted = 0\n let untracked = 0\n\n const lines = statusOutput.trim().split('\\n').filter(Boolean)\n for (const line of lines) {\n const status = line.substring(0, 2)\n if (status[0] === 'M' || status[1] === 'M') modified++\n if (status[0] === 'A' || status[0] === 'D' || status[0] === 'R') staged++\n if (status[0] === 'D' || status[1] === 'D') deleted++\n if (status === '??') untracked++\n }\n\n const currentBranch = (await getCurrentBranch(worktreePath)) ?? 'unknown'\n const detached = currentBranch === 'unknown'\n\n // Get ahead/behind information\n let ahead = 0\n let behind = 0\n try {\n const aheadBehindOutput = await executeGitCommand(\n ['rev-list', '--left-right', '--count', `origin/${currentBranch}...HEAD`],\n { cwd: worktreePath }\n )\n const parts = aheadBehindOutput.trim().split('\\t')\n const behindStr = parts[0]\n const aheadStr = parts[1]\n behind = behindStr ? parseInt(behindStr, 10) || 0 : 0\n ahead = aheadStr ? parseInt(aheadStr, 10) || 0 : 0\n } catch {\n // Ignore errors for ahead/behind calculation\n }\n\n return {\n modified,\n staged,\n deleted,\n untracked,\n hasChanges: modified + staged + deleted + untracked > 0,\n branch: currentBranch,\n detached,\n ahead,\n behind,\n }\n }\n\n /**\n * Generate a suggested worktree path for a branch\n */\n generateWorktreePath(\n branchName: string,\n customRoot?: string,\n options?: { isPR?: boolean; prNumber?: number; prefix?: string }\n ): string {\n const root = customRoot ?? this._workingDirectory\n return generateWorktreePath(branchName, root, options)\n }\n\n /**\n * Sanitize a branch name for use as a directory name\n * Replaces slashes with dashes and removes invalid filesystem characters\n * Ports logic from bash script line 593: ${BRANCH_NAME//\\\\//-}\n */\n sanitizeBranchName(branchName: string): string {\n return branchName\n .replace(/\\//g, '-') // Replace slashes with dashes\n .replace(/[^a-zA-Z0-9-]/g, '-') // Replace invalid chars (including underscores) with dashes\n .replace(/-+/g, '-') // Collapse multiple dashes\n .replace(/^-|-$/g, '') // Remove leading/trailing dashes\n .toLowerCase()\n }\n\n /**\n * Check if repository is in a valid state for worktree operations\n */\n async isRepoReady(): Promise<boolean> {\n try {\n const repoRoot = await getRepoRoot(this._workingDirectory)\n return repoRoot !== null\n } catch {\n return false\n }\n }\n\n /**\n * Get repository information\n */\n async getRepoInfo(): Promise<{\n root: string | null\n defaultBranch: string\n currentBranch: string | null\n }> {\n const root = await getRepoRoot(this._workingDirectory)\n const defaultBranch = await getDefaultBranch(this._workingDirectory)\n const currentBranch = await getCurrentBranch(this._workingDirectory)\n\n return {\n root,\n defaultBranch,\n currentBranch,\n }\n }\n\n /**\n * Prune stale worktree entries (worktrees that no longer exist on disk)\n */\n async pruneWorktrees(): Promise<void> {\n await executeGitCommand(['worktree', 'prune', '-v'], { cwd: this._workingDirectory })\n }\n\n /**\n * Lock a worktree to prevent it from being pruned or moved\n */\n async lockWorktree(worktreePath: string, reason?: string): Promise<void> {\n const args = ['worktree', 'lock', worktreePath]\n if (reason) args.push('--reason', reason)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n }\n\n /**\n * Unlock a previously locked worktree\n */\n async unlockWorktree(worktreePath: string): Promise<void> {\n await executeGitCommand(['worktree', 'unlock', worktreePath], { cwd: this._workingDirectory })\n }\n\n /**\n * Find worktrees matching an identifier (branch name, path, or PR number)\n */\n async findWorktreesByIdentifier(identifier: string): Promise<GitWorktree[]> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n return worktrees.filter(\n wt =>\n wt.branch.includes(identifier) ||\n wt.path.includes(identifier) ||\n this.getPRNumberFromWorktree(wt)?.toString() === identifier\n )\n }\n\n /**\n * Find worktree for a specific issue number using exact pattern matching\n * Matches: issue-{N} at start OR after /, -, _ (but NOT issue-{N}X where X is a digit)\n * Supports patterns like: issue-44, feat/issue-44-feature, feat-issue-44, bugfix_issue-44, etc.\n * Avoids false matches like: tissue-44, myissue-44\n * Ports: find_existing_worktree() from bash script lines 131-165\n */\n async findWorktreeForIssue(issueNumber: string | number): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Pattern: starts with 'issue-{N}' OR has '/issue-{N}', '-issue-{N}', '_issue-{N}' but not 'issue-{N}{digit}'\n const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|__|$)`)\n\n return worktrees.find(wt => pattern.test(wt.branch)) ?? null\n }\n\n /**\n * Find worktree for a specific PR by branch name\n * Ports: find_existing_worktree() for PR type from bash script lines 149-160\n */\n async findWorktreeForPR(prNumber: number, branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Find by exact branch name match (prioritized)\n const byBranch = worktrees.find(wt => wt.branch === branchName)\n if (byBranch) return byBranch\n\n // Also check directory name pattern: *_pr_{N}\n const pathPattern = new RegExp(`_pr_${prNumber}$`)\n return worktrees.find(wt => pathPattern.test(wt.path)) ?? null\n }\n\n /**\n * Remove multiple worktrees\n * Returns a summary of successes and failures\n * Automatically filters out the main worktree\n *\n * @param worktrees - Array of worktrees to remove\n * @param settingsManager - SettingsManager instance for determining main worktree\n * @param options - Cleanup options\n */\n async removeWorktrees(\n worktrees: GitWorktree[],\n settingsManager: SettingsManager,\n options: WorktreeCleanupOptions = {}\n ): Promise<{\n successes: Array<{ worktree: GitWorktree }>\n failures: Array<{ worktree: GitWorktree; error: string }>\n skipped: Array<{ worktree: GitWorktree; reason: string }>\n }> {\n const successes: Array<{ worktree: GitWorktree }> = []\n const failures: Array<{ worktree: GitWorktree; error: string }> = []\n const skipped: Array<{ worktree: GitWorktree; reason: string }> = []\n\n for (const worktree of worktrees) {\n // Skip main worktree\n if (await this.isMainWorktree(worktree, settingsManager)) {\n skipped.push({ worktree, reason: 'Cannot remove main worktree' })\n continue\n }\n\n try {\n await this.removeWorktree(worktree.path, {\n ...options,\n removeDirectory: true,\n })\n successes.push({ worktree })\n } catch (error) {\n failures.push({\n worktree,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n\n return { successes, failures, skipped }\n }\n\n /**\n * Format worktree information for display\n */\n formatWorktree(worktree: GitWorktree): {\n title: string\n path: string\n commit: string\n } {\n const prNumber = this.getPRNumberFromWorktree(worktree)\n const prLabel = prNumber ? ` (PR #${prNumber})` : ''\n const bareLabel = worktree.bare ? ' [main]' : ''\n\n return {\n title: `${worktree.branch}${prLabel}${bareLabel}`,\n path: worktree.path,\n commit: worktree.commit.substring(0, 7),\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/GitWorktreeManager.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport {\n type GitWorktree,\n type WorktreeCreateOptions,\n type WorktreeListOptions,\n type WorktreeValidation,\n type WorktreeStatus,\n type WorktreeCleanupOptions,\n} from '../types/worktree.js'\nimport {\n executeGitCommand,\n parseWorktreeList,\n isPRBranch,\n extractPRNumber,\n generateWorktreePath,\n isValidGitRepo,\n getCurrentBranch,\n getRepoRoot,\n hasUncommittedChanges,\n getDefaultBranch,\n findMainWorktreePathWithSettings,\n} from '../utils/git.js'\nimport type { SettingsManager } from './SettingsManager.js'\n\n/**\n * Manages Git worktrees for the iloom CLI\n * Ports functionality from bash scripts into TypeScript\n */\nexport class GitWorktreeManager {\n private readonly _workingDirectory: string\n\n constructor(workingDirectory: string = process.cwd()) {\n this._workingDirectory = workingDirectory\n }\n\n /**\n * Get the working directory for git operations (main worktree path)\n */\n get workingDirectory(): string {\n return this._workingDirectory\n }\n\n /**\n * List all worktrees in the repository\n * Defaults to porcelain format for reliable machine parsing\n * Equivalent to: git worktree list --porcelain\n */\n async listWorktrees(options: WorktreeListOptions = {}): Promise<GitWorktree[]> {\n const args = ['worktree', 'list']\n // Default to porcelain format for consistent parsing (can be disabled with porcelain: false)\n if (options.porcelain !== false) args.push('--porcelain')\n if (options.verbose) args.push('-v')\n\n const output = await executeGitCommand(args, { cwd: this._workingDirectory })\n return parseWorktreeList(output)\n }\n\n /**\n * Find worktree for a specific branch\n * Ports: find_worktree_for_branch() from find-worktree-for-branch.sh\n */\n async findWorktreeForBranch(branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees()\n return worktrees.find(wt => wt.branch === branchName) ?? null\n }\n\n /**\n * Check if a worktree is the main repository worktree\n * Uses findMainWorktreePathWithSettings to determine the main worktree based on settings.\n *\n * @param worktree - The worktree to check\n * @param settingsManager - SettingsManager instance for loading settings\n * @returns true if the worktree is the main worktree\n */\n async isMainWorktree(worktree: GitWorktree, settingsManager: SettingsManager): Promise<boolean> {\n const mainWorktreePath = await findMainWorktreePathWithSettings(worktree.path, settingsManager)\n return worktree.path === mainWorktreePath\n }\n\n /**\n * Check if a worktree is a PR worktree based on naming patterns\n * Ports: is_pr_worktree() from worktree-utils.sh\n */\n isPRWorktree(worktree: GitWorktree): boolean {\n return isPRBranch(worktree.branch)\n }\n\n /**\n * Get PR number from worktree branch name\n * Ports: get_pr_number_from_worktree() from worktree-utils.sh\n */\n getPRNumberFromWorktree(worktree: GitWorktree): number | null {\n return extractPRNumber(worktree.branch)\n }\n\n /**\n * Create a new worktree\n * Ports worktree creation logic from new-branch-workflow.sh\n * @returns The absolute path to the created worktree\n */\n async createWorktree(options: WorktreeCreateOptions): Promise<string> {\n // Validate inputs\n if (!options.branch) {\n throw new Error('Branch name is required')\n }\n\n // Ensure path is absolute\n const absolutePath = path.resolve(options.path)\n\n // Check if path already exists and handle force flag\n if (await fs.pathExists(absolutePath)) {\n if (!options.force) {\n throw new Error(`Path already exists: ${absolutePath}`)\n }\n // Remove existing directory if force is true\n await fs.remove(absolutePath)\n }\n\n // Build git worktree add command\n const args = ['worktree', 'add']\n\n if (options.createBranch) {\n args.push('-b', options.branch)\n }\n\n if (options.force) {\n args.push('--force')\n }\n\n args.push(absolutePath)\n\n // Add branch name if not creating new branch\n if (!options.createBranch) {\n args.push(options.branch)\n } else if (options.baseBranch) {\n args.push(options.baseBranch)\n }\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n return absolutePath\n }\n\n /**\n * Remove a worktree and optionally clean up associated files\n * Ports worktree removal logic from cleanup-worktree.sh\n * @returns A message describing what was done (for dry-run mode)\n */\n async removeWorktree(\n worktreePath: string,\n options: WorktreeCleanupOptions = {}\n ): Promise<string | void> {\n // Validate worktree exists - use porcelain format for consistent parsing\n const worktrees = await this.listWorktrees({ porcelain: true })\n const worktree = worktrees.find(wt => wt.path === worktreePath)\n\n if (!worktree) {\n // Add debug logging to help diagnose the issue\n const { logger } = await import('../utils/logger.js')\n logger.debug(`Looking for worktree path: ${worktreePath}`)\n logger.debug(`Found ${worktrees.length} worktrees:`)\n worktrees.forEach((wt, i) => {\n logger.debug(` ${i}: path=\"${wt.path}\", branch=\"${wt.branch}\"`)\n })\n throw new Error(`Worktree not found: ${worktreePath}`)\n }\n\n // Check for uncommitted changes unless force is specified\n if (!options.force && !options.dryRun) {\n const hasChanges = await hasUncommittedChanges(worktreePath)\n if (hasChanges) {\n throw new Error(`Worktree has uncommitted changes: ${worktreePath}. Use --force to override.`)\n }\n }\n\n if (options.dryRun) {\n const actions = ['Remove worktree registration']\n if (options.removeDirectory) actions.push('Remove directory from disk')\n if (options.removeBranch) actions.push(`Remove branch: ${worktree.branch}`)\n\n return `Would perform: ${actions.join(', ')}`\n }\n\n // Remove worktree registration\n const args = ['worktree', 'remove']\n if (options.force) args.push('--force')\n args.push(worktreePath)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n\n // Remove directory if requested\n if (options.removeDirectory && (await fs.pathExists(worktreePath))) {\n await fs.remove(worktreePath)\n }\n\n // Remove branch if requested and safe to do so\n if (options.removeBranch && !worktree.bare) {\n try {\n await executeGitCommand(['branch', '-D', worktree.branch], {\n cwd: this._workingDirectory,\n })\n } catch (error) {\n // Don't fail the whole operation if branch deletion fails\n // Just log a warning (caller can handle this)\n throw new Error(\n `Worktree removed but failed to delete branch ${worktree.branch}: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n }\n }\n }\n\n /**\n * Validate worktree state and integrity\n */\n async validateWorktree(worktreePath: string): Promise<WorktreeValidation> {\n const issues: string[] = []\n let existsOnDisk = false\n let isValidRepo = false\n let hasValidBranch = false\n\n try {\n // Check if path exists on disk\n existsOnDisk = await fs.pathExists(worktreePath)\n if (!existsOnDisk) {\n issues.push('Worktree directory does not exist on disk')\n }\n\n // Check if it's a valid Git repository\n if (existsOnDisk) {\n isValidRepo = await isValidGitRepo(worktreePath)\n if (!isValidRepo) {\n issues.push('Directory is not a valid Git repository')\n }\n }\n\n // Check if branch reference is valid\n if (isValidRepo) {\n const currentBranch = await getCurrentBranch(worktreePath)\n hasValidBranch = currentBranch !== null\n if (!hasValidBranch) {\n issues.push('Could not determine current branch')\n }\n }\n\n // Check if worktree is registered with Git\n const worktrees = await this.listWorktrees()\n const isRegistered = worktrees.some(wt => wt.path === worktreePath)\n if (!isRegistered) {\n issues.push('Worktree is not registered with Git')\n }\n } catch (error) {\n issues.push(`Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n\n return {\n isValid: issues.length === 0,\n issues,\n existsOnDisk,\n isValidRepo,\n hasValidBranch,\n }\n }\n\n /**\n * Get detailed status information for a worktree\n */\n async getWorktreeStatus(worktreePath: string): Promise<WorktreeStatus> {\n const statusOutput = await executeGitCommand(['status', '--porcelain=v1'], {\n cwd: worktreePath,\n })\n\n let modified = 0\n let staged = 0\n let deleted = 0\n let untracked = 0\n\n const lines = statusOutput.trim().split('\\n').filter(Boolean)\n for (const line of lines) {\n const status = line.substring(0, 2)\n if (status[0] === 'M' || status[1] === 'M') modified++\n if (status[0] === 'A' || status[0] === 'D' || status[0] === 'R') staged++\n if (status[0] === 'D' || status[1] === 'D') deleted++\n if (status === '??') untracked++\n }\n\n const currentBranch = (await getCurrentBranch(worktreePath)) ?? 'unknown'\n const detached = currentBranch === 'unknown'\n\n // Get ahead/behind information\n let ahead = 0\n let behind = 0\n try {\n const aheadBehindOutput = await executeGitCommand(\n ['rev-list', '--left-right', '--count', `origin/${currentBranch}...HEAD`],\n { cwd: worktreePath }\n )\n const parts = aheadBehindOutput.trim().split('\\t')\n const behindStr = parts[0]\n const aheadStr = parts[1]\n behind = behindStr ? parseInt(behindStr, 10) || 0 : 0\n ahead = aheadStr ? parseInt(aheadStr, 10) || 0 : 0\n } catch {\n // Ignore errors for ahead/behind calculation\n }\n\n return {\n modified,\n staged,\n deleted,\n untracked,\n hasChanges: modified + staged + deleted + untracked > 0,\n branch: currentBranch,\n detached,\n ahead,\n behind,\n }\n }\n\n /**\n * Generate a suggested worktree path for a branch\n */\n generateWorktreePath(\n branchName: string,\n customRoot?: string,\n options?: { isPR?: boolean; prNumber?: number; prefix?: string }\n ): string {\n const root = customRoot ?? this._workingDirectory\n return generateWorktreePath(branchName, root, options)\n }\n\n /**\n * Sanitize a branch name for use as a directory name\n * Replaces slashes with dashes and removes invalid filesystem characters\n * Ports logic from bash script line 593: ${BRANCH_NAME//\\\\//-}\n */\n sanitizeBranchName(branchName: string): string {\n return branchName\n .replace(/\\//g, '-') // Replace slashes with dashes\n .replace(/[^a-zA-Z0-9-]/g, '-') // Replace invalid chars (including underscores) with dashes\n .replace(/-+/g, '-') // Collapse multiple dashes\n .replace(/^-|-$/g, '') // Remove leading/trailing dashes\n .toLowerCase()\n }\n\n /**\n * Check if repository is in a valid state for worktree operations\n */\n async isRepoReady(): Promise<boolean> {\n try {\n const repoRoot = await getRepoRoot(this._workingDirectory)\n return repoRoot !== null\n } catch {\n return false\n }\n }\n\n /**\n * Get repository information\n */\n async getRepoInfo(): Promise<{\n root: string | null\n defaultBranch: string\n currentBranch: string | null\n }> {\n const root = await getRepoRoot(this._workingDirectory)\n const defaultBranch = await getDefaultBranch(this._workingDirectory)\n const currentBranch = await getCurrentBranch(this._workingDirectory)\n\n return {\n root,\n defaultBranch,\n currentBranch,\n }\n }\n\n /**\n * Prune stale worktree entries (worktrees that no longer exist on disk)\n */\n async pruneWorktrees(): Promise<void> {\n await executeGitCommand(['worktree', 'prune', '-v'], { cwd: this._workingDirectory })\n }\n\n /**\n * Lock a worktree to prevent it from being pruned or moved\n */\n async lockWorktree(worktreePath: string, reason?: string): Promise<void> {\n const args = ['worktree', 'lock', worktreePath]\n if (reason) args.push('--reason', reason)\n\n await executeGitCommand(args, { cwd: this._workingDirectory })\n }\n\n /**\n * Unlock a previously locked worktree\n */\n async unlockWorktree(worktreePath: string): Promise<void> {\n await executeGitCommand(['worktree', 'unlock', worktreePath], { cwd: this._workingDirectory })\n }\n\n /**\n * Find worktrees matching an identifier (branch name, path, or PR number)\n */\n async findWorktreesByIdentifier(identifier: string): Promise<GitWorktree[]> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n return worktrees.filter(\n wt =>\n wt.branch.includes(identifier) ||\n wt.path.includes(identifier) ||\n this.getPRNumberFromWorktree(wt)?.toString() === identifier\n )\n }\n\n /**\n * Find worktree for a specific issue number using exact pattern matching\n * Matches: issue-{N} at start OR after /, -, _ (but NOT issue-{N}X where X is a digit)\n * Supports patterns like: issue-44, feat/issue-44-feature, feat-issue-44, bugfix_issue-44, etc.\n * Avoids false matches like: tissue-44, myissue-44\n * Ports: find_existing_worktree() from bash script lines 131-165\n */\n async findWorktreeForIssue(issueNumber: string | number): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Pattern: starts with 'issue-{N}' OR has '/issue-{N}', '-issue-{N}', '_issue-{N}' but not 'issue-{N}{digit}'\n // Case-insensitive to handle Linear IDs (MARK-1 vs mark-1)\n const pattern = new RegExp(`(?:^|[/_-])issue-${issueNumber}(?:-|__|$)`, 'i')\n\n return worktrees.find(wt => pattern.test(wt.branch)) ?? null\n }\n\n /**\n * Find worktree for a specific PR by branch name\n * Ports: find_existing_worktree() for PR type from bash script lines 149-160\n */\n async findWorktreeForPR(prNumber: number, branchName: string): Promise<GitWorktree | null> {\n const worktrees = await this.listWorktrees({ porcelain: true })\n\n // Find by exact branch name match (prioritized)\n const byBranch = worktrees.find(wt => wt.branch === branchName)\n if (byBranch) return byBranch\n\n // Also check directory name pattern: *_pr_{N}\n const pathPattern = new RegExp(`_pr_${prNumber}$`)\n return worktrees.find(wt => pathPattern.test(wt.path)) ?? null\n }\n\n /**\n * Remove multiple worktrees\n * Returns a summary of successes and failures\n * Automatically filters out the main worktree\n *\n * @param worktrees - Array of worktrees to remove\n * @param settingsManager - SettingsManager instance for determining main worktree\n * @param options - Cleanup options\n */\n async removeWorktrees(\n worktrees: GitWorktree[],\n settingsManager: SettingsManager,\n options: WorktreeCleanupOptions = {}\n ): Promise<{\n successes: Array<{ worktree: GitWorktree }>\n failures: Array<{ worktree: GitWorktree; error: string }>\n skipped: Array<{ worktree: GitWorktree; reason: string }>\n }> {\n const successes: Array<{ worktree: GitWorktree }> = []\n const failures: Array<{ worktree: GitWorktree; error: string }> = []\n const skipped: Array<{ worktree: GitWorktree; reason: string }> = []\n\n for (const worktree of worktrees) {\n // Skip main worktree\n if (await this.isMainWorktree(worktree, settingsManager)) {\n skipped.push({ worktree, reason: 'Cannot remove main worktree' })\n continue\n }\n\n try {\n await this.removeWorktree(worktree.path, {\n ...options,\n removeDirectory: true,\n })\n successes.push({ worktree })\n } catch (error) {\n failures.push({\n worktree,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n\n return { successes, failures, skipped }\n }\n\n /**\n * Format worktree information for display\n */\n formatWorktree(worktree: GitWorktree): {\n title: string\n path: string\n commit: string\n } {\n const prNumber = this.getPRNumberFromWorktree(worktree)\n const prLabel = prNumber ? ` (PR #${prNumber})` : ''\n const bareLabel = worktree.bare ? ' [main]' : ''\n\n return {\n title: `${worktree.branch}${prLabel}${bareLabel}`,\n path: worktree.path,\n commit: worktree.commit.substring(0, 7),\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/IdentifierParser.ts
|
|
4
|
+
var IdentifierParser = class {
|
|
5
|
+
constructor(gitWorktreeManager) {
|
|
6
|
+
this.gitWorktreeManager = gitWorktreeManager;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parse identifier using pattern-based detection on existing worktrees.
|
|
10
|
+
* Does NOT make GitHub API calls - only checks local worktree patterns.
|
|
11
|
+
*
|
|
12
|
+
* @param identifier - The identifier to parse (e.g., "42", "#66", "ENG-123", "my-branch")
|
|
13
|
+
* @returns ParsedInput with type, number/branchName, and originalInput
|
|
14
|
+
* @throws Error if no matching worktree is found
|
|
15
|
+
*/
|
|
16
|
+
async parseForPatternDetection(identifier) {
|
|
17
|
+
const cleanId = identifier.replace(/^#/, "").trim();
|
|
18
|
+
const originalInput = identifier;
|
|
19
|
+
const numericMatch = cleanId.match(/^(\d+)$/);
|
|
20
|
+
if (numericMatch == null ? void 0 : numericMatch[1]) {
|
|
21
|
+
const number = parseInt(numericMatch[1], 10);
|
|
22
|
+
const prWorktree = await this.gitWorktreeManager.findWorktreeForPR(number, "");
|
|
23
|
+
if (prWorktree) {
|
|
24
|
+
return {
|
|
25
|
+
type: "pr",
|
|
26
|
+
number,
|
|
27
|
+
originalInput
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(number);
|
|
31
|
+
if (issueWorktree) {
|
|
32
|
+
return {
|
|
33
|
+
type: "issue",
|
|
34
|
+
number,
|
|
35
|
+
originalInput
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
39
|
+
}
|
|
40
|
+
const alphanumericMatch = cleanId.match(/^([A-Za-z]+-\d+)$/);
|
|
41
|
+
if (alphanumericMatch == null ? void 0 : alphanumericMatch[1]) {
|
|
42
|
+
const alphanumericId = alphanumericMatch[1];
|
|
43
|
+
const issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(alphanumericId);
|
|
44
|
+
if (issueWorktree) {
|
|
45
|
+
return {
|
|
46
|
+
type: "issue",
|
|
47
|
+
number: alphanumericId,
|
|
48
|
+
originalInput
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
52
|
+
}
|
|
53
|
+
const branchWorktree = await this.gitWorktreeManager.findWorktreeForBranch(cleanId);
|
|
54
|
+
if (branchWorktree) {
|
|
55
|
+
return {
|
|
56
|
+
type: "branch",
|
|
57
|
+
branchName: cleanId,
|
|
58
|
+
originalInput
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`No worktree found for identifier: ${identifier}`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
IdentifierParser
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=chunk-OXAM2WVC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/IdentifierParser.ts"],"sourcesContent":["import type { ParsedInput } from '../commands/start.js'\nimport type { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\n\n/**\n * IdentifierParser provides consistent identifier parsing across commands\n * using pattern-based detection without GitHub API calls.\n *\n * Detection Strategy:\n * 1. For numeric input (e.g., \"42\", \"#66\"):\n * - Check for PR worktree first (_pr_N pattern in path)\n * - Then check for issue worktree (issue-N pattern in branch)\n * 2. For non-numeric input:\n * - Treat as branch name and verify worktree exists\n *\n * This ensures:\n * - No unnecessary GitHub API calls\n * - Consistent behavior across finish/cleanup commands\n * - PR detection takes priority over issue detection\n */\nexport class IdentifierParser {\n\tconstructor(private gitWorktreeManager: GitWorktreeManager) {}\n\n\t/**\n\t * Parse identifier using pattern-based detection on existing worktrees.\n\t * Does NOT make GitHub API calls - only checks local worktree patterns.\n\t *\n\t * @param identifier - The identifier to parse (e.g., \"42\", \"#66\", \"ENG-123\", \"my-branch\")\n\t * @returns ParsedInput with type, number/branchName, and originalInput\n\t * @throws Error if no matching worktree is found\n\t */\n\tasync parseForPatternDetection(identifier: string): Promise<ParsedInput> {\n\t\t// Remove # prefix if present and trim whitespace\n\t\tconst cleanId = identifier.replace(/^#/, '').trim()\n\t\tconst originalInput = identifier\n\n\t\t// Check if input is numeric (GitHub-style issue/PR numbers)\n\t\tconst numericMatch = cleanId.match(/^(\\d+)$/)\n\n\t\tif (numericMatch?.[1]) {\n\t\t\tconst number = parseInt(numericMatch[1], 10)\n\n\t\t\t// Priority 1: Check for PR worktree (_pr_N pattern)\n\t\t\t// Pass empty string for branch name since we don't know it yet\n\t\t\tconst prWorktree = await this.gitWorktreeManager.findWorktreeForPR(number, '')\n\t\t\tif (prWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'pr',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Priority 2: Check for issue worktree (issue-N pattern)\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(number)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for numeric input\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Check if input is alphanumeric issue identifier (Linear/Jira-style: ABC-123, ENG-42)\n\t\tconst alphanumericMatch = cleanId.match(/^([A-Za-z]+-\\d+)$/)\n\n\t\tif (alphanumericMatch?.[1]) {\n\t\t\tconst alphanumericId = alphanumericMatch[1]\n\n\t\t\t// Check for issue worktree with alphanumeric identifier\n\t\t\tconst issueWorktree = await this.gitWorktreeManager.findWorktreeForIssue(alphanumericId)\n\t\t\tif (issueWorktree) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tnumber: alphanumericId,\n\t\t\t\t\toriginalInput,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No matching worktree found for alphanumeric identifier\n\t\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t\t}\n\n\t\t// Non-numeric/non-alphanumeric input: treat as branch name\n\t\tconst branchWorktree = await this.gitWorktreeManager.findWorktreeForBranch(cleanId)\n\t\tif (branchWorktree) {\n\t\t\treturn {\n\t\t\t\ttype: 'branch',\n\t\t\t\tbranchName: cleanId,\n\t\t\t\toriginalInput,\n\t\t\t}\n\t\t}\n\n\t\t// No matching worktree found for branch name\n\t\tthrow new Error(`No worktree found for identifier: ${identifier}`)\n\t}\n}\n"],"mappings":";;;AAmBO,IAAM,mBAAN,MAAuB;AAAA,EAC7B,YAAoB,oBAAwC;AAAxC;AAAA,EAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU7D,MAAM,yBAAyB,YAA0C;AAExE,UAAM,UAAU,WAAW,QAAQ,MAAM,EAAE,EAAE,KAAK;AAClD,UAAM,gBAAgB;AAGtB,UAAM,eAAe,QAAQ,MAAM,SAAS;AAE5C,QAAI,6CAAe,IAAI;AACtB,YAAM,SAAS,SAAS,aAAa,CAAC,GAAG,EAAE;AAI3C,YAAM,aAAa,MAAM,KAAK,mBAAmB,kBAAkB,QAAQ,EAAE;AAC7E,UAAI,YAAY;AACf,eAAO;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAGA,YAAM,gBAAgB,MAAM,KAAK,mBAAmB,qBAAqB,MAAM;AAC/E,UAAI,eAAe;AAClB,eAAO;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAGA,YAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,IAClE;AAGA,UAAM,oBAAoB,QAAQ,MAAM,mBAAmB;AAE3D,QAAI,uDAAoB,IAAI;AAC3B,YAAM,iBAAiB,kBAAkB,CAAC;AAG1C,YAAM,gBAAgB,MAAM,KAAK,mBAAmB,qBAAqB,cAAc;AACvF,UAAI,eAAe;AAClB,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AAGA,YAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,IAClE;AAGA,UAAM,iBAAiB,MAAM,KAAK,mBAAmB,sBAAsB,OAAO;AAClF,QAAI,gBAAgB;AACnB,aAAO;AAAA,QACN,MAAM;AAAA,QACN,YAAY;AAAA,QACZ;AAAA,MACD;AAAA,IACD;AAGA,UAAM,IAAI,MAAM,qCAAqC,UAAU,EAAE;AAAA,EAClE;AACD;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
GitWorktreeManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OAP6SASD.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -299,4 +299,4 @@ Please consult your shell's documentation for setting up custom completions.
|
|
|
299
299
|
export {
|
|
300
300
|
ShellCompletion
|
|
301
301
|
};
|
|
302
|
-
//# sourceMappingURL=chunk-
|
|
302
|
+
//# sourceMappingURL=chunk-PRE7KTM4.js.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildEnvSourceCommands
|
|
4
|
+
} from "./chunk-UJL4HI2R.js";
|
|
2
5
|
|
|
3
6
|
// src/utils/terminal.ts
|
|
4
7
|
import { execa } from "execa";
|
|
@@ -23,7 +26,7 @@ async function openTerminalWindow(options) {
|
|
|
23
26
|
);
|
|
24
27
|
}
|
|
25
28
|
const hasITerm2 = await detectITerm2();
|
|
26
|
-
const applescript = hasITerm2 ? buildITerm2SingleTabScript(options) : buildAppleScript(options);
|
|
29
|
+
const applescript = hasITerm2 ? await buildITerm2SingleTabScript(options) : await buildAppleScript(options);
|
|
27
30
|
try {
|
|
28
31
|
await execa("osascript", ["-e", applescript]);
|
|
29
32
|
if (!hasITerm2) {
|
|
@@ -35,7 +38,7 @@ async function openTerminalWindow(options) {
|
|
|
35
38
|
);
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
|
-
function buildAppleScript(options) {
|
|
41
|
+
async function buildAppleScript(options) {
|
|
39
42
|
const {
|
|
40
43
|
workspacePath,
|
|
41
44
|
command,
|
|
@@ -48,8 +51,12 @@ function buildAppleScript(options) {
|
|
|
48
51
|
if (workspacePath) {
|
|
49
52
|
commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
|
|
50
53
|
}
|
|
51
|
-
if (includeEnvSetup) {
|
|
52
|
-
|
|
54
|
+
if (includeEnvSetup && workspacePath) {
|
|
55
|
+
const sourceCommands = await buildEnvSourceCommands(
|
|
56
|
+
workspacePath,
|
|
57
|
+
async (p) => existsSync(p)
|
|
58
|
+
);
|
|
59
|
+
commands.push(...sourceCommands);
|
|
53
60
|
}
|
|
54
61
|
if (includePortExport && port !== void 0) {
|
|
55
62
|
commands.push(`export PORT=${port}`);
|
|
@@ -77,8 +84,8 @@ function escapePathForAppleScript(path) {
|
|
|
77
84
|
function escapeForAppleScript(command) {
|
|
78
85
|
return command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
79
86
|
}
|
|
80
|
-
function buildITerm2SingleTabScript(options) {
|
|
81
|
-
const command = buildCommandSequence(options);
|
|
87
|
+
async function buildITerm2SingleTabScript(options) {
|
|
88
|
+
const command = await buildCommandSequence(options);
|
|
82
89
|
let script = 'tell application id "com.googlecode.iterm2"\n';
|
|
83
90
|
script += " create window with default profile\n";
|
|
84
91
|
script += " set s1 to current session of current window\n\n";
|
|
@@ -99,7 +106,7 @@ function buildITerm2SingleTabScript(options) {
|
|
|
99
106
|
script += "end tell";
|
|
100
107
|
return script;
|
|
101
108
|
}
|
|
102
|
-
function buildCommandSequence(options) {
|
|
109
|
+
async function buildCommandSequence(options) {
|
|
103
110
|
const {
|
|
104
111
|
workspacePath,
|
|
105
112
|
command,
|
|
@@ -111,8 +118,12 @@ function buildCommandSequence(options) {
|
|
|
111
118
|
if (workspacePath) {
|
|
112
119
|
commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
|
|
113
120
|
}
|
|
114
|
-
if (includeEnvSetup) {
|
|
115
|
-
|
|
121
|
+
if (includeEnvSetup && workspacePath) {
|
|
122
|
+
const sourceCommands = await buildEnvSourceCommands(
|
|
123
|
+
workspacePath,
|
|
124
|
+
async (p) => existsSync(p)
|
|
125
|
+
);
|
|
126
|
+
commands.push(...sourceCommands);
|
|
116
127
|
}
|
|
117
128
|
if (includePortExport && port !== void 0) {
|
|
118
129
|
commands.push(`export PORT=${port}`);
|
|
@@ -123,7 +134,7 @@ function buildCommandSequence(options) {
|
|
|
123
134
|
const fullCommand = commands.join(" && ");
|
|
124
135
|
return ` ${fullCommand}`;
|
|
125
136
|
}
|
|
126
|
-
function buildITerm2MultiTabScript(optionsArray) {
|
|
137
|
+
async function buildITerm2MultiTabScript(optionsArray) {
|
|
127
138
|
if (optionsArray.length < 2) {
|
|
128
139
|
throw new Error("buildITerm2MultiTabScript requires at least 2 terminal options");
|
|
129
140
|
}
|
|
@@ -134,7 +145,7 @@ function buildITerm2MultiTabScript(optionsArray) {
|
|
|
134
145
|
if (!options1) {
|
|
135
146
|
throw new Error("First terminal option is undefined");
|
|
136
147
|
}
|
|
137
|
-
const command1 = buildCommandSequence(options1);
|
|
148
|
+
const command1 = await buildCommandSequence(options1);
|
|
138
149
|
script += " set s1 to current session of newWindow\n\n";
|
|
139
150
|
if (options1.backgroundColor) {
|
|
140
151
|
const { r, g, b } = options1.backgroundColor;
|
|
@@ -154,7 +165,7 @@ function buildITerm2MultiTabScript(optionsArray) {
|
|
|
154
165
|
if (!options) {
|
|
155
166
|
throw new Error(`Terminal option at index ${i} is undefined`);
|
|
156
167
|
}
|
|
157
|
-
const command = buildCommandSequence(options);
|
|
168
|
+
const command = await buildCommandSequence(options);
|
|
158
169
|
const sessionVar = `s${i + 1}`;
|
|
159
170
|
script += " tell newWindow\n";
|
|
160
171
|
script += ` set newTab${i} to (create tab with default profile)
|
|
@@ -193,7 +204,7 @@ async function openMultipleTerminalWindows(optionsArray) {
|
|
|
193
204
|
}
|
|
194
205
|
const hasITerm2 = await detectITerm2();
|
|
195
206
|
if (hasITerm2) {
|
|
196
|
-
const applescript = buildITerm2MultiTabScript(optionsArray);
|
|
207
|
+
const applescript = await buildITerm2MultiTabScript(optionsArray);
|
|
197
208
|
try {
|
|
198
209
|
await execa("osascript", ["-e", applescript]);
|
|
199
210
|
} catch (error) {
|
|
@@ -214,15 +225,10 @@ async function openMultipleTerminalWindows(optionsArray) {
|
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
227
|
}
|
|
217
|
-
async function openDualTerminalWindow(options1, options2) {
|
|
218
|
-
await openMultipleTerminalWindows([options1, options2]);
|
|
219
|
-
}
|
|
220
228
|
|
|
221
229
|
export {
|
|
222
|
-
detectPlatform,
|
|
223
230
|
detectITerm2,
|
|
224
231
|
openTerminalWindow,
|
|
225
|
-
openMultipleTerminalWindows
|
|
226
|
-
openDualTerminalWindow
|
|
232
|
+
openMultipleTerminalWindows
|
|
227
233
|
};
|
|
228
|
-
//# sourceMappingURL=chunk-
|
|
234
|
+
//# sourceMappingURL=chunk-RW54ZMBM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/terminal.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport type { Platform } from '../types/index.js'\nimport { buildEnvSourceCommands } from './env.js'\n\nexport interface TerminalWindowOptions {\n\tworkspacePath?: string\n\tcommand?: string\n\tbackgroundColor?: { r: number; g: number; b: number }\n\tport?: number\n\tincludeEnvSetup?: boolean // source .env\n\tincludePortExport?: boolean // export PORT=<port>\n\ttitle?: string // Terminal tab title\n}\n\n/**\n * Detect current platform\n */\nexport function detectPlatform(): Platform {\n\tconst platform = process.platform\n\tif (platform === 'darwin') return 'darwin'\n\tif (platform === 'linux') return 'linux'\n\tif (platform === 'win32') return 'win32'\n\treturn 'unsupported'\n}\n\n/**\n * Detect if iTerm2 is installed on macOS\n * Returns false on non-macOS platforms\n */\nexport async function detectITerm2(): Promise<boolean> {\n\tconst platform = detectPlatform()\n\tif (platform !== 'darwin') return false\n\n\t// Check if iTerm.app exists at standard location\n\treturn existsSync('/Applications/iTerm.app')\n}\n\n/**\n * Open new terminal window with specified options\n * Currently supports macOS only\n */\nexport async function openTerminalWindow(\n\toptions: TerminalWindowOptions\n): Promise<void> {\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// Detect if iTerm2 is available\n\tconst hasITerm2 = await detectITerm2()\n\n\t// Build appropriate AppleScript based on terminal availability\n\tconst applescript = hasITerm2\n\t\t? await buildITerm2SingleTabScript(options)\n\t\t: await buildAppleScript(options)\n\n\ttry {\n\t\tawait execa('osascript', ['-e', applescript])\n\n\t\t// Activate the appropriate terminal application (only needed for Terminal.app)\n\t\t// iTerm2 script includes its own activation\n\t\tif (!hasITerm2) {\n\t\t\tawait execa('osascript', ['-e', 'tell application \"Terminal\" to activate'])\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to open terminal window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t)\n\t}\n}\n\n/**\n * Build AppleScript for macOS Terminal.app\n */\nasync function buildAppleScript(options: TerminalWindowOptions): Promise<string> {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tbackgroundColor,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\t// Build command sequence\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source all dotenv-flow files\n\tif (includeEnvSetup && workspacePath) {\n\t\tconst sourceCommands = await buildEnvSourceCommands(\n\t\t\tworkspacePath,\n\t\t\tasync (p) => existsSync(p)\n\t\t)\n\t\tcommands.push(...sourceCommands)\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\t// Most shells (bash/zsh) ignore commands starting with space when HISTCONTROL=ignorespace\n\tconst historyFreeCommand = ` ${fullCommand}`\n\n\t// Build AppleScript\n\tlet script = `tell application \"Terminal\"\\n`\n\tscript += ` set newTab to do script \"${escapeForAppleScript(historyFreeCommand)}\"\\n`\n\n\t// Apply background color if provided\n\tif (backgroundColor) {\n\t\tconst { r, g, b } = backgroundColor\n\t\t// Convert 8-bit RGB (0-255) to 16-bit RGB (0-65535)\n\t\tscript += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\tscript += `end tell`\n\n\treturn script\n}\n\n/**\n * Escape path for AppleScript string\n * Single quotes in path need special escaping\n */\nfunction escapePathForAppleScript(path: string): string {\n\t// Replace single quote with '\\''\n\treturn path.replace(/'/g, \"'\\\\''\")\n}\n\n/**\n * Escape command for AppleScript do script\n * Must handle double quotes and backslashes\n */\nfunction escapeForAppleScript(command: string): string {\n\treturn (\n\t\tcommand\n\t\t\t.replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n\t\t\t.replace(/\"/g, '\\\\\"') // Escape double quotes\n\t)\n}\n\n/**\n * Build iTerm2 AppleScript for single tab\n */\nasync function buildITerm2SingleTabScript(options: TerminalWindowOptions): Promise<string> {\n\tconst command = await buildCommandSequence(options)\n\n\tlet script = 'tell application id \"com.googlecode.iterm2\"\\n'\n\tscript += ' create window with default profile\\n'\n\tscript += ' set s1 to current session of current window\\n\\n'\n\n\t// Set background color\n\tif (options.backgroundColor) {\n\t\tconst { r, g, b } = options.backgroundColor\n\t\tscript += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\t// Execute command\n\tscript += ` tell s1 to write text \"${escapeForAppleScript(command)}\"\\n\\n`\n\n\t// Set session name (tab title)\n\tif (options.title) {\n\t\tscript += ` set name of s1 to \"${escapeForAppleScript(options.title)}\"\\n\\n`\n\t}\n\n\t// Activate iTerm2\n\tscript += ' activate\\n'\n\tscript += 'end tell'\n\n\treturn script\n}\n\n/**\n * Build command sequence for terminal\n */\nasync function buildCommandSequence(options: TerminalWindowOptions): Promise<string> {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source all dotenv-flow files\n\tif (includeEnvSetup && workspacePath) {\n\t\tconst sourceCommands = await buildEnvSourceCommands(\n\t\t\tworkspacePath,\n\t\t\tasync (p) => existsSync(p)\n\t\t)\n\t\tcommands.push(...sourceCommands)\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\treturn ` ${fullCommand}`\n}\n\n/**\n * Build iTerm2 AppleScript for multiple tabs (2+) in single window\n */\nasync function buildITerm2MultiTabScript(\n\toptionsArray: TerminalWindowOptions[]\n): Promise<string> {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('buildITerm2MultiTabScript requires at least 2 terminal options')\n\t}\n\n\tlet script = 'tell application id \"com.googlecode.iterm2\"\\n'\n\tscript += ' create window with default profile\\n'\n\tscript += ' set newWindow to current window\\n'\n\n\t// First tab\n\tconst options1 = optionsArray[0]\n\tif (!options1) {\n\t\tthrow new Error('First terminal option is undefined')\n\t}\n\tconst command1 = await buildCommandSequence(options1)\n\n\tscript += ' set s1 to current session of newWindow\\n\\n'\n\n\t// Set background color for first tab\n\tif (options1.backgroundColor) {\n\t\tconst { r, g, b } = options1.backgroundColor\n\t\tscript += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\t// Execute command in first tab\n\tscript += ` tell s1 to write text \"${escapeForAppleScript(command1)}\"\\n\\n`\n\n\t// Set tab title for first tab\n\tif (options1.title) {\n\t\tscript += ` set name of s1 to \"${escapeForAppleScript(options1.title)}\"\\n\\n`\n\t}\n\n\t// Subsequent tabs (2, 3, ...)\n\tfor (let i = 1; i < optionsArray.length; i++) {\n\t\tconst options = optionsArray[i]\n\t\tif (!options) {\n\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t}\n\t\tconst command = await buildCommandSequence(options)\n\t\tconst sessionVar = `s${i + 1}`\n\n\t\t// Create tab\n\t\tscript += ' tell newWindow\\n'\n\t\tscript += ` set newTab${i} to (create tab with default profile)\\n`\n\t\tscript += ' end tell\\n'\n\t\tscript += ` set ${sessionVar} to current session of newTab${i}\\n\\n`\n\n\t\t// Set background color\n\t\tif (options.backgroundColor) {\n\t\t\tconst { r, g, b } = options.backgroundColor\n\t\t\tscript += ` set background color of ${sessionVar} to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t\t}\n\n\t\t// Execute command\n\t\tscript += ` tell ${sessionVar} to write text \"${escapeForAppleScript(command)}\"\\n\\n`\n\n\t\t// Set tab title\n\t\tif (options.title) {\n\t\t\tscript += ` set name of ${sessionVar} to \"${escapeForAppleScript(options.title)}\"\\n\\n`\n\t\t}\n\t}\n\n\t// Activate iTerm2\n\tscript += ' activate\\n'\n\tscript += 'end tell'\n\n\treturn script\n}\n\n/**\n * Open multiple terminal windows/tabs (2+) with specified options\n * If iTerm2 is available on macOS, creates single window with multiple tabs\n * Otherwise falls back to multiple separate Terminal.app windows\n */\nexport async function openMultipleTerminalWindows(\n\toptionsArray: TerminalWindowOptions[]\n): Promise<void> {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('openMultipleTerminalWindows requires at least 2 terminal options. Use openTerminalWindow for single terminal.')\n\t}\n\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// Detect if iTerm2 is available\n\tconst hasITerm2 = await detectITerm2()\n\n\tif (hasITerm2) {\n\t\t// Use iTerm2 with multiple tabs in single window\n\t\tconst applescript = await buildITerm2MultiTabScript(optionsArray)\n\n\t\ttry {\n\t\t\tawait execa('osascript', ['-e', applescript])\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to open iTerm2 window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t} else {\n\t\t// Fall back to multiple Terminal.app windows\n\t\tfor (let i = 0; i < optionsArray.length; i++) {\n\t\t\tconst options = optionsArray[i]\n\t\t\tif (!options) {\n\t\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t\t}\n\t\t\tawait openTerminalWindow(options)\n\n\t\t\t// Brief pause between terminals (except after last one)\n\t\t\tif (i < optionsArray.length - 1) {\n\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 1000))\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Open dual terminal windows/tabs with specified options\n * If iTerm2 is available on macOS, creates single window with two tabs\n * Otherwise falls back to two separate Terminal.app windows\n */\nexport async function openDualTerminalWindow(\n\toptions1: TerminalWindowOptions,\n\toptions2: TerminalWindowOptions\n): Promise<void> {\n\t// Delegate to openMultipleTerminalWindows for consistency\n\tawait openMultipleTerminalWindows([options1, options2])\n}\n"],"mappings":"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","names":[]}
|
|
@@ -18,7 +18,7 @@ var WorkflowPermissionSchema = z.object({
|
|
|
18
18
|
noVerify: z.boolean().optional().describe("Skip pre-commit hooks (--no-verify) when committing during finish workflow"),
|
|
19
19
|
startIde: z.boolean().default(true).describe("Launch IDE (code) when starting this workflow type"),
|
|
20
20
|
startDevServer: z.boolean().default(true).describe("Launch development server when starting this workflow type"),
|
|
21
|
-
startAiAgent: z.boolean().default(true).describe("Launch Claude
|
|
21
|
+
startAiAgent: z.boolean().default(true).describe("Launch Claude Code agent when starting this workflow type"),
|
|
22
22
|
startTerminal: z.boolean().default(false).describe("Launch terminal window without dev server when starting this workflow type")
|
|
23
23
|
});
|
|
24
24
|
var WorkflowPermissionSchemaNoDefaults = z.object({
|
|
@@ -26,7 +26,7 @@ var WorkflowPermissionSchemaNoDefaults = z.object({
|
|
|
26
26
|
noVerify: z.boolean().optional().describe("Skip pre-commit hooks (--no-verify) when committing during finish workflow"),
|
|
27
27
|
startIde: z.boolean().optional().describe("Launch IDE (code) when starting this workflow type"),
|
|
28
28
|
startDevServer: z.boolean().optional().describe("Launch development server when starting this workflow type"),
|
|
29
|
-
startAiAgent: z.boolean().optional().describe("Launch Claude
|
|
29
|
+
startAiAgent: z.boolean().optional().describe("Launch Claude Code agent when starting this workflow type"),
|
|
30
30
|
startTerminal: z.boolean().optional().describe("Launch terminal window without dev server when starting this workflow type")
|
|
31
31
|
});
|
|
32
32
|
var WorkflowsSettingsSchema = z.object({
|
|
@@ -67,7 +67,7 @@ var DatabaseProvidersSettingsSchema = z.object({
|
|
|
67
67
|
var IloomSettingsSchema = z.object({
|
|
68
68
|
mainBranch: z.string().min(1, "Settings 'mainBranch' cannot be empty").optional().describe("Name of the main/primary branch for the repository"),
|
|
69
69
|
sourceEnvOnStart: z.boolean().default(false).describe(
|
|
70
|
-
"Source .env
|
|
70
|
+
"Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). Files are sourced in precedence order so later files override earlier ones. NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify ALL your .env.* files do not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
|
|
71
71
|
),
|
|
72
72
|
worktreePrefix: z.string().optional().refine(
|
|
73
73
|
(val) => {
|
|
@@ -123,7 +123,7 @@ var IloomSettingsSchema = z.object({
|
|
|
123
123
|
var IloomSettingsSchemaNoDefaults = z.object({
|
|
124
124
|
mainBranch: z.string().min(1, "Settings 'mainBranch' cannot be empty").optional().describe("Name of the main/primary branch for the repository"),
|
|
125
125
|
sourceEnvOnStart: z.boolean().optional().describe(
|
|
126
|
-
"Source .env
|
|
126
|
+
"Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). Files are sourced in precedence order so later files override earlier ones. NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify ALL your .env.* files do not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
|
|
127
127
|
),
|
|
128
128
|
worktreePrefix: z.string().optional().refine(
|
|
129
129
|
(val) => {
|
|
@@ -393,4 +393,4 @@ export {
|
|
|
393
393
|
IloomSettingsSchemaNoDefaults,
|
|
394
394
|
SettingsManager
|
|
395
395
|
};
|
|
396
|
-
//# sourceMappingURL=chunk-
|
|
396
|
+
//# sourceMappingURL=chunk-UCZ24SUE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/SettingsManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport path from 'path'\nimport os from 'os'\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 Code 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 * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const WorkflowPermissionSchemaNoDefaults = 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.optional()\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch Claude Code agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.optional()\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 * Non-defaulting variant for pre-merge validation\n */\nexport const WorkflowsSettingsSchemaNoDefaults = z\n\t.object({\n\t\tissue: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tpr: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tregular: WorkflowPermissionSchemaNoDefaults.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 * Non-defaulting variant for pre-merge validation\n */\nexport const CapabilitiesSettingsSchemaNoDefaults = 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.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\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\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\tprovider: z.enum(['github', 'linear']).optional().default('github').describe('Issue tracker provider (github, linear)'),\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\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\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\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf'])\n\t\t\t\t.default('vscode')\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, and Windsurf. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf).'\n\t\t),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const IloomSettingsSchemaNoDefaults = 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\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\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: WorkflowsSettingsSchemaNoDefaults.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: CapabilitiesSettingsSchemaNoDefaults.describe('Project capability configurations'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'linear']).optional().describe('Issue tracker provider (github, linear)'),\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\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\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']).optional(),\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\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf'])\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, and Windsurf. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf).'\n\t\t),\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 IDE settings derived from Zod schema\n */\nexport type IdeSettings = z.infer<typeof IloomSettingsSchema>['ide']\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 global, project, and local sources with proper precedence\n\t * Merge hierarchy (lowest to highest priority):\n\t * 1. Global settings (~/.config/iloom-ai/settings.json)\n\t * 2. Project settings (<PROJECT_ROOT>/.iloom/settings.json)\n\t * 3. Local settings (<PROJECT_ROOT>/.iloom/settings.local.json)\n\t * 4. CLI overrides (--set flags)\n\t * Returns empty object if all 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 global settings (lowest priority)\n\t\tconst globalSettings = await this.loadGlobalSettingsFile()\n\t\tconst globalSettingsPath = this.getGlobalSettingsPath()\n\t\tlogger.debug(`🌍 Global settings from ${globalSettingsPath}:`, JSON.stringify(globalSettings, null, 2))\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 > globalSettings\n\t\tlet merged = this.mergeSettings(this.mergeSettings(globalSettings, baseSettings), localSettings)\n\t\tlogger.debug('🔄 After merging global + 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 * Uses non-defaulting schema to prevent polluting partial settings with defaults before merge\n\t */\n\tprivate async loadSettingsFile(\n\t\tprojectRoot: string,\n\t\tfilename: string,\n\t): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\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// Basic type checking - ensure it's an object, but don't validate schema completeness\n\t\t\t// Individual files may be incomplete (e.g., Linear config split between files)\n\t\t\t// Final validation will happen on the merged result in loadSettings()\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Settings validation failed at ${filename}:\\n - root: Expected object, received ${typeof parsed}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn parsed as z.infer<typeof IloomSettingsSchemaNoDefaults>\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> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t\toverride: Partial<IloomSettings> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t): IloomSettings {\n\t\t// Use deepmerge with array replacement (not concatenation)\n\t\t// Type assertion is safe because the merged result will be validated with IloomSettingsSchema\n\t\t// which applies all the defaults after merging\n\t\treturn deepmerge(base as Record<string, unknown>, override as Record<string, unknown>, {\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 global config directory path (~/.config/iloom-ai)\n\t */\n\tprivate getGlobalConfigDir(): string {\n\t\treturn path.join(os.homedir(), '.config', 'iloom-ai')\n\t}\n\n\t/**\n\t * Get global settings file path (~/.config/iloom-ai/settings.json)\n\t */\n\tprivate getGlobalSettingsPath(): string {\n\t\treturn path.join(this.getGlobalConfigDir(), 'settings.json')\n\t}\n\n\t/**\n\t * Load and parse global settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t * Warns but returns empty object on validation/parse errors (graceful degradation)\n\t */\n\tprivate async loadGlobalSettingsFile(): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\n\t\tconst settingsPath = this.getGlobalSettingsPath()\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\tlogger.warn(\n\t\t\t\t\t`Failed to parse global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}. Ignoring global settings.`,\n\t\t\t\t)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Validate with non-defaulting schema\n\t\t\ttry {\n\t\t\t\tconst validated = IloomSettingsSchemaNoDefaults.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, 'global settings')\n\t\t\t\t\tlogger.warn(`${errorMsg.message}. Ignoring global settings.`)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.warn(`Validation error in global settings: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\t\t}\n\t\t\t\treturn {}\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 global settings file found at ${settingsPath}`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Other file system errors - warn and continue\n\t\t\tlogger.warn(`Error reading global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\treturn {}\n\t\t}\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"]}
|
|
@@ -15,7 +15,7 @@ var ClaudeBranchNameStrategy = class {
|
|
|
15
15
|
this.claudeModel = claudeModel;
|
|
16
16
|
}
|
|
17
17
|
async generate(issueNumber, title) {
|
|
18
|
-
const { generateBranchName } = await import("./claude-
|
|
18
|
+
const { generateBranchName } = await import("./claude-KIZYXTSG.js");
|
|
19
19
|
return generateBranchName(title, issueNumber, this.claudeModel);
|
|
20
20
|
}
|
|
21
21
|
};
|
|
@@ -52,4 +52,4 @@ export {
|
|
|
52
52
|
ClaudeBranchNameStrategy,
|
|
53
53
|
DefaultBranchNamingService
|
|
54
54
|
};
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
55
|
+
//# sourceMappingURL=chunk-UERERX6M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/BranchNamingService.ts"],"sourcesContent":["import type { BranchNameStrategy, BranchGenerationOptions } from '../types/branch-naming.js'\nimport { logger } from '../utils/logger.js'\n\n// ============================================\n// Strategy Classes\n// ============================================\n\n/**\n * Simple branch naming strategy\n * Format: feat/issue-{number}__{slug}\n */\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: string | number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}__${slug}`\n\t}\n}\n\n/**\n * Claude Code-powered branch naming strategy\n * Uses Claude CLI to generate semantic branch names\n */\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: string | number, title: string): Promise<string> {\n\t\t// Dynamic import to allow mocking in tests\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// ============================================\n// Service Interface and Implementation\n// ============================================\n\n/**\n * Service interface for branch name generation\n * Provides strategy management and generation capabilities\n */\nexport interface BranchNamingService {\n\tgenerateBranchName(options: BranchGenerationOptions): Promise<string>\n\tsetDefaultStrategy(strategy: BranchNameStrategy): void\n\tgetDefaultStrategy(): BranchNameStrategy\n}\n\n/**\n * Default implementation of BranchNamingService\n * Supports multiple naming strategies with configurable defaults\n */\nexport class DefaultBranchNamingService implements BranchNamingService {\n\tprivate defaultStrategy: BranchNameStrategy\n\n\tconstructor(options?: {\n\t\tstrategy?: BranchNameStrategy\n\t\tuseClaude?: boolean\n\t\tclaudeModel?: string\n\t}) {\n\t\t// Set up default strategy based on options\n\t\tif (options?.strategy) {\n\t\t\tthis.defaultStrategy = options.strategy\n\t\t} else if (options?.useClaude !== false) {\n\t\t\tthis.defaultStrategy = new ClaudeBranchNameStrategy(options?.claudeModel)\n\t\t} else {\n\t\t\tthis.defaultStrategy = new SimpleBranchNameStrategy()\n\t\t}\n\t}\n\n\tasync generateBranchName(options: BranchGenerationOptions): 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.defaultStrategy\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\tsetDefaultStrategy(strategy: BranchNameStrategy): void {\n\t\tthis.defaultStrategy = strategy\n\t}\n\n\tgetDefaultStrategy(): BranchNameStrategy {\n\t\treturn this.defaultStrategy\n\t}\n}\n"],"mappings":";;;;;;AAWO,IAAM,2BAAN,MAA6D;AAAA,EACnE,MAAM,SAAS,aAA8B,OAAgC;AAE5E,UAAM,OAAO,MACX,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,UAAU,EAAE,EACpB,UAAU,GAAG,EAAE;AAEjB,WAAO,cAAc,WAAW,KAAK,IAAI;AAAA,EAC1C;AACD;AAMO,IAAM,2BAAN,MAA6D;AAAA,EACnE,YAAoB,cAAc,SAAS;AAAvB;AAAA,EAAwB;AAAA,EAE5C,MAAM,SAAS,aAA8B,OAAgC;AAE5E,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,sBAAoB;AAChE,WAAO,mBAAmB,OAAO,aAAa,KAAK,WAAW;AAAA,EAC/D;AACD;AAoBO,IAAM,6BAAN,MAAgE;AAAA,EAGtE,YAAY,SAIT;AAEF,QAAI,mCAAS,UAAU;AACtB,WAAK,kBAAkB,QAAQ;AAAA,IAChC,YAAW,mCAAS,eAAc,OAAO;AACxC,WAAK,kBAAkB,IAAI,yBAAyB,mCAAS,WAAW;AAAA,IACzE,OAAO;AACN,WAAK,kBAAkB,IAAI,yBAAyB;AAAA,IACrD;AAAA,EACD;AAAA,EAEA,MAAM,mBAAmB,SAAmD;AAC3E,UAAM,EAAE,aAAa,OAAO,SAAS,IAAI;AAGzC,UAAM,eAAe,YAAY,KAAK;AAEtC,WAAO,MAAM,0BAA0B;AAAA,MACtC;AAAA,MACA;AAAA,MACA,UAAU,aAAa,YAAY;AAAA,IACpC,CAAC;AAED,WAAO,aAAa,SAAS,aAAa,KAAK;AAAA,EAChD;AAAA,EAEA,mBAAmB,UAAoC;AACtD,SAAK,kBAAkB;AAAA,EACxB;AAAA,EAEA,qBAAyC;AACxC,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
|