@iloom/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +228 -905
  2. package/dist/{BranchNamingService-3OQPRSWT.js → BranchNamingService-OMWKUYMM.js} +2 -2
  3. package/dist/ClaudeContextManager-3VXA6UPR.js +13 -0
  4. package/dist/ClaudeService-6CPK43N4.js +12 -0
  5. package/dist/{LoomLauncher-FLEMBCSQ.js → LoomLauncher-JF7JZMTZ.js} +5 -5
  6. package/dist/README.md +228 -905
  7. package/dist/{SettingsManager-WHHFGSL7.js → SettingsManager-ZCWJ56WP.js} +2 -2
  8. package/dist/{chunk-MAVL6PJF.js → chunk-4BGK7T6X.js} +3 -3
  9. package/dist/{chunk-VAYCCUXW.js → chunk-4E4LD3QR.js} +2 -2
  10. package/dist/{chunk-5EF7Z346.js → chunk-HBYZH6GD.js} +11 -9
  11. package/dist/chunk-HBYZH6GD.js.map +1 -0
  12. package/dist/{chunk-3KATJIKO.js → chunk-INW24J2W.js} +2 -2
  13. package/dist/chunk-INW24J2W.js.map +1 -0
  14. package/dist/{chunk-AWOFAD5O.js → chunk-IXKLYTWO.js} +4 -4
  15. package/dist/{chunk-ML3NRPNB.js → chunk-O5OH5MRX.js} +3 -3
  16. package/dist/chunk-O5OH5MRX.js.map +1 -0
  17. package/dist/{chunk-4HHRTA7Q.js → chunk-TSKY3JI7.js} +3 -3
  18. package/dist/{chunk-4HHRTA7Q.js.map → chunk-TSKY3JI7.js.map} +1 -1
  19. package/dist/{chunk-FIAT22G7.js → chunk-U5QDY7ZD.js} +3 -3
  20. package/dist/{chunk-C5QCTEQK.js → chunk-XPKDPZ5D.js} +2 -2
  21. package/dist/{chunk-47KSHUCR.js → chunk-ZBQVSHVT.js} +4 -4
  22. package/dist/chunk-ZBQVSHVT.js.map +1 -0
  23. package/dist/{chunk-XXV3UFZL.js → chunk-ZWFBBPJI.js} +5 -5
  24. package/dist/{chunk-XXV3UFZL.js.map → chunk-ZWFBBPJI.js.map} +1 -1
  25. package/dist/{claude-GOP6PFC7.js → claude-LUZ35IMK.js} +2 -2
  26. package/dist/{cleanup-7RWLBSLE.js → cleanup-3MONU4PU.js} +10 -10
  27. package/dist/{cleanup-7RWLBSLE.js.map → cleanup-3MONU4PU.js.map} +1 -1
  28. package/dist/cli.js +32 -32
  29. package/dist/cli.js.map +1 -1
  30. package/dist/{contribute-BS2L4FZR.js → contribute-UWJAGIG7.js} +2 -2
  31. package/dist/{feedback-N4ECWIPF.js → feedback-W3BXTGIM.js} +4 -4
  32. package/dist/{git-TDXKRTXM.js → git-34Z6QVDS.js} +2 -2
  33. package/dist/{ignite-VM64QO3J.js → ignite-KVJEFXNO.js} +5 -5
  34. package/dist/index.js +3 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/{init-G3T64SC4.js → init-L55Q73H4.js} +5 -5
  37. package/dist/{open-KXDXEKRZ.js → open-LNRZL3UU.js} +4 -4
  38. package/dist/prompts/init-prompt.txt +3 -3
  39. package/dist/{rebase-Q7GMM7EI.js → rebase-C4WNCVGM.js} +6 -6
  40. package/dist/{run-PAWJJCSX.js → run-IOGNIOYN.js} +4 -4
  41. package/dist/schema/settings.schema.json +3 -3
  42. package/dist/{test-git-3WDLNQCA.js → test-git-J7I5MFYH.js} +3 -3
  43. package/dist/{test-prefix-EVGAWAJW.js → test-prefix-ZCONBCBX.js} +3 -3
  44. package/package.json +2 -2
  45. package/dist/ClaudeContextManager-MUQSDY2E.js +0 -13
  46. package/dist/ClaudeService-HG4VQ7AW.js +0 -12
  47. package/dist/chunk-3KATJIKO.js.map +0 -1
  48. package/dist/chunk-47KSHUCR.js.map +0 -1
  49. package/dist/chunk-5EF7Z346.js.map +0 -1
  50. package/dist/chunk-ML3NRPNB.js.map +0 -1
  51. /package/dist/{BranchNamingService-3OQPRSWT.js.map → BranchNamingService-OMWKUYMM.js.map} +0 -0
  52. /package/dist/{ClaudeContextManager-MUQSDY2E.js.map → ClaudeContextManager-3VXA6UPR.js.map} +0 -0
  53. /package/dist/{ClaudeService-HG4VQ7AW.js.map → ClaudeService-6CPK43N4.js.map} +0 -0
  54. /package/dist/{LoomLauncher-FLEMBCSQ.js.map → LoomLauncher-JF7JZMTZ.js.map} +0 -0
  55. /package/dist/{SettingsManager-WHHFGSL7.js.map → SettingsManager-ZCWJ56WP.js.map} +0 -0
  56. /package/dist/{chunk-MAVL6PJF.js.map → chunk-4BGK7T6X.js.map} +0 -0
  57. /package/dist/{chunk-VAYCCUXW.js.map → chunk-4E4LD3QR.js.map} +0 -0
  58. /package/dist/{chunk-AWOFAD5O.js.map → chunk-IXKLYTWO.js.map} +0 -0
  59. /package/dist/{chunk-FIAT22G7.js.map → chunk-U5QDY7ZD.js.map} +0 -0
  60. /package/dist/{chunk-C5QCTEQK.js.map → chunk-XPKDPZ5D.js.map} +0 -0
  61. /package/dist/{claude-GOP6PFC7.js.map → claude-LUZ35IMK.js.map} +0 -0
  62. /package/dist/{contribute-BS2L4FZR.js.map → contribute-UWJAGIG7.js.map} +0 -0
  63. /package/dist/{feedback-N4ECWIPF.js.map → feedback-W3BXTGIM.js.map} +0 -0
  64. /package/dist/{git-TDXKRTXM.js.map → git-34Z6QVDS.js.map} +0 -0
  65. /package/dist/{ignite-VM64QO3J.js.map → ignite-KVJEFXNO.js.map} +0 -0
  66. /package/dist/{init-G3T64SC4.js.map → init-L55Q73H4.js.map} +0 -0
  67. /package/dist/{open-KXDXEKRZ.js.map → open-LNRZL3UU.js.map} +0 -0
  68. /package/dist/{rebase-Q7GMM7EI.js.map → rebase-C4WNCVGM.js.map} +0 -0
  69. /package/dist/{run-PAWJJCSX.js.map → run-IOGNIOYN.js.map} +0 -0
  70. /package/dist/{test-git-3WDLNQCA.js.map → test-git-J7I5MFYH.js.map} +0 -0
  71. /package/dist/{test-prefix-EVGAWAJW.js.map → test-prefix-ZCONBCBX.js.map} +0 -0
@@ -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 .env file when launching terminal processes (Claude, dev server, terminal). ' +\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 your .env file does 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 .env file when launching terminal processes (Claude, dev server, terminal). ' +\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 your .env file does 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"]}
@@ -11,7 +11,7 @@ import {
11
11
  isPRBranch,
12
12
  isValidGitRepo,
13
13
  parseWorktreeList
14
- } from "./chunk-MAVL6PJF.js";
14
+ } from "./chunk-4BGK7T6X.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-4HHRTA7Q.js.map
389
+ //# sourceMappingURL=chunk-TSKY3JI7.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":";;;;;;;;;;;;;;;;AAAA,OAAO,UAAU;AACjB,OAAO,QAAQ;AA4BR,IAAM,qBAAN,MAAyB;AAAA,EAG9B,YAAY,mBAA2B,QAAQ,IAAI,GAAG;AACpD,SAAK,oBAAoB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,mBAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAc,UAA+B,CAAC,GAA2B;AAC7E,UAAM,OAAO,CAAC,YAAY,MAAM;AAEhC,QAAI,QAAQ,cAAc,MAAO,MAAK,KAAK,aAAa;AACxD,QAAI,QAAQ,QAAS,MAAK,KAAK,IAAI;AAEnC,UAAM,SAAS,MAAM,kBAAkB,MAAM,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAC5E,WAAO,kBAAkB,MAAM;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBAAsB,YAAiD;AAC3E,UAAM,YAAY,MAAM,KAAK,cAAc;AAC3C,WAAO,UAAU,KAAK,QAAM,GAAG,WAAW,UAAU,KAAK;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,eAAe,UAAuB,iBAAoD;AAC9F,UAAM,mBAAmB,MAAM,iCAAiC,SAAS,MAAM,eAAe;AAC9F,WAAO,SAAS,SAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UAAgC;AAC3C,WAAO,WAAW,SAAS,MAAM;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB,UAAsC;AAC5D,WAAO,gBAAgB,SAAS,MAAM;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,SAAiD;AAEpE,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAGA,UAAM,eAAe,KAAK,QAAQ,QAAQ,IAAI;AAG9C,QAAI,MAAM,GAAG,WAAW,YAAY,GAAG;AACrC,UAAI,CAAC,QAAQ,OAAO;AAClB,cAAM,IAAI,MAAM,wBAAwB,YAAY,EAAE;AAAA,MACxD;AAEA,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B;AAGA,UAAM,OAAO,CAAC,YAAY,KAAK;AAE/B,QAAI,QAAQ,cAAc;AACxB,WAAK,KAAK,MAAM,QAAQ,MAAM;AAAA,IAChC;AAEA,QAAI,QAAQ,OAAO;AACjB,WAAK,KAAK,SAAS;AAAA,IACrB;AAEA,SAAK,KAAK,YAAY;AAGtB,QAAI,CAAC,QAAQ,cAAc;AACzB,WAAK,KAAK,QAAQ,MAAM;AAAA,IAC1B,WAAW,QAAQ,YAAY;AAC7B,WAAK,KAAK,QAAQ,UAAU;AAAA,IAC9B;AAEA,UAAM,kBAAkB,MAAM,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eACJ,cACA,UAAkC,CAAC,GACX;AAExB,UAAM,YAAY,MAAM,KAAK,cAAc,EAAE,WAAW,KAAK,CAAC;AAC9D,UAAM,WAAW,UAAU,KAAK,QAAM,GAAG,SAAS,YAAY;AAE9D,QAAI,CAAC,UAAU;AAEb,YAAM,EAAE,OAAO,IAAI,MAAM,OAAO,sBAAoB;AACpD,aAAO,MAAM,8BAA8B,YAAY,EAAE;AACzD,aAAO,MAAM,SAAS,UAAU,MAAM,aAAa;AACnD,gBAAU,QAAQ,CAAC,IAAI,MAAM;AAC3B,eAAO,MAAM,KAAK,CAAC,WAAW,GAAG,IAAI,cAAc,GAAG,MAAM,GAAG;AAAA,MACjE,CAAC;AACD,YAAM,IAAI,MAAM,uBAAuB,YAAY,EAAE;AAAA,IACvD;AAGA,QAAI,CAAC,QAAQ,SAAS,CAAC,QAAQ,QAAQ;AACrC,YAAM,aAAa,MAAM,sBAAsB,YAAY;AAC3D,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,qCAAqC,YAAY,4BAA4B;AAAA,MAC/F;AAAA,IACF;AAEA,QAAI,QAAQ,QAAQ;AAClB,YAAM,UAAU,CAAC,8BAA8B;AAC/C,UAAI,QAAQ,gBAAiB,SAAQ,KAAK,4BAA4B;AACtE,UAAI,QAAQ,aAAc,SAAQ,KAAK,kBAAkB,SAAS,MAAM,EAAE;AAE1E,aAAO,kBAAkB,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7C;AAGA,UAAM,OAAO,CAAC,YAAY,QAAQ;AAClC,QAAI,QAAQ,MAAO,MAAK,KAAK,SAAS;AACtC,SAAK,KAAK,YAAY;AAEtB,UAAM,kBAAkB,MAAM,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAG7D,QAAI,QAAQ,mBAAoB,MAAM,GAAG,WAAW,YAAY,GAAI;AAClE,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B;AAGA,QAAI,QAAQ,gBAAgB,CAAC,SAAS,MAAM;AAC1C,UAAI;AACF,cAAM,kBAAkB,CAAC,UAAU,MAAM,SAAS,MAAM,GAAG;AAAA,UACzD,KAAK,KAAK;AAAA,QACZ,CAAC;AAAA,MACH,SAAS,OAAO;AAGd,cAAM,IAAI;AAAA,UACR,gDAAgD,SAAS,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,QAC9H;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,cAAmD;AACxE,UAAM,SAAmB,CAAC;AAC1B,QAAI,eAAe;AACnB,QAAI,cAAc;AAClB,QAAI,iBAAiB;AAErB,QAAI;AAEF,qBAAe,MAAM,GAAG,WAAW,YAAY;AAC/C,UAAI,CAAC,cAAc;AACjB,eAAO,KAAK,2CAA2C;AAAA,MACzD;AAGA,UAAI,cAAc;AAChB,sBAAc,MAAM,eAAe,YAAY;AAC/C,YAAI,CAAC,aAAa;AAChB,iBAAO,KAAK,yCAAyC;AAAA,QACvD;AAAA,MACF;AAGA,UAAI,aAAa;AACf,cAAM,gBAAgB,MAAM,iBAAiB,YAAY;AACzD,yBAAiB,kBAAkB;AACnC,YAAI,CAAC,gBAAgB;AACnB,iBAAO,KAAK,oCAAoC;AAAA,QAClD;AAAA,MACF;AAGA,YAAM,YAAY,MAAM,KAAK,cAAc;AAC3C,YAAM,eAAe,UAAU,KAAK,QAAM,GAAG,SAAS,YAAY;AAClE,UAAI,CAAC,cAAc;AACjB,eAAO,KAAK,qCAAqC;AAAA,MACnD;AAAA,IACF,SAAS,OAAO;AACd,aAAO,KAAK,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE;AAAA,IAC7F;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,WAAW;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,cAA+C;AACrE,UAAM,eAAe,MAAM,kBAAkB,CAAC,UAAU,gBAAgB,GAAG;AAAA,MACzE,KAAK;AAAA,IACP,CAAC;AAED,QAAI,WAAW;AACf,QAAI,SAAS;AACb,QAAI,UAAU;AACd,QAAI,YAAY;AAEhB,UAAM,QAAQ,aAAa,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AAC5D,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,KAAK,UAAU,GAAG,CAAC;AAClC,UAAI,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,MAAM,IAAK;AAC5C,UAAI,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,MAAM,IAAK;AACjE,UAAI,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,MAAM,IAAK;AAC5C,UAAI,WAAW,KAAM;AAAA,IACvB;AAEA,UAAM,gBAAiB,MAAM,iBAAiB,YAAY,KAAM;AAChE,UAAM,WAAW,kBAAkB;AAGnC,QAAI,QAAQ;AACZ,QAAI,SAAS;AACb,QAAI;AACF,YAAM,oBAAoB,MAAM;AAAA,QAC9B,CAAC,YAAY,gBAAgB,WAAW,UAAU,aAAa,SAAS;AAAA,QACxE,EAAE,KAAK,aAAa;AAAA,MACtB;AACA,YAAM,QAAQ,kBAAkB,KAAK,EAAE,MAAM,GAAI;AACjD,YAAM,YAAY,MAAM,CAAC;AACzB,YAAM,WAAW,MAAM,CAAC;AACxB,eAAS,YAAY,SAAS,WAAW,EAAE,KAAK,IAAI;AACpD,cAAQ,WAAW,SAAS,UAAU,EAAE,KAAK,IAAI;AAAA,IACnD,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,WAAW,SAAS,UAAU,YAAY;AAAA,MACtD,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,qBACE,YACA,YACA,SACQ;AACR,UAAM,OAAO,cAAc,KAAK;AAChC,WAAO,qBAAqB,YAAY,MAAM,OAAO;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB,YAA4B;AAC7C,WAAO,WACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE,EACpB,YAAY;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAgC;AACpC,QAAI;AACF,YAAM,WAAW,MAAM,YAAY,KAAK,iBAAiB;AACzD,aAAO,aAAa;AAAA,IACtB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAIH;AACD,UAAM,OAAO,MAAM,YAAY,KAAK,iBAAiB;AACrD,UAAM,gBAAgB,MAAM,iBAAiB,KAAK,iBAAiB;AACnE,UAAM,gBAAgB,MAAM,iBAAiB,KAAK,iBAAiB;AAEnE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,UAAM,kBAAkB,CAAC,YAAY,SAAS,IAAI,GAAG,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAAA,EACtF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,cAAsB,QAAgC;AACvE,UAAM,OAAO,CAAC,YAAY,QAAQ,YAAY;AAC9C,QAAI,OAAQ,MAAK,KAAK,YAAY,MAAM;AAExC,UAAM,kBAAkB,MAAM,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,cAAqC;AACxD,UAAM,kBAAkB,CAAC,YAAY,UAAU,YAAY,GAAG,EAAE,KAAK,KAAK,kBAAkB,CAAC;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,0BAA0B,YAA4C;AAC1E,UAAM,YAAY,MAAM,KAAK,cAAc,EAAE,WAAW,KAAK,CAAC;AAC9D,WAAO,UAAU;AAAA,MACf,QAAG;AArZT;AAsZQ,kBAAG,OAAO,SAAS,UAAU,KAC7B,GAAG,KAAK,SAAS,UAAU,OAC3B,UAAK,wBAAwB,EAAE,MAA/B,mBAAkC,gBAAe;AAAA;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,qBAAqB,aAA2D;AACpF,UAAM,YAAY,MAAM,KAAK,cAAc,EAAE,WAAW,KAAK,CAAC;AAG9D,UAAM,UAAU,IAAI,OAAO,oBAAoB,WAAW,YAAY;AAEtE,WAAO,UAAU,KAAK,QAAM,QAAQ,KAAK,GAAG,MAAM,CAAC,KAAK;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,kBAAkB,UAAkB,YAAiD;AACzF,UAAM,YAAY,MAAM,KAAK,cAAc,EAAE,WAAW,KAAK,CAAC;AAG9D,UAAM,WAAW,UAAU,KAAK,QAAM,GAAG,WAAW,UAAU;AAC9D,QAAI,SAAU,QAAO;AAGrB,UAAM,cAAc,IAAI,OAAO,OAAO,QAAQ,GAAG;AACjD,WAAO,UAAU,KAAK,QAAM,YAAY,KAAK,GAAG,IAAI,CAAC,KAAK;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,gBACJ,WACA,iBACA,UAAkC,CAAC,GAKlC;AACD,UAAM,YAA8C,CAAC;AACrD,UAAM,WAA4D,CAAC;AACnE,UAAM,UAA4D,CAAC;AAEnE,eAAW,YAAY,WAAW;AAEhC,UAAI,MAAM,KAAK,eAAe,UAAU,eAAe,GAAG;AACxD,gBAAQ,KAAK,EAAE,UAAU,QAAQ,8BAA8B,CAAC;AAChE;AAAA,MACF;AAEA,UAAI;AACF,cAAM,KAAK,eAAe,SAAS,MAAM;AAAA,UACvC,GAAG;AAAA,UACH,iBAAiB;AAAA,QACnB,CAAC;AACD,kBAAU,KAAK,EAAE,SAAS,CAAC;AAAA,MAC7B,SAAS,OAAO;AACd,iBAAS,KAAK;AAAA,UACZ;AAAA,UACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,UAAU,QAAQ;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAIb;AACA,UAAM,WAAW,KAAK,wBAAwB,QAAQ;AACtD,UAAM,UAAU,WAAW,SAAS,QAAQ,MAAM;AAClD,UAAM,YAAY,SAAS,OAAO,YAAY;AAE9C,WAAO;AAAA,MACL,OAAO,GAAG,SAAS,MAAM,GAAG,OAAO,GAAG,SAAS;AAAA,MAC/C,MAAM,SAAS;AAAA,MACf,QAAQ,SAAS,OAAO,UAAU,GAAG,CAAC;AAAA,IACxC;AAAA,EACF;AACF;","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":[]}
@@ -3,13 +3,13 @@ import {
3
3
  detectClaudeCli,
4
4
  launchClaude,
5
5
  launchClaudeInNewTerminalWindow
6
- } from "./chunk-XXV3UFZL.js";
6
+ } from "./chunk-ZWFBBPJI.js";
7
7
  import {
8
8
  PromptTemplateManager
9
9
  } from "./chunk-WEN5C5DM.js";
10
10
  import {
11
11
  SettingsManager
12
- } from "./chunk-ML3NRPNB.js";
12
+ } from "./chunk-O5OH5MRX.js";
13
13
  import {
14
14
  logger
15
15
  } from "./chunk-GEHQXLEI.js";
@@ -136,4 +136,4 @@ var ClaudeService = class {
136
136
  export {
137
137
  ClaudeService
138
138
  };
139
- //# sourceMappingURL=chunk-FIAT22G7.js.map
139
+ //# sourceMappingURL=chunk-U5QDY7ZD.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ClaudeService
4
- } from "./chunk-FIAT22G7.js";
4
+ } from "./chunk-U5QDY7ZD.js";
5
5
  import {
6
6
  logger
7
7
  } from "./chunk-GEHQXLEI.js";
@@ -63,4 +63,4 @@ var ClaudeContextManager = class {
63
63
  export {
64
64
  ClaudeContextManager
65
65
  };
66
- //# sourceMappingURL=chunk-C5QCTEQK.js.map
66
+ //# sourceMappingURL=chunk-XPKDPZ5D.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YETJNRQM.js";
5
5
  import {
6
6
  launchClaude
7
- } from "./chunk-XXV3UFZL.js";
7
+ } from "./chunk-ZWFBBPJI.js";
8
8
  import {
9
9
  waitForKeypress
10
10
  } from "./chunk-JKXJ7BGL.js";
@@ -29,12 +29,12 @@ var IssueEnhancementService = class {
29
29
  return trimmedDescription.length > 30 && spaceCount > 2;
30
30
  }
31
31
  /**
32
- * Enhances a description using Claude AI in headless mode.
32
+ * Enhances a description using Claude Code in headless mode.
33
33
  * Falls back to original description if enhancement fails.
34
34
  */
35
35
  async enhanceDescription(description) {
36
36
  try {
37
- logger.info("Enhancing description with Claude AI. This may take a moment...");
37
+ logger.info("Enhancing description with Claude Code. This may take a moment...");
38
38
  const settings = await this.settingsManager.loadSettings();
39
39
  const loadedAgents = await this.agentManager.loadAgents(settings);
40
40
  const agents = this.agentManager.formatForCli(loadedAgents);
@@ -117,4 +117,4 @@ Press any key to open issue for editing...`;
117
117
  export {
118
118
  IssueEnhancementService
119
119
  };
120
- //# sourceMappingURL=chunk-47KSHUCR.js.map
120
+ //# sourceMappingURL=chunk-ZBQVSHVT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/IssueEnhancementService.ts"],"sourcesContent":["import type { IssueTracker } from './IssueTracker.js'\nimport type { AgentManager } from './AgentManager.js'\nimport type { SettingsManager } from './SettingsManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Service for enhancing and creating GitHub issues with AI assistance.\n * Extracts reusable issue enhancement logic from StartCommand.\n */\nexport class IssueEnhancementService {\n\tconstructor(\n\t\tprivate gitHubService: IssueTracker,\n\t\tprivate agentManager: AgentManager,\n\t\tprivate settingsManager: SettingsManager\n\t) {}\n\n\t/**\n\t * Validates that a description meets minimum requirements.\n\t * Requirements: >30 characters AND >2 spaces\n\t */\n\tpublic validateDescription(description: string): boolean {\n\t\tconst trimmedDescription = description.trim()\n\t\tconst spaceCount = (trimmedDescription.match(/ /g) ?? []).length\n\n\t\treturn trimmedDescription.length > 30 && spaceCount > 2\n\t}\n\n\t/**\n\t * Enhances a description using Claude Code in headless mode.\n\t * Falls back to original description if enhancement fails.\n\t */\n\tpublic async enhanceDescription(description: string): Promise<string> {\n\t\ttry {\n\t\t\tlogger.info('Enhancing description with Claude Code. This may take a moment...')\n\n\t\t\t// Load agent configurations\n\t\t\tconst settings = await this.settingsManager.loadSettings()\n\t\t\tconst loadedAgents = await this.agentManager.loadAgents(settings)\n\t\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t\t// Call Claude in headless mode with issue enhancer agent\n\t\t\tconst prompt = `@agent-iloom-issue-enhancer\n\nTASK: Enhance the following issue description for GitHub.\n\nINPUT:\n${description}\n\nOUTPUT REQUIREMENTS:\n- Return ONLY the enhanced description markdown text\n- NO meta-commentary (no \"Here is...\", \"The enhanced...\", \"I have...\", etc)\n- NO code block markers (\\`\\`\\`)\n- NO conversational framing or acknowledgments\n- NO explanations of your work\n- Start your response immediately with the enhanced content\n\nYour response should be the raw markdown that will become the GitHub issue body.`\n\n\t\t\tconst enhanced = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: 'sonnet',\n\t\t\t\tagents,\n\t\t\t})\n\n\t\t\tif (enhanced && typeof enhanced === 'string') {\n\t\t\t\tlogger.success('Description enhanced successfully')\n\t\t\t\treturn enhanced\n\t\t\t}\n\n\t\t\t// Fallback to original description\n\t\t\tlogger.warn('Claude enhancement returned empty result, using original description')\n\t\t\treturn description\n\t\t} catch (error) {\n\t\t\tlogger.warn(`Failed to enhance description: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\treturn description\n\t\t}\n\t}\n\n\t/**\n\t * Creates a GitHub issue with title and enhanced body.\n\t * @param originalDescription - Used as the issue title\n\t * @param enhancedDescription - Used as the issue body\n\t * @param repository - Optional repository override (format: \"owner/repo\")\n\t * @param labels - Optional array of label names to add to the issue\n\t * @returns Issue number and URL\n\t */\n\tpublic async createEnhancedIssue(\n\t\toriginalDescription: string,\n\t\tenhancedDescription: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\tlogger.info('Creating GitHub issue from description...')\n\n\t\tconst result = await this.gitHubService.createIssue(\n\t\t\toriginalDescription, // Use original description as title\n\t\t\tenhancedDescription, // Use enhanced description as body\n\t\t\trepository,\n\t\t\tlabels\n\t\t)\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Waits for user keypress and opens issue in browser for review.\n\t * @param issueNumber - Issue number to open for review\n\t * @param confirm - If true, wait for additional keypress after opening browser before returning\n\t * @param repository - Optional repository to fetch issue from (format: \"owner/repo\")\n\t */\n\tpublic async waitForReviewAndOpen(issueNumber: string | number, confirm = false, repository?: string): Promise<void> {\n\t\t// Check if running in CI environment\n\t\tconst isCI = process.env.CI === 'true'\n\n\t\tif (isCI) {\n\t\t\t// In CI: Skip all interactive operations\n\t\t\tlogger.info(`Running in CI environment - skipping interactive prompts for issue #${issueNumber}`)\n\t\t\treturn\n\t\t}\n\n\t\t// Get issue URL\n\t\tconst issueUrl = await this.gitHubService.getIssueUrl(issueNumber, repository)\n\n\t\t// Display message and wait for first keypress\n\t\tconst message = `Created issue #${issueNumber}.\nReview and edit the issue in your browser if needed.\nPress any key to open issue for editing...`\n\t\tawait waitForKeypress(message)\n\n\t\t// Open issue in browser\n\t\tawait openBrowser(issueUrl)\n\n\t\t// If confirmation required, wait for second keypress\n\t\tif (confirm) {\n\t\t\tawait waitForKeypress('Press any key to continue with loom creation...')\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AAYO,IAAM,0BAAN,MAA8B;AAAA,EACpC,YACS,eACA,cACA,iBACP;AAHO;AACA;AACA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMI,oBAAoB,aAA8B;AACxD,UAAM,qBAAqB,YAAY,KAAK;AAC5C,UAAM,cAAc,mBAAmB,MAAM,IAAI,KAAK,CAAC,GAAG;AAE1D,WAAO,mBAAmB,SAAS,MAAM,aAAa;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,mBAAmB,aAAsC;AACrE,QAAI;AACH,aAAO,KAAK,mEAAmE;AAG/E,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa;AACzD,YAAM,eAAe,MAAM,KAAK,aAAa,WAAW,QAAQ;AAChE,YAAM,SAAS,KAAK,aAAa,aAAa,YAAY;AAG1D,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYV,YAAM,WAAW,MAAM,aAAa,QAAQ;AAAA,QAC3C,UAAU;AAAA,QACV,OAAO;AAAA,QACP;AAAA,MACD,CAAC;AAED,UAAI,YAAY,OAAO,aAAa,UAAU;AAC7C,eAAO,QAAQ,mCAAmC;AAClD,eAAO;AAAA,MACR;AAGA,aAAO,KAAK,sEAAsE;AAClF,aAAO;AAAA,IACR,SAAS,OAAO;AACf,aAAO,KAAK,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE;AACxG,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,oBACZ,qBACA,qBACA,YACA,QACoD;AACpD,WAAO,KAAK,2CAA2C;AAEvD,UAAM,SAAS,MAAM,KAAK,cAAc;AAAA,MACvC;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,qBAAqB,aAA8B,UAAU,OAAO,YAAoC;AAEpH,UAAM,OAAO,QAAQ,IAAI,OAAO;AAEhC,QAAI,MAAM;AAET,aAAO,KAAK,uEAAuE,WAAW,EAAE;AAChG;AAAA,IACD;AAGA,UAAM,WAAW,MAAM,KAAK,cAAc,YAAY,aAAa,UAAU;AAG7E,UAAM,UAAU,kBAAkB,WAAW;AAAA;AAAA;AAG7C,UAAM,gBAAgB,OAAO;AAG7B,UAAM,YAAY,QAAQ;AAG1B,QAAI,SAAS;AACZ,YAAM,gBAAgB,iDAAiD;AAAA,IACxE;AAAA,EACD;AACD;","names":[]}
@@ -223,20 +223,20 @@ Generate a git branch name for the following issue:
223
223
  model,
224
224
  headless: true
225
225
  });
226
- const branchName = result.trim();
226
+ const branchName = result.trim().toLowerCase();
227
227
  logger.debug("Claude returned branch name", { branchName, issueNumber });
228
228
  if (!branchName || !isValidBranchName(branchName, issueNumber)) {
229
229
  logger.warn("Invalid branch name from Claude, using fallback", { branchName });
230
- return `feat/issue-${issueNumber}`;
230
+ return `feat/issue-${issueNumber}`.toLowerCase();
231
231
  }
232
232
  return branchName;
233
233
  } catch (error) {
234
234
  logger.warn("Failed to generate branch name with Claude", { error });
235
- return `feat/issue-${issueNumber}`;
235
+ return `feat/issue-${issueNumber}`.toLowerCase();
236
236
  }
237
237
  }
238
238
  function isValidBranchName(name, issueNumber) {
239
- const pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`);
239
+ const pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`, "i");
240
240
  return pattern.test(name) && name.length <= 50;
241
241
  }
242
242
 
@@ -247,4 +247,4 @@ export {
247
247
  launchClaudeInNewTerminalWindow,
248
248
  generateBranchName
249
249
  };
250
- //# sourceMappingURL=chunk-XXV3UFZL.js.map
250
+ //# sourceMappingURL=chunk-ZWFBBPJI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/claude.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { logger } from './logger.js'\n\nexport interface ClaudeCliOptions {\n\tmodel?: string\n\tpermissionMode?: 'plan' | 'acceptEdits' | 'bypassPermissions' | 'default'\n\taddDir?: string\n\theadless?: boolean\n\tbranchName?: string // Optional branch name for terminal coloring\n\tport?: number // Optional port for terminal window export\n\ttimeout?: number // Timeout in milliseconds\n\tappendSystemPrompt?: string // System instructions to append to system prompt\n\tmcpConfig?: Record<string, unknown>[] // Array of MCP server configurations\n\tallowedTools?: string[] // Tools to allow via --allowed-tools flag\n\tdisallowedTools?: string[] // Tools to disallow via --disallowed-tools flag\n\tagents?: Record<string, unknown> // Agent configurations for --agents flag\n\toneShot?: import('../types/index.js').OneShotMode // One-shot automation mode\n\tsetArguments?: string[] // Raw --set arguments to forward (e.g., ['workflows.issue.startIde=false'])\n\texecutablePath?: string // Executable path to use for spin command (e.g., 'il', 'il-125', or '/path/to/dist/cli.js')\n}\n\n/**\n * Detect if Claude CLI is available on the system\n */\nexport async function detectClaudeCli(): Promise<boolean> {\n\ttry {\n\t\t// Use 'command -v' for cross-platform compatibility (works on macOS/Linux)\n\t\tawait execa('command', ['-v', 'claude'], {\n\t\t\tshell: true,\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn true\n\t} catch (error) {\n\t\t// Claude CLI not found\n\t\tlogger.debug('Claude CLI not available', { error })\n\t\treturn false\n\t}\n}\n\n/**\n * Get Claude CLI version\n */\nexport async function getClaudeVersion(): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa('claude', ['--version'], {\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn result.stdout.trim()\n\t} catch (error) {\n\t\tlogger.warn('Failed to get Claude version', { error })\n\t\treturn null\n\t}\n}\n\n/**\n * Parse JSON stream output and extract result from last JSON object with type:\"result\"\n */\nfunction parseJsonStreamOutput(output: string): string {\n\ttry {\n\t\t// Split by newlines and filter out empty lines\n\t\tconst lines = output.split('\\n').filter(line => line.trim())\n\n\t\t// Find the last valid JSON object with type:\"result\"\n\t\tlet lastResult = ''\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst jsonObj = JSON.parse(line)\n\t\t\t\tif (jsonObj && typeof jsonObj === 'object' && jsonObj.type === 'result' && 'result' in jsonObj) {\n\t\t\t\t\tlastResult = jsonObj.result\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip invalid JSON lines\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\treturn lastResult || output // Fallback to original output if no valid result found\n\t} catch {\n\t\t// If parsing fails completely, return original output\n\t\treturn output\n\t}\n}\n\n/**\n * Launch Claude CLI with specified options\n * In headless mode, returns stdout. In interactive mode, returns void.\n */\nexport async function launchClaude(\n\tprompt: string,\n\toptions: ClaudeCliOptions = {}\n): Promise<string | void> {\n\tconst { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents } = options\n\n\t// Build command arguments\n\tconst args: string[] = []\n\n\tif (headless) {\n\t\targs.push('-p')\n\n\t\t// Add JSON streaming output for progress tracking\n\t\targs.push('--output-format', 'stream-json')\n\t\targs.push('--verbose')\n\t}\n\n\tif (model) {\n\t\targs.push('--model', model)\n\t}\n\n\tif (permissionMode && permissionMode !== 'default') {\n\t\targs.push('--permission-mode', permissionMode)\n\t}\n\n\tif (addDir) {\n\t\targs.push('--add-dir', addDir)\n\t}\n\n\targs.push('--add-dir', '/tmp') //TODO: Won't work on Windows\n\n\t// Add --append-system-prompt flag if provided\n\tif (appendSystemPrompt) {\n\t\targs.push('--append-system-prompt', appendSystemPrompt)\n\t}\n\n\t// Add --mcp-config flags for each MCP server configuration\n\tif (mcpConfig && mcpConfig.length > 0) {\n\t\tfor (const config of mcpConfig) {\n\t\t\targs.push('--mcp-config', JSON.stringify(config))\n\t\t}\n\t}\n\n\t// Add --allowed-tools flags if provided\n\tif (allowedTools && allowedTools.length > 0) {\n\t\targs.push('--allowed-tools', ...allowedTools)\n\t}\n\n\t// Add --disallowed-tools flags if provided\n\tif (disallowedTools && disallowedTools.length > 0) {\n\t\targs.push('--disallowed-tools', ...disallowedTools)\n\t}\n\n\t// Add --agents flag if provided\n\tif (agents) {\n\t\targs.push('--agents', JSON.stringify(agents))\n\t}\n\n\ttry {\n\t\tif (headless) {\n\t\t\t// Headless mode: capture and return output\n\t\t\tconst isDebugMode = logger.isDebugEnabled()\n\n\t\t\t// Set up execa options based on debug mode\n\t\t\tconst execaOptions = {\n\t\t\t\tinput: prompt,\n\t\t\t\ttimeout: 0, // Disable timeout for long responses\n\t\t\t\t...(addDir && { cwd: addDir }), // Run Claude in the worktree directory\n\t\t\t\tverbose: isDebugMode,\n\t\t\t\t...(isDebugMode && { stdio: ['pipe', 'pipe', 'pipe'] as const }), // Enable streaming in debug mode\n\t\t\t}\n\n\t\t\tconst subprocess = execa('claude', args, execaOptions)\n\n\t\t\t// Check if JSON streaming format is enabled (always true in headless mode)\n\t\t\tconst isJsonStreamFormat = args.includes('--output-format') && args.includes('stream-json')\n\n\t\t\t// Handle real-time streaming (enabled for progress tracking)\n\t\t\tlet outputBuffer = ''\n\t\t\tlet isStreaming = false\n\t\t\tlet isFirstProgress = true\n\t\t\tif (subprocess.stdout && typeof subprocess.stdout.on === 'function') {\n\t\t\t\tisStreaming = true\n\t\t\t\tsubprocess.stdout.on('data', (chunk: Buffer) => {\n\t\t\t\t\tconst text = chunk.toString()\n\t\t\t\t\toutputBuffer += text\n\n\t\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t\tprocess.stdout.write(text) // Full JSON streaming in debug mode\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Progress dots in non-debug mode with robot emoji prefix\n\t\t\t\t\t\tif (isFirstProgress) {\n\t\t\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\t\t\tisFirstProgress = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tprocess.stdout.write('.')\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst result = await subprocess\n\n\t\t\t// Return streamed output if we were streaming, otherwise use result.stdout\n\t\t\tif (isStreaming) {\n\t\t\t\tconst rawOutput = outputBuffer.trim()\n\n\t\t\t\t// Clean up progress dots with newline in non-debug mode\n\t\t\t\tif (!isDebugMode) {\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t} else {\n\t\t\t\t// Fallback for mocked tests or when streaming not available\n\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t// In debug mode, write to stdout even if not streaming (old behavior for tests)\n\t\t\t\t\tprocess.stdout.write(result.stdout)\n\t\t\t\t\tif (result.stdout && !result.stdout.endsWith('\\n')) {\n\t\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// In non-debug mode, show a single progress dot even without streaming (for tests)\n\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\t\t\t\tconst rawOutput = result.stdout.trim()\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t}\n\t\t} else {\n\t\t\t// Simple interactive mode: run Claude in current terminal with stdio inherit\n\t\t\t// Used for conflict resolution, error fixing, etc.\n\t\t\t// This is the simple approach: claude -- \"prompt\"\n\n\t\t\t// Execute in current terminal (blocking, inherits stdio)\n\t\t\tawait execa('claude', [...args, '--', prompt], {\n\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\tstdio: 'inherit', // Let user interact directly in current terminal\n\t\t\t\ttimeout: 0, // Disable timeout\n\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t})\n\n\t\t\treturn\n\t\t}\n\t} catch (error) {\n\t\t// Check for specific Claude CLI errors\n\t\tconst execaError = error as {\n\t\t\tstderr?: string\n\t\t\tmessage?: string\n\t\t\texitCode?: number\n\t\t}\n\n\t\t// Re-throw with more context\n\t\tconst errorMessage = execaError.stderr ?? execaError.message ?? 'Unknown Claude CLI error'\n\t\tthrow new Error(`Claude CLI error: ${errorMessage}`)\n\t}\n}\n\n/**\n * Launch Claude in a new terminal window with rich context\n * This is specifically for \"end of il start\" workflow\n * Ports the terminal window opening, coloring, and .env sourcing behavior\n */\nexport async function launchClaudeInNewTerminalWindow(\n\t_prompt: string,\n\toptions: ClaudeCliOptions & {\n\t\tworkspacePath: string // Required for terminal window launch\n\t}\n): Promise<void> {\n\tconst { workspacePath, branchName, oneShot = 'default', port, setArguments, executablePath } = options\n\n\t// Verify required parameter\n\tif (!workspacePath) {\n\t\tthrow new Error('workspacePath is required for terminal window launch')\n\t}\n\n\t// Import terminal launcher for new terminal window creation\n\tconst { openTerminalWindow } = await import('./terminal.js')\n\n\t// Build launch command with optional --one-shot flag\n\t// Use provided executable path or fallback to 'il'\n\tconst executable = executablePath ?? 'iloom'\n\tlet launchCommand = `${executable} spin`\n\tif (oneShot !== 'default') {\n\t\tlaunchCommand += ` --one-shot=${oneShot}`\n\t}\n\n\t// Append --set arguments if provided\n\tif (setArguments && setArguments.length > 0) {\n\t\tfor (const setArg of setArguments) {\n\t\t\tlaunchCommand += ` --set ${setArg}`\n\t\t}\n\t}\n\n\t// Apply terminal background color if branch name available\n\tlet backgroundColor: { r: number; g: number; b: number } | undefined\n\tif (branchName) {\n\t\ttry {\n\t\t\tconst { generateColorFromBranchName } = await import('./color.js')\n\t\t\tconst colorData = generateColorFromBranchName(branchName)\n\t\t\tbackgroundColor = colorData.rgb\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to generate terminal color: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t// Check if .env file exists in workspace\n\tconst hasEnvFile = existsSync(join(workspacePath, '.env'))\n\n\t// Open new terminal window with Claude\n\tawait openTerminalWindow({\n\t\tworkspacePath,\n\t\tcommand: launchCommand,\n\t\t...(backgroundColor && { backgroundColor }),\n\t\tincludeEnvSetup: hasEnvFile, // source .env only if it exists\n\t\t...(port !== undefined && { port, includePortExport: true }),\n\t})\n}\n\n/**\n * Generate a branch name using Claude with fallback\n * This matches the implementation that was working in ClaudeBranchNameStrategy\n */\nexport async function generateBranchName(\n\tissueTitle: string,\n\tissueNumber: string | number,\n\tmodel: string = 'haiku'\n): Promise<string> {\n\ttry {\n\t\t// Check if Claude CLI is available\n\t\tconst isAvailable = await detectClaudeCli()\n\t\tif (!isAvailable) {\n\t\t\tlogger.warn('Claude CLI not available, using fallback branch name')\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\n\t\tlogger.debug('Generating branch name with Claude', { issueNumber, issueTitle })\n\n\t\t// Use the proven prompt format from ClaudeBranchNameStrategy\n\t\tconst prompt = `<Task>\nGenerate a git branch name for the following issue:\n<Issue>\n<IssueNumber>${issueNumber}</IssueNumber>\n<IssueTitle>${issueTitle}</IssueTitle>\n</Issue>\n\n<Requirements>\n<IssueNumber>Must use this exact issue number: ${issueNumber}</IssueNumber>\n<Format>Format must be: {prefix}/issue-${issueNumber}__{description}</Format>\n<Prefix>Prefix must be one of: feat, fix, docs, refactor, test, chore</Prefix>\n<MaxLength>Maximum 50 characters total</MaxLength>\n<Characters>Only lowercase letters, numbers, and hyphens allowed</Characters>\n<Output>Reply with ONLY the branch name, nothing else</Output>\n</Requirements>\n</Task>`\n\n\t\tlogger.debug('Sending prompt to Claude', { prompt })\n\n\t\tconst result = (await launchClaude(prompt, {\n\t\t\tmodel,\n\t\t\theadless: true,\n\t\t})) as string\n\n\t\tconst branchName = result.trim()\n\t\tlogger.debug('Claude returned branch name', { branchName, issueNumber })\n\n\t\t// Validate generated name using same validation as ClaudeBranchNameStrategy\n\t\tif (!branchName || !isValidBranchName(branchName, issueNumber)) {\n\t\t\tlogger.warn('Invalid branch name from Claude, using fallback', { branchName })\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\n\t\treturn branchName\n\t} catch (error) {\n\t\tlogger.warn('Failed to generate branch name with Claude', { error })\n\t\treturn `feat/issue-${issueNumber}`\n\t}\n}\n\n/**\n * Validate branch name format\n * Check format: {prefix}/issue-{number}__{description}\n */\nfunction isValidBranchName(name: string, issueNumber: string | number): boolean {\n\tconst pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`)\n\treturn pattern.test(name) && name.length <= 50\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/claude.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { logger } from './logger.js'\n\nexport interface ClaudeCliOptions {\n\tmodel?: string\n\tpermissionMode?: 'plan' | 'acceptEdits' | 'bypassPermissions' | 'default'\n\taddDir?: string\n\theadless?: boolean\n\tbranchName?: string // Optional branch name for terminal coloring\n\tport?: number // Optional port for terminal window export\n\ttimeout?: number // Timeout in milliseconds\n\tappendSystemPrompt?: string // System instructions to append to system prompt\n\tmcpConfig?: Record<string, unknown>[] // Array of MCP server configurations\n\tallowedTools?: string[] // Tools to allow via --allowed-tools flag\n\tdisallowedTools?: string[] // Tools to disallow via --disallowed-tools flag\n\tagents?: Record<string, unknown> // Agent configurations for --agents flag\n\toneShot?: import('../types/index.js').OneShotMode // One-shot automation mode\n\tsetArguments?: string[] // Raw --set arguments to forward (e.g., ['workflows.issue.startIde=false'])\n\texecutablePath?: string // Executable path to use for spin command (e.g., 'il', 'il-125', or '/path/to/dist/cli.js')\n}\n\n/**\n * Detect if Claude CLI is available on the system\n */\nexport async function detectClaudeCli(): Promise<boolean> {\n\ttry {\n\t\t// Use 'command -v' for cross-platform compatibility (works on macOS/Linux)\n\t\tawait execa('command', ['-v', 'claude'], {\n\t\t\tshell: true,\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn true\n\t} catch (error) {\n\t\t// Claude CLI not found\n\t\tlogger.debug('Claude CLI not available', { error })\n\t\treturn false\n\t}\n}\n\n/**\n * Get Claude CLI version\n */\nexport async function getClaudeVersion(): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa('claude', ['--version'], {\n\t\t\ttimeout: 5000,\n\t\t})\n\t\treturn result.stdout.trim()\n\t} catch (error) {\n\t\tlogger.warn('Failed to get Claude version', { error })\n\t\treturn null\n\t}\n}\n\n/**\n * Parse JSON stream output and extract result from last JSON object with type:\"result\"\n */\nfunction parseJsonStreamOutput(output: string): string {\n\ttry {\n\t\t// Split by newlines and filter out empty lines\n\t\tconst lines = output.split('\\n').filter(line => line.trim())\n\n\t\t// Find the last valid JSON object with type:\"result\"\n\t\tlet lastResult = ''\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst jsonObj = JSON.parse(line)\n\t\t\t\tif (jsonObj && typeof jsonObj === 'object' && jsonObj.type === 'result' && 'result' in jsonObj) {\n\t\t\t\t\tlastResult = jsonObj.result\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip invalid JSON lines\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\treturn lastResult || output // Fallback to original output if no valid result found\n\t} catch {\n\t\t// If parsing fails completely, return original output\n\t\treturn output\n\t}\n}\n\n/**\n * Launch Claude CLI with specified options\n * In headless mode, returns stdout. In interactive mode, returns void.\n */\nexport async function launchClaude(\n\tprompt: string,\n\toptions: ClaudeCliOptions = {}\n): Promise<string | void> {\n\tconst { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents } = options\n\n\t// Build command arguments\n\tconst args: string[] = []\n\n\tif (headless) {\n\t\targs.push('-p')\n\n\t\t// Add JSON streaming output for progress tracking\n\t\targs.push('--output-format', 'stream-json')\n\t\targs.push('--verbose')\n\t}\n\n\tif (model) {\n\t\targs.push('--model', model)\n\t}\n\n\tif (permissionMode && permissionMode !== 'default') {\n\t\targs.push('--permission-mode', permissionMode)\n\t}\n\n\tif (addDir) {\n\t\targs.push('--add-dir', addDir)\n\t}\n\n\targs.push('--add-dir', '/tmp') //TODO: Won't work on Windows\n\n\t// Add --append-system-prompt flag if provided\n\tif (appendSystemPrompt) {\n\t\targs.push('--append-system-prompt', appendSystemPrompt)\n\t}\n\n\t// Add --mcp-config flags for each MCP server configuration\n\tif (mcpConfig && mcpConfig.length > 0) {\n\t\tfor (const config of mcpConfig) {\n\t\t\targs.push('--mcp-config', JSON.stringify(config))\n\t\t}\n\t}\n\n\t// Add --allowed-tools flags if provided\n\tif (allowedTools && allowedTools.length > 0) {\n\t\targs.push('--allowed-tools', ...allowedTools)\n\t}\n\n\t// Add --disallowed-tools flags if provided\n\tif (disallowedTools && disallowedTools.length > 0) {\n\t\targs.push('--disallowed-tools', ...disallowedTools)\n\t}\n\n\t// Add --agents flag if provided\n\tif (agents) {\n\t\targs.push('--agents', JSON.stringify(agents))\n\t}\n\n\ttry {\n\t\tif (headless) {\n\t\t\t// Headless mode: capture and return output\n\t\t\tconst isDebugMode = logger.isDebugEnabled()\n\n\t\t\t// Set up execa options based on debug mode\n\t\t\tconst execaOptions = {\n\t\t\t\tinput: prompt,\n\t\t\t\ttimeout: 0, // Disable timeout for long responses\n\t\t\t\t...(addDir && { cwd: addDir }), // Run Claude in the worktree directory\n\t\t\t\tverbose: isDebugMode,\n\t\t\t\t...(isDebugMode && { stdio: ['pipe', 'pipe', 'pipe'] as const }), // Enable streaming in debug mode\n\t\t\t}\n\n\t\t\tconst subprocess = execa('claude', args, execaOptions)\n\n\t\t\t// Check if JSON streaming format is enabled (always true in headless mode)\n\t\t\tconst isJsonStreamFormat = args.includes('--output-format') && args.includes('stream-json')\n\n\t\t\t// Handle real-time streaming (enabled for progress tracking)\n\t\t\tlet outputBuffer = ''\n\t\t\tlet isStreaming = false\n\t\t\tlet isFirstProgress = true\n\t\t\tif (subprocess.stdout && typeof subprocess.stdout.on === 'function') {\n\t\t\t\tisStreaming = true\n\t\t\t\tsubprocess.stdout.on('data', (chunk: Buffer) => {\n\t\t\t\t\tconst text = chunk.toString()\n\t\t\t\t\toutputBuffer += text\n\n\t\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t\tprocess.stdout.write(text) // Full JSON streaming in debug mode\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Progress dots in non-debug mode with robot emoji prefix\n\t\t\t\t\t\tif (isFirstProgress) {\n\t\t\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\t\t\tisFirstProgress = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tprocess.stdout.write('.')\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst result = await subprocess\n\n\t\t\t// Return streamed output if we were streaming, otherwise use result.stdout\n\t\t\tif (isStreaming) {\n\t\t\t\tconst rawOutput = outputBuffer.trim()\n\n\t\t\t\t// Clean up progress dots with newline in non-debug mode\n\t\t\t\tif (!isDebugMode) {\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t} else {\n\t\t\t\t// Fallback for mocked tests or when streaming not available\n\t\t\t\tif (isDebugMode) {\n\t\t\t\t\t// In debug mode, write to stdout even if not streaming (old behavior for tests)\n\t\t\t\t\tprocess.stdout.write(result.stdout)\n\t\t\t\t\tif (result.stdout && !result.stdout.endsWith('\\n')) {\n\t\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// In non-debug mode, show a single progress dot even without streaming (for tests)\n\t\t\t\t\tprocess.stdout.write('🤖 .')\n\t\t\t\t\tprocess.stdout.write('\\n')\n\t\t\t\t}\n\t\t\t\tconst rawOutput = result.stdout.trim()\n\t\t\t\treturn isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput\n\t\t\t}\n\t\t} else {\n\t\t\t// Simple interactive mode: run Claude in current terminal with stdio inherit\n\t\t\t// Used for conflict resolution, error fixing, etc.\n\t\t\t// This is the simple approach: claude -- \"prompt\"\n\n\t\t\t// Execute in current terminal (blocking, inherits stdio)\n\t\t\tawait execa('claude', [...args, '--', prompt], {\n\t\t\t\t...(addDir && { cwd: addDir }),\n\t\t\t\tstdio: 'inherit', // Let user interact directly in current terminal\n\t\t\t\ttimeout: 0, // Disable timeout\n\t\t\t\tverbose: logger.isDebugEnabled(),\n\t\t\t})\n\n\t\t\treturn\n\t\t}\n\t} catch (error) {\n\t\t// Check for specific Claude CLI errors\n\t\tconst execaError = error as {\n\t\t\tstderr?: string\n\t\t\tmessage?: string\n\t\t\texitCode?: number\n\t\t}\n\n\t\t// Re-throw with more context\n\t\tconst errorMessage = execaError.stderr ?? execaError.message ?? 'Unknown Claude CLI error'\n\t\tthrow new Error(`Claude CLI error: ${errorMessage}`)\n\t}\n}\n\n/**\n * Launch Claude in a new terminal window with rich context\n * This is specifically for \"end of il start\" workflow\n * Ports the terminal window opening, coloring, and .env sourcing behavior\n */\nexport async function launchClaudeInNewTerminalWindow(\n\t_prompt: string,\n\toptions: ClaudeCliOptions & {\n\t\tworkspacePath: string // Required for terminal window launch\n\t}\n): Promise<void> {\n\tconst { workspacePath, branchName, oneShot = 'default', port, setArguments, executablePath } = options\n\n\t// Verify required parameter\n\tif (!workspacePath) {\n\t\tthrow new Error('workspacePath is required for terminal window launch')\n\t}\n\n\t// Import terminal launcher for new terminal window creation\n\tconst { openTerminalWindow } = await import('./terminal.js')\n\n\t// Build launch command with optional --one-shot flag\n\t// Use provided executable path or fallback to 'il'\n\tconst executable = executablePath ?? 'iloom'\n\tlet launchCommand = `${executable} spin`\n\tif (oneShot !== 'default') {\n\t\tlaunchCommand += ` --one-shot=${oneShot}`\n\t}\n\n\t// Append --set arguments if provided\n\tif (setArguments && setArguments.length > 0) {\n\t\tfor (const setArg of setArguments) {\n\t\t\tlaunchCommand += ` --set ${setArg}`\n\t\t}\n\t}\n\n\t// Apply terminal background color if branch name available\n\tlet backgroundColor: { r: number; g: number; b: number } | undefined\n\tif (branchName) {\n\t\ttry {\n\t\t\tconst { generateColorFromBranchName } = await import('./color.js')\n\t\t\tconst colorData = generateColorFromBranchName(branchName)\n\t\t\tbackgroundColor = colorData.rgb\n\t\t} catch (error) {\n\t\t\tlogger.warn(\n\t\t\t\t`Failed to generate terminal color: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t// Check if .env file exists in workspace\n\tconst hasEnvFile = existsSync(join(workspacePath, '.env'))\n\n\t// Open new terminal window with Claude\n\tawait openTerminalWindow({\n\t\tworkspacePath,\n\t\tcommand: launchCommand,\n\t\t...(backgroundColor && { backgroundColor }),\n\t\tincludeEnvSetup: hasEnvFile, // source .env only if it exists\n\t\t...(port !== undefined && { port, includePortExport: true }),\n\t})\n}\n\n/**\n * Generate a branch name using Claude with fallback\n * This matches the implementation that was working in ClaudeBranchNameStrategy\n */\nexport async function generateBranchName(\n\tissueTitle: string,\n\tissueNumber: string | number,\n\tmodel: string = 'haiku'\n): Promise<string> {\n\ttry {\n\t\t// Check if Claude CLI is available\n\t\tconst isAvailable = await detectClaudeCli()\n\t\tif (!isAvailable) {\n\t\t\tlogger.warn('Claude CLI not available, using fallback branch name')\n\t\t\treturn `feat/issue-${issueNumber}`\n\t\t}\n\n\t\tlogger.debug('Generating branch name with Claude', { issueNumber, issueTitle })\n\n\t\t// Use the proven prompt format from ClaudeBranchNameStrategy\n\t\tconst prompt = `<Task>\nGenerate a git branch name for the following issue:\n<Issue>\n<IssueNumber>${issueNumber}</IssueNumber>\n<IssueTitle>${issueTitle}</IssueTitle>\n</Issue>\n\n<Requirements>\n<IssueNumber>Must use this exact issue number: ${issueNumber}</IssueNumber>\n<Format>Format must be: {prefix}/issue-${issueNumber}__{description}</Format>\n<Prefix>Prefix must be one of: feat, fix, docs, refactor, test, chore</Prefix>\n<MaxLength>Maximum 50 characters total</MaxLength>\n<Characters>Only lowercase letters, numbers, and hyphens allowed</Characters>\n<Output>Reply with ONLY the branch name, nothing else</Output>\n</Requirements>\n</Task>`\n\n\t\tlogger.debug('Sending prompt to Claude', { prompt })\n\n\t\tconst result = (await launchClaude(prompt, {\n\t\t\tmodel,\n\t\t\theadless: true,\n\t\t})) as string\n\n\t\t// Normalize to lowercase for consistency (Linear IDs are uppercase but branches should be lowercase)\n\t\tconst branchName = result.trim().toLowerCase()\n\t\tlogger.debug('Claude returned branch name', { branchName, issueNumber })\n\n\t\t// Validate generated name using same validation as ClaudeBranchNameStrategy\n\t\tif (!branchName || !isValidBranchName(branchName, issueNumber)) {\n\t\t\tlogger.warn('Invalid branch name from Claude, using fallback', { branchName })\n\t\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t\t}\n\n\t\treturn branchName\n\t} catch (error) {\n\t\tlogger.warn('Failed to generate branch name with Claude', { error })\n\t\treturn `feat/issue-${issueNumber}`.toLowerCase()\n\t}\n}\n\n/**\n * Validate branch name format\n * Check format: {prefix}/issue-{number}__{description}\n * Uses case-insensitive matching for issue number (Linear uses uppercase like MARK-1)\n */\nfunction isValidBranchName(name: string, issueNumber: string | number): boolean {\n\tconst pattern = new RegExp(`^(feat|fix|docs|refactor|test|chore)/issue-${issueNumber}__[a-z0-9-]+$`, 'i')\n\treturn pattern.test(name) && name.length <= 50\n}\n"],"mappings":"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","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  getClaudeVersion,
6
6
  launchClaude,
7
7
  launchClaudeInNewTerminalWindow
8
- } from "./chunk-XXV3UFZL.js";
8
+ } from "./chunk-ZWFBBPJI.js";
9
9
  import "./chunk-GEHQXLEI.js";
10
10
  export {
11
11
  detectClaudeCli,
@@ -14,4 +14,4 @@ export {
14
14
  launchClaude,
15
15
  launchClaudeInNewTerminalWindow
16
16
  };
17
- //# sourceMappingURL=claude-GOP6PFC7.js.map
17
+ //# sourceMappingURL=claude-LUZ35IMK.js.map
@@ -5,7 +5,7 @@ import {
5
5
  EnvironmentManager,
6
6
  LoomManager,
7
7
  ResourceCleanup
8
- } from "./chunk-5EF7Z346.js";
8
+ } from "./chunk-HBYZH6GD.js";
9
9
  import {
10
10
  IdentifierParser,
11
11
  loadEnvIntoProcess
@@ -18,13 +18,13 @@ import {
18
18
  } from "./chunk-JQFO7QQN.js";
19
19
  import {
20
20
  GitWorktreeManager
21
- } from "./chunk-4HHRTA7Q.js";
21
+ } from "./chunk-TSKY3JI7.js";
22
22
  import "./chunk-ZT3YZB4K.js";
23
23
  import "./chunk-2ZPFJQ3B.js";
24
24
  import {
25
25
  SettingsManager
26
- } from "./chunk-ML3NRPNB.js";
27
- import "./chunk-MAVL6PJF.js";
26
+ } from "./chunk-O5OH5MRX.js";
27
+ import "./chunk-4BGK7T6X.js";
28
28
  import {
29
29
  promptConfirmation
30
30
  } from "./chunk-JKXJ7BGL.js";
@@ -72,9 +72,9 @@ var CleanupCommand = class {
72
72
  );
73
73
  if (!this.loomManager) {
74
74
  const { GitHubService } = await import("./GitHubService-EBOETDIW.js");
75
- const { ClaudeContextManager } = await import("./ClaudeContextManager-MUQSDY2E.js");
75
+ const { ClaudeContextManager } = await import("./ClaudeContextManager-3VXA6UPR.js");
76
76
  const { ProjectCapabilityDetector } = await import("./ProjectCapabilityDetector-34LU7JJ4.js");
77
- const { DefaultBranchNamingService } = await import("./BranchNamingService-3OQPRSWT.js");
77
+ const { DefaultBranchNamingService } = await import("./BranchNamingService-OMWKUYMM.js");
78
78
  this.loomManager = new LoomManager(
79
79
  this.gitWorktreeManager,
80
80
  new GitHubService(),
@@ -268,7 +268,7 @@ var CleanupCommand = class {
268
268
  const { force, dryRun } = parsed.options;
269
269
  let parsedInput = await this.identifierParser.parseForPatternDetection(identifier);
270
270
  if (parsedInput.type === "branch" && parsedInput.branchName) {
271
- const { extractIssueNumber } = await import("./git-TDXKRTXM.js");
271
+ const { extractIssueNumber } = await import("./git-34Z6QVDS.js");
272
272
  const extractedNumber = extractIssueNumber(parsedInput.branchName);
273
273
  if (extractedNumber !== null) {
274
274
  parsedInput = {
@@ -360,8 +360,8 @@ var CleanupCommand = class {
360
360
  const worktrees = await this.gitWorktreeManager.listWorktrees();
361
361
  const matchingWorktrees = worktrees.filter((wt) => {
362
362
  const path = wt.path.toLowerCase();
363
- const numStr = String(issueNumber);
364
- const pattern = new RegExp(`(?<!\\d)${numStr}(?!\\d)`);
363
+ const idStr = String(issueNumber).toLowerCase();
364
+ const pattern = new RegExp(`(?:^|[/_-])${idStr}(?:[/_-]|$)`);
365
365
  return pattern.test(path);
366
366
  });
367
367
  if (matchingWorktrees.length === 0) {
@@ -466,4 +466,4 @@ var CleanupCommand = class {
466
466
  export {
467
467
  CleanupCommand
468
468
  };
469
- //# sourceMappingURL=cleanup-7RWLBSLE.js.map
469
+ //# sourceMappingURL=cleanup-3MONU4PU.js.map