@iloom/cli 0.8.3 → 0.9.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 (180) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +50 -4
  3. package/dist/{BranchNamingService-AO7BPIUJ.js → BranchNamingService-K6XNWQ6C.js} +2 -2
  4. package/dist/ClaudeContextManager-X2Y72GRL.js +14 -0
  5. package/dist/ClaudeService-7P32TTES.js +13 -0
  6. package/dist/{GitHubService-ACZVNTJE.js → GitHubService-O7T6CFAJ.js} +3 -3
  7. package/dist/{LoomLauncher-NHZMEVTQ.js → LoomLauncher-3I47SUPV.js} +6 -6
  8. package/dist/{ProjectCapabilityDetector-IA56AUE6.js → ProjectCapabilityDetector-N5L7T4IY.js} +3 -3
  9. package/dist/PromptTemplateManager-36YLQRHP.js +11 -0
  10. package/dist/README.md +50 -4
  11. package/dist/{SettingsManager-VCVLL32H.js → SettingsManager-QR7V2IW2.js} +2 -2
  12. package/dist/agents/iloom-artifact-reviewer.md +280 -0
  13. package/dist/agents/iloom-code-reviewer.md +9 -7
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +21 -6
  15. package/dist/agents/iloom-issue-analyzer.md +21 -6
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +21 -6
  17. package/dist/agents/iloom-issue-enhancer.md +21 -6
  18. package/dist/agents/iloom-issue-implementer.md +21 -6
  19. package/dist/agents/iloom-issue-planner.md +21 -6
  20. package/dist/{build-Z3WCIKPD.js → build-IC4CJRMP.js} +8 -8
  21. package/dist/{chunk-TVH67KEO.js → chunk-2HZX6AMR.js} +2 -2
  22. package/dist/{chunk-VZYSM7N7.js → chunk-2JPXGGP4.js} +20 -15
  23. package/dist/chunk-2JPXGGP4.js.map +1 -0
  24. package/dist/{chunk-SC6X5EBG.js → chunk-3P6J4IZZ.js} +3 -3
  25. package/dist/{chunk-HSGZW3ID.js → chunk-4GAJJUYS.js} +3 -3
  26. package/dist/chunk-4GAJJUYS.js.map +1 -0
  27. package/dist/{chunk-RD7I2Q2F.js → chunk-4LKGCFGG.js} +2 -2
  28. package/dist/{chunk-GWONJE3X.js → chunk-4ZIHFUPN.js} +226 -62
  29. package/dist/chunk-4ZIHFUPN.js.map +1 -0
  30. package/dist/{chunk-SSASIBDJ.js → chunk-5LVVQGB3.js} +5 -5
  31. package/dist/{chunk-4BSXZ5YZ.js → chunk-B7U6OKUR.js} +5 -24
  32. package/dist/chunk-B7U6OKUR.js.map +1 -0
  33. package/dist/{chunk-RNBIISBZ.js → chunk-ENGCJIYQ.js} +48 -3
  34. package/dist/chunk-ENGCJIYQ.js.map +1 -0
  35. package/dist/{chunk-IGKPPACU.js → chunk-FO5GGFOV.js} +17 -8
  36. package/dist/chunk-FO5GGFOV.js.map +1 -0
  37. package/dist/{chunk-GDS2HXSW.js → chunk-H6ST2TGP.js} +20 -3
  38. package/dist/chunk-H6ST2TGP.js.map +1 -0
  39. package/dist/{chunk-A7XHHUEV.js → chunk-HZXBHMVM.js} +47 -22
  40. package/dist/chunk-HZXBHMVM.js.map +1 -0
  41. package/dist/{chunk-44Y5IF7P.js → chunk-I3HMNWQQ.js} +9 -8
  42. package/dist/chunk-I3HMNWQQ.js.map +1 -0
  43. package/dist/{chunk-XJHQVOT6.js → chunk-J7FJ6PUT.js} +2 -2
  44. package/dist/chunk-JT5LZRMI.js +302 -0
  45. package/dist/chunk-JT5LZRMI.js.map +1 -0
  46. package/dist/{chunk-Q457PKGH.js → chunk-KAYXR544.js} +2 -2
  47. package/dist/{chunk-XU5A6BWA.js → chunk-MZPRBNYC.js} +4 -4
  48. package/dist/{chunk-XHNACIHO.js → chunk-NTTSUAVM.js} +2 -2
  49. package/dist/{chunk-PLI3JQWT.js → chunk-OAVJR4PM.js} +2 -2
  50. package/dist/{chunk-UDZCTLD6.js → chunk-OK7LUTRW.js} +3 -3
  51. package/dist/{chunk-PBSHQVCT.js → chunk-POU2UMWN.js} +5 -5
  52. package/dist/chunk-QN47QVBX.js +131 -0
  53. package/dist/chunk-QN47QVBX.js.map +1 -0
  54. package/dist/{chunk-3FC3VNEX.js → chunk-RD7OPXZK.js} +34 -8
  55. package/dist/chunk-RD7OPXZK.js.map +1 -0
  56. package/dist/{chunk-O6LECMT6.js → chunk-TGRK3CHF.js} +8 -8
  57. package/dist/{chunk-7GLZVDPQ.js → chunk-TL72BGP6.js} +2 -2
  58. package/dist/{chunk-THS5L54H.js → chunk-TRUMP4DA.js} +26 -2
  59. package/dist/{chunk-THS5L54H.js.map → chunk-TRUMP4DA.js.map} +1 -1
  60. package/dist/{chunk-52MVUK5V.js → chunk-USSL2X4A.js} +2 -2
  61. package/dist/chunk-USSL2X4A.js.map +1 -0
  62. package/dist/{chunk-XPKN3QWY.js → chunk-VOGGLPG5.js} +1 -2
  63. package/dist/{chunk-IWIIOFEB.js → chunk-XFEK2X2D.js} +16 -6
  64. package/dist/chunk-XFEK2X2D.js.map +1 -0
  65. package/dist/{chunk-PVW6JE7E.js → chunk-Y5HSSIK2.js} +2 -2
  66. package/dist/{chunk-4KGRPHM6.js → chunk-Y5O2ALDZ.js} +3 -3
  67. package/dist/{claude-V4HRPR4Z.js → claude-TP2QO3BU.js} +2 -2
  68. package/dist/{cleanup-NWNKWPUY.js → cleanup-D3CSRBBZ.js} +23 -18
  69. package/dist/{cleanup-NWNKWPUY.js.map → cleanup-D3CSRBBZ.js.map} +1 -1
  70. package/dist/cli.js +143 -93
  71. package/dist/cli.js.map +1 -1
  72. package/dist/{commit-534QIRHY.js → commit-IWGT42XN.js} +13 -13
  73. package/dist/{compile-UANHMNTS.js → compile-EOWJORKO.js} +8 -8
  74. package/dist/{contribute-7USRBWRM.js → contribute-WSJTV2RX.js} +4 -4
  75. package/dist/{dev-server-TO7RLYJI.js → dev-server-Q6M62ATG.js} +13 -13
  76. package/dist/{feedback-7ZZI6RC5.js → feedback-QPNDZQRV.js} +14 -14
  77. package/dist/{git-GUNOPP4Q.js → git-W3XUIFTR.js} +5 -3
  78. package/dist/hooks/iloom-hook.js +5 -3
  79. package/dist/{ignite-JBX3BUDE.js → ignite-OPO6EDYT.js} +95 -52
  80. package/dist/ignite-OPO6EDYT.js.map +1 -0
  81. package/dist/index.d.ts +40 -2
  82. package/dist/index.js +52 -15
  83. package/dist/index.js.map +1 -1
  84. package/dist/{chunk-FPNSFP6K.js → init-ALYWKNWG.js} +42 -329
  85. package/dist/init-ALYWKNWG.js.map +1 -0
  86. package/dist/issues-L7TBUPXT.js +116 -0
  87. package/dist/issues-L7TBUPXT.js.map +1 -0
  88. package/dist/{lint-XPODLDVA.js → lint-IHUH45OC.js} +8 -8
  89. package/dist/mcp/issue-management-server.js +3 -2
  90. package/dist/mcp/issue-management-server.js.map +1 -1
  91. package/dist/{open-M2SUR74Y.js → open-KWOV2OFO.js} +15 -15
  92. package/dist/{plan-FB4AOJ2Q.js → plan-BRJBFJHF.js} +60 -28
  93. package/dist/plan-BRJBFJHF.js.map +1 -0
  94. package/dist/{projects-325GEEGJ.js → projects-LH362JZQ.js} +3 -3
  95. package/dist/prompts/init-prompt.txt +9 -1
  96. package/dist/prompts/issue-prompt.txt +310 -0
  97. package/dist/prompts/plan-prompt.txt +4 -6
  98. package/dist/prompts/pr-prompt.txt +79 -0
  99. package/dist/prompts/regular-prompt.txt +205 -0
  100. package/dist/{rebase-4FNRBW3H.js → rebase-AJOJOZUG.js} +9 -9
  101. package/dist/{recap-GSXFEOD6.js → recap-GKJXMDXW.js} +5 -5
  102. package/dist/{run-GZNHRJB2.js → run-QEUVZF7J.js} +15 -15
  103. package/dist/schema/settings.schema.json +9 -1
  104. package/dist/{shell-2SPM3Z5O.js → shell-DAAVG4YN.js} +5 -5
  105. package/dist/{summary-Z4F7YFXE.js → summary-ZKOA35PT.js} +13 -13
  106. package/dist/{test-LBSPYIJW.js → test-5GPWWO3P.js} +8 -8
  107. package/dist/{test-git-ZPSPA2TP.js → test-git-EJUKDB7F.js} +3 -3
  108. package/dist/{test-prefix-6DLB2BHE.js → test-prefix-23TOBUXY.js} +3 -3
  109. package/dist/{test-webserver-XLJ2TZFP.js → test-webserver-CKROHFBQ.js} +5 -5
  110. package/dist/{vscode-LH3VSQ2W.js → vscode-6TOLFCI2.js} +5 -5
  111. package/package.json +2 -2
  112. package/dist/ClaudeContextManager-RDP6CLK6.js +0 -14
  113. package/dist/ClaudeService-FKPOQRA4.js +0 -13
  114. package/dist/PRManager-A63LT3NF.js +0 -16
  115. package/dist/PromptTemplateManager-OUYDHOPI.js +0 -9
  116. package/dist/chunk-3FC3VNEX.js.map +0 -1
  117. package/dist/chunk-44Y5IF7P.js.map +0 -1
  118. package/dist/chunk-4BSXZ5YZ.js.map +0 -1
  119. package/dist/chunk-52MVUK5V.js.map +0 -1
  120. package/dist/chunk-66QOCD5N.js +0 -79
  121. package/dist/chunk-66QOCD5N.js.map +0 -1
  122. package/dist/chunk-A7XHHUEV.js.map +0 -1
  123. package/dist/chunk-FPNSFP6K.js.map +0 -1
  124. package/dist/chunk-GDS2HXSW.js.map +0 -1
  125. package/dist/chunk-GWONJE3X.js.map +0 -1
  126. package/dist/chunk-HSGZW3ID.js.map +0 -1
  127. package/dist/chunk-IGKPPACU.js.map +0 -1
  128. package/dist/chunk-IWIIOFEB.js.map +0 -1
  129. package/dist/chunk-RNBIISBZ.js.map +0 -1
  130. package/dist/chunk-VZYSM7N7.js.map +0 -1
  131. package/dist/git-GUNOPP4Q.js.map +0 -1
  132. package/dist/ignite-JBX3BUDE.js.map +0 -1
  133. package/dist/init-XXDIB2UJ.js +0 -21
  134. package/dist/init-XXDIB2UJ.js.map +0 -1
  135. package/dist/plan-FB4AOJ2Q.js.map +0 -1
  136. /package/dist/{BranchNamingService-AO7BPIUJ.js.map → BranchNamingService-K6XNWQ6C.js.map} +0 -0
  137. /package/dist/{ClaudeContextManager-RDP6CLK6.js.map → ClaudeContextManager-X2Y72GRL.js.map} +0 -0
  138. /package/dist/{ClaudeService-FKPOQRA4.js.map → ClaudeService-7P32TTES.js.map} +0 -0
  139. /package/dist/{GitHubService-ACZVNTJE.js.map → GitHubService-O7T6CFAJ.js.map} +0 -0
  140. /package/dist/{LoomLauncher-NHZMEVTQ.js.map → LoomLauncher-3I47SUPV.js.map} +0 -0
  141. /package/dist/{PRManager-A63LT3NF.js.map → ProjectCapabilityDetector-N5L7T4IY.js.map} +0 -0
  142. /package/dist/{ProjectCapabilityDetector-IA56AUE6.js.map → PromptTemplateManager-36YLQRHP.js.map} +0 -0
  143. /package/dist/{PromptTemplateManager-OUYDHOPI.js.map → SettingsManager-QR7V2IW2.js.map} +0 -0
  144. /package/dist/{build-Z3WCIKPD.js.map → build-IC4CJRMP.js.map} +0 -0
  145. /package/dist/{chunk-TVH67KEO.js.map → chunk-2HZX6AMR.js.map} +0 -0
  146. /package/dist/{chunk-SC6X5EBG.js.map → chunk-3P6J4IZZ.js.map} +0 -0
  147. /package/dist/{chunk-RD7I2Q2F.js.map → chunk-4LKGCFGG.js.map} +0 -0
  148. /package/dist/{chunk-SSASIBDJ.js.map → chunk-5LVVQGB3.js.map} +0 -0
  149. /package/dist/{chunk-XJHQVOT6.js.map → chunk-J7FJ6PUT.js.map} +0 -0
  150. /package/dist/{chunk-Q457PKGH.js.map → chunk-KAYXR544.js.map} +0 -0
  151. /package/dist/{chunk-XU5A6BWA.js.map → chunk-MZPRBNYC.js.map} +0 -0
  152. /package/dist/{chunk-XHNACIHO.js.map → chunk-NTTSUAVM.js.map} +0 -0
  153. /package/dist/{chunk-PLI3JQWT.js.map → chunk-OAVJR4PM.js.map} +0 -0
  154. /package/dist/{chunk-UDZCTLD6.js.map → chunk-OK7LUTRW.js.map} +0 -0
  155. /package/dist/{chunk-PBSHQVCT.js.map → chunk-POU2UMWN.js.map} +0 -0
  156. /package/dist/{chunk-O6LECMT6.js.map → chunk-TGRK3CHF.js.map} +0 -0
  157. /package/dist/{chunk-7GLZVDPQ.js.map → chunk-TL72BGP6.js.map} +0 -0
  158. /package/dist/{chunk-XPKN3QWY.js.map → chunk-VOGGLPG5.js.map} +0 -0
  159. /package/dist/{chunk-PVW6JE7E.js.map → chunk-Y5HSSIK2.js.map} +0 -0
  160. /package/dist/{chunk-4KGRPHM6.js.map → chunk-Y5O2ALDZ.js.map} +0 -0
  161. /package/dist/{SettingsManager-VCVLL32H.js.map → claude-TP2QO3BU.js.map} +0 -0
  162. /package/dist/{commit-534QIRHY.js.map → commit-IWGT42XN.js.map} +0 -0
  163. /package/dist/{compile-UANHMNTS.js.map → compile-EOWJORKO.js.map} +0 -0
  164. /package/dist/{contribute-7USRBWRM.js.map → contribute-WSJTV2RX.js.map} +0 -0
  165. /package/dist/{dev-server-TO7RLYJI.js.map → dev-server-Q6M62ATG.js.map} +0 -0
  166. /package/dist/{feedback-7ZZI6RC5.js.map → feedback-QPNDZQRV.js.map} +0 -0
  167. /package/dist/{claude-V4HRPR4Z.js.map → git-W3XUIFTR.js.map} +0 -0
  168. /package/dist/{lint-XPODLDVA.js.map → lint-IHUH45OC.js.map} +0 -0
  169. /package/dist/{open-M2SUR74Y.js.map → open-KWOV2OFO.js.map} +0 -0
  170. /package/dist/{projects-325GEEGJ.js.map → projects-LH362JZQ.js.map} +0 -0
  171. /package/dist/{rebase-4FNRBW3H.js.map → rebase-AJOJOZUG.js.map} +0 -0
  172. /package/dist/{recap-GSXFEOD6.js.map → recap-GKJXMDXW.js.map} +0 -0
  173. /package/dist/{run-GZNHRJB2.js.map → run-QEUVZF7J.js.map} +0 -0
  174. /package/dist/{shell-2SPM3Z5O.js.map → shell-DAAVG4YN.js.map} +0 -0
  175. /package/dist/{summary-Z4F7YFXE.js.map → summary-ZKOA35PT.js.map} +0 -0
  176. /package/dist/{test-LBSPYIJW.js.map → test-5GPWWO3P.js.map} +0 -0
  177. /package/dist/{test-git-ZPSPA2TP.js.map → test-git-EJUKDB7F.js.map} +0 -0
  178. /package/dist/{test-prefix-6DLB2BHE.js.map → test-prefix-23TOBUXY.js.map} +0 -0
  179. /package/dist/{test-webserver-XLJ2TZFP.js.map → test-webserver-CKROHFBQ.js.map} +0 -0
  180. /package/dist/{vscode-LH3VSQ2W.js.map → vscode-6TOLFCI2.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, --jq, or GraphQL was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json') ||\n\t\t(args[0] === 'api' && args[1] === 'graphql')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: string | number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}\n\n// GitHub Sub-Issue Operations\n\n/**\n * Get the GraphQL node ID for a GitHub issue\n * Required for sub-issue API which uses node IDs, not issue numbers\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns GraphQL node ID (e.g., \"I_kwDOPvp_cc7...\")\n */\nexport async function getIssueNodeId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<string> {\n\tlogger.debug('Fetching GitHub issue node ID', { issueNumber, repo })\n\n\tconst args = ['issue', 'view', String(issueNumber), '--json', 'id']\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\tconst result = await executeGhCommand<{ id: string }>(args)\n\treturn result.id\n}\n\n/**\n * Link a child issue to a parent issue using GitHub's sub-issue API\n * Requires GraphQL-Features: sub_issues header\n * @param parentNodeId - GraphQL node ID of the parent issue\n * @param childNodeId - GraphQL node ID of the child issue\n */\nexport async function addSubIssue(\n\tparentNodeId: string,\n\tchildNodeId: string\n): Promise<void> {\n\tlogger.debug('Linking child issue to parent', { parentNodeId, childNodeId })\n\n\tconst mutation = `\n\t\tmutation addSubIssue($parentId: ID!, $subIssueId: ID!) {\n\t\t\taddSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {\n\t\t\t\tissue { id }\n\t\t\t\tsubIssue { id }\n\t\t\t}\n\t\t}\n\t`\n\n\tawait executeGhCommand([\n\t\t'api', 'graphql',\n\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t'-f', `query=${mutation}`,\n\t\t'-F', `parentId=${parentNodeId}`,\n\t\t'-F', `subIssueId=${childNodeId}`,\n\t])\n}\n\n/**\n * Get sub-issues (children) of a parent GitHub issue\n * Uses GraphQL to query the sub-issue relationship\n * @param issueNumber - The parent issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of child issues with id, title, url, and state\n */\nexport async function getSubIssues(\n\tissueNumber: number,\n\trepo?: string\n): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub sub-issues', { issueNumber, repo })\n\n\t// Get the node ID for the parent issue\n\tconst parentNodeId = await getIssueNodeId(issueNumber, repo)\n\n\t// Query sub-issues using GraphQL\n\tconst query = `\n\t\tquery getSubIssues($parentId: ID!) {\n\t\t\tnode(id: $parentId) {\n\t\t\t\t... on Issue {\n\t\t\t\t\tsubIssues(first: 100) {\n\t\t\t\t\t\tnodes {\n\t\t\t\t\t\t\tnumber\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\tstate\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t`\n\n\tinterface SubIssueNode {\n\t\tnumber: number\n\t\ttitle: string\n\t\turl: string\n\t\tstate: string\n\t}\n\n\tinterface SubIssuesResponse {\n\t\tdata: {\n\t\t\tnode: {\n\t\t\t\tsubIssues: {\n\t\t\t\t\tnodes: SubIssueNode[]\n\t\t\t\t}\n\t\t\t} | null\n\t\t}\n\t}\n\n\ttry {\n\t\tconst result = await executeGhCommand<SubIssuesResponse>([\n\t\t\t'api', 'graphql',\n\t\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t\t'-f', `query=${query}`,\n\t\t\t'-F', `parentId=${parentNodeId}`,\n\t\t])\n\n\t\tconst subIssues = result.data.node?.subIssues?.nodes ?? []\n\n\t\treturn subIssues.map(issue => ({\n\t\t\tid: String(issue.number),\n\t\t\ttitle: issue.title,\n\t\t\turl: issue.url,\n\t\t\tstate: issue.state.toLowerCase(),\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array if sub-issues feature is not available or no children\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for feature not available or empty result\n\t\t\tif (combinedError.includes('sub_issues') || combinedError.includes('null')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n// GitHub Issue Dependency Operations\n\n/**\n * GitHub dependency result from API\n */\ninterface GitHubDependency {\n\tid: number\n\tnumber: number\n\ttitle: string\n\tstate: string\n\thtml_url: string\n}\n\n/**\n * Get the internal database ID for a GitHub issue\n * Required for dependency API which uses database IDs, not node IDs\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Internal GitHub issue database ID\n */\nexport async function getIssueDatabaseId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<number> {\n\tlogger.debug('Fetching GitHub issue database ID', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}`\n\n\tconst result = await executeGhCommand<{ id: number }>([\n\t\t'api',\n\t\tapiPath,\n\t\t'--jq',\n\t\t'{id: .id}',\n\t])\n\n\treturn result.id\n}\n\n/**\n * Get dependencies for a GitHub issue\n * Uses GitHub's issue dependencies API\n * @param issueNumber - The issue number\n * @param direction - 'blocking' for issues this blocks, 'blocked_by' for issues blocking this\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of dependency objects with id, title, url, state\n */\nexport async function getIssueDependencies(\n\tissueNumber: number,\n\tdirection: 'blocking' | 'blocked_by',\n\trepo?: string\n): Promise<Array<{ id: string; databaseId: number; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub issue dependencies', { issueNumber, direction, repo })\n\n\t// Use the dependencies API with the appropriate direction endpoint\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/dependencies/${direction}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/dependencies/${direction}`\n\n\ttry {\n\t\tconst result = await executeGhCommand<GitHubDependency[]>([\n\t\t\t'api',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\t'--jq', '.',\n\t\t\tapiPath,\n\t\t])\n\n\t\treturn (result ?? []).map(dep => ({\n\t\t\tid: String(dep.number),\n\t\t\tdatabaseId: dep.id,\n\t\t\ttitle: dep.title,\n\t\t\turl: dep.html_url,\n\t\t\tstate: dep.state,\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array for 404 on the dependencies endpoint\n\t\t// This indicates the issue exists but has no dependencies configured\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for 404 specifically on dependencies endpoint\n\t\t\tif (combinedError.includes('404') && combinedError.includes('dependencies')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/**\n * Create a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n * @throws Error with specific message for: dependency already exists, issue not found, or dependencies feature not enabled\n */\nexport async function createIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Creating GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// POST to the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\n\ttry {\n\t\tawait executeGhCommand([\n\t\t\t'api',\n\t\t\t'-X', 'POST',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\tapiPath,\n\t\t\t'-F', `issue_id=${blockingIssueDatabaseId}`,\n\t\t])\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for dependency already exists (422 Unprocessable Entity)\n\t\t\tif (combinedError.includes('422') || combinedError.includes('already exists') || combinedError.includes('Unprocessable Entity')) {\n\t\t\t\tthrow new Error(`Dependency already exists: issue #${blockedIssueNumber} is already blocked by the specified issue`)\n\t\t\t}\n\n\t\t\t// Check for issue not found (404)\n\t\t\tif (combinedError.includes('404') || combinedError.includes('Not Found')) {\n\t\t\t\tthrow new Error(`Issue not found: unable to create dependency for issue #${blockedIssueNumber}. The issue may not exist or you may not have access to it.`)\n\t\t\t}\n\n\t\t\t// Check for dependencies feature not enabled (403 or specific error message)\n\t\t\tif (combinedError.includes('403') || combinedError.includes('Forbidden') || combinedError.includes('not enabled')) {\n\t\t\t\tthrow new Error(`Dependencies feature not enabled: the repository may not have issue dependencies enabled. This feature requires GitHub Enterprise or specific repository settings.`)\n\t\t\t}\n\t\t}\n\n\t\t// Re-throw the original error if it doesn't match any known patterns\n\t\tthrow error\n\t}\n}\n\n/**\n * Remove a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n */\nexport async function removeIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Removing GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// DELETE from the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\n\tawait executeGhCommand([\n\t\t'api',\n\t\t'-X', 'DELETE',\n\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\tapiPath,\n\t])\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, --jq, or GraphQL was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json') ||\n\t\t(args[0] === 'api' && args[1] === 'graphql')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: string | number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}\n\n// GitHub Sub-Issue Operations\n\n/**\n * Get the GraphQL node ID for a GitHub issue\n * Required for sub-issue API which uses node IDs, not issue numbers\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns GraphQL node ID (e.g., \"I_kwDOPvp_cc7...\")\n */\nexport async function getIssueNodeId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<string> {\n\tlogger.debug('Fetching GitHub issue node ID', { issueNumber, repo })\n\n\tconst args = ['issue', 'view', String(issueNumber), '--json', 'id']\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\tconst result = await executeGhCommand<{ id: string }>(args)\n\treturn result.id\n}\n\n/**\n * Link a child issue to a parent issue using GitHub's sub-issue API\n * Requires GraphQL-Features: sub_issues header\n * @param parentNodeId - GraphQL node ID of the parent issue\n * @param childNodeId - GraphQL node ID of the child issue\n */\nexport async function addSubIssue(\n\tparentNodeId: string,\n\tchildNodeId: string\n): Promise<void> {\n\tlogger.debug('Linking child issue to parent', { parentNodeId, childNodeId })\n\n\tconst mutation = `\n\t\tmutation addSubIssue($parentId: ID!, $subIssueId: ID!) {\n\t\t\taddSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {\n\t\t\t\tissue { id }\n\t\t\t\tsubIssue { id }\n\t\t\t}\n\t\t}\n\t`\n\n\tawait executeGhCommand([\n\t\t'api', 'graphql',\n\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t'-f', `query=${mutation}`,\n\t\t'-F', `parentId=${parentNodeId}`,\n\t\t'-F', `subIssueId=${childNodeId}`,\n\t])\n}\n\n/**\n * Get sub-issues (children) of a parent GitHub issue\n * Uses GraphQL to query the sub-issue relationship\n * @param issueNumber - The parent issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of child issues with id, title, url, and state\n */\nexport async function getSubIssues(\n\tissueNumber: number,\n\trepo?: string\n): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub sub-issues', { issueNumber, repo })\n\n\t// Get the node ID for the parent issue\n\tconst parentNodeId = await getIssueNodeId(issueNumber, repo)\n\n\t// Query sub-issues using GraphQL\n\tconst query = `\n\t\tquery getSubIssues($parentId: ID!) {\n\t\t\tnode(id: $parentId) {\n\t\t\t\t... on Issue {\n\t\t\t\t\tsubIssues(first: 100) {\n\t\t\t\t\t\tnodes {\n\t\t\t\t\t\t\tnumber\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t\tstate\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t`\n\n\tinterface SubIssueNode {\n\t\tnumber: number\n\t\ttitle: string\n\t\turl: string\n\t\tstate: string\n\t}\n\n\tinterface SubIssuesResponse {\n\t\tdata: {\n\t\t\tnode: {\n\t\t\t\tsubIssues: {\n\t\t\t\t\tnodes: SubIssueNode[]\n\t\t\t\t}\n\t\t\t} | null\n\t\t}\n\t}\n\n\ttry {\n\t\tconst result = await executeGhCommand<SubIssuesResponse>([\n\t\t\t'api', 'graphql',\n\t\t\t'-H', 'GraphQL-Features: sub_issues',\n\t\t\t'-f', `query=${query}`,\n\t\t\t'-F', `parentId=${parentNodeId}`,\n\t\t])\n\n\t\tconst subIssues = result.data.node?.subIssues?.nodes ?? []\n\n\t\treturn subIssues.map(issue => ({\n\t\t\tid: String(issue.number),\n\t\t\ttitle: issue.title,\n\t\t\turl: issue.url,\n\t\t\tstate: issue.state.toLowerCase(),\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array if sub-issues feature is not available or no children\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for feature not available or empty result\n\t\t\tif (combinedError.includes('sub_issues') || combinedError.includes('null')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n// GitHub Issue Dependency Operations\n\n/**\n * GitHub dependency result from API\n */\ninterface GitHubDependency {\n\tid: number\n\tnumber: number\n\ttitle: string\n\tstate: string\n\thtml_url: string\n}\n\n/**\n * Get the internal database ID for a GitHub issue\n * Required for dependency API which uses database IDs, not node IDs\n * @param issueNumber - The issue number\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Internal GitHub issue database ID\n */\nexport async function getIssueDatabaseId(\n\tissueNumber: number,\n\trepo?: string\n): Promise<number> {\n\tlogger.debug('Fetching GitHub issue database ID', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}`\n\n\tconst result = await executeGhCommand<{ id: number }>([\n\t\t'api',\n\t\tapiPath,\n\t\t'--jq',\n\t\t'{id: .id}',\n\t])\n\n\treturn result.id\n}\n\n/**\n * Get dependencies for a GitHub issue\n * Uses GitHub's issue dependencies API\n * @param issueNumber - The issue number\n * @param direction - 'blocking' for issues this blocks, 'blocked_by' for issues blocking this\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Array of dependency objects with id, title, url, state\n */\nexport async function getIssueDependencies(\n\tissueNumber: number,\n\tdirection: 'blocking' | 'blocked_by',\n\trepo?: string\n): Promise<Array<{ id: string; databaseId: number; title: string; url: string; state: string }>> {\n\tlogger.debug('Fetching GitHub issue dependencies', { issueNumber, direction, repo })\n\n\t// Use the dependencies API with the appropriate direction endpoint\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/dependencies/${direction}`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/dependencies/${direction}`\n\n\ttry {\n\t\tconst result = await executeGhCommand<GitHubDependency[]>([\n\t\t\t'api',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\t'--jq', '.',\n\t\t\tapiPath,\n\t\t])\n\n\t\treturn (result ?? []).map(dep => ({\n\t\t\tid: String(dep.number),\n\t\t\tdatabaseId: dep.id,\n\t\t\ttitle: dep.title,\n\t\t\turl: dep.html_url,\n\t\t\tstate: dep.state,\n\t\t}))\n\t} catch (error) {\n\t\t// Return empty array for 404 on the dependencies endpoint\n\t\t// This indicates the issue exists but has no dependencies configured\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for 404 specifically on dependencies endpoint\n\t\t\tif (combinedError.includes('404') && combinedError.includes('dependencies')) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/**\n * Create a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n * @throws Error with specific message for: dependency already exists, issue not found, or dependencies feature not enabled\n */\nexport async function createIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Creating GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// POST to the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by`\n\n\ttry {\n\t\tawait executeGhCommand([\n\t\t\t'api',\n\t\t\t'-X', 'POST',\n\t\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\t\tapiPath,\n\t\t\t'-F', `issue_id=${blockingIssueDatabaseId}`,\n\t\t])\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconst errorMessage = error.message\n\t\t\tconst stderr = 'stderr' in error ? (error as { stderr?: string }).stderr ?? '' : ''\n\t\t\tconst combinedError = `${errorMessage} ${stderr}`\n\n\t\t\t// Check for dependency already exists (422 Unprocessable Entity)\n\t\t\tif (combinedError.includes('422') || combinedError.includes('already exists') || combinedError.includes('Unprocessable Entity')) {\n\t\t\t\tthrow new Error(`Dependency already exists: issue #${blockedIssueNumber} is already blocked by the specified issue`)\n\t\t\t}\n\n\t\t\t// Check for issue not found (404)\n\t\t\tif (combinedError.includes('404') || combinedError.includes('Not Found')) {\n\t\t\t\tthrow new Error(`Issue not found: unable to create dependency for issue #${blockedIssueNumber}. The issue may not exist or you may not have access to it.`)\n\t\t\t}\n\n\t\t\t// Check for dependencies feature not enabled (403 or specific error message)\n\t\t\tif (combinedError.includes('403') || combinedError.includes('Forbidden') || combinedError.includes('not enabled')) {\n\t\t\t\tthrow new Error(`Dependencies feature not enabled: the repository may not have issue dependencies enabled. This feature requires GitHub Enterprise or specific repository settings.`)\n\t\t\t}\n\t\t}\n\n\t\t// Re-throw the original error if it doesn't match any known patterns\n\t\tthrow error\n\t}\n}\n\n/**\n * Remove a dependency between two issues (A blocks B)\n * Uses GitHub's issue dependencies API\n * @param blockedIssueNumber - The issue number that is blocked\n * @param blockingIssueDatabaseId - The database ID of the issue that blocks\n * @param repo - Optional repo in \"owner/repo\" format\n */\nexport async function removeIssueDependency(\n\tblockedIssueNumber: number,\n\tblockingIssueDatabaseId: number,\n\trepo?: string\n): Promise<void> {\n\tlogger.debug('Removing GitHub issue dependency', { blockedIssueNumber, blockingIssueDatabaseId, repo })\n\n\t// DELETE from the blocked issue's blocked_by endpoint with the blocking issue's database ID\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\t\t: `repos/:owner/:repo/issues/${blockedIssueNumber}/dependencies/blocked_by/${blockingIssueDatabaseId}`\n\n\tawait executeGhCommand([\n\t\t'api',\n\t\t'-X', 'DELETE',\n\t\t'-H', 'Accept: application/vnd.github+json',\n\t\t'-H', 'X-GitHub-Api-Version: 2022-11-28',\n\t\tapiPath,\n\t])\n}\n\n// Issue List Operations (for il issues command)\n\nexport interface GitHubIssueListItem {\n\tid: string\n\ttitle: string\n\tupdatedAt: string\n\turl: string\n\tstate: string\n}\n\n/**\n * Fetch a list of open GitHub issues sorted by recently updated\n * @param options - Fetch options\n * @param options.limit - Maximum number of issues to return (default: 100)\n * @param options.cwd - Working directory for gh CLI (default: process.cwd())\n * @returns Array of issues\n */\nexport async function fetchGitHubIssueList(\n\toptions?: { limit?: number; cwd?: string }\n): Promise<GitHubIssueListItem[]> {\n\tconst limit = options?.limit ?? 100\n\n\tlogger.debug('Fetching GitHub issue list', { limit, cwd: options?.cwd })\n\n\tconst result = await executeGhCommand<Array<{\n\t\tnumber: number\n\t\ttitle: string\n\t\tupdatedAt: string\n\t\turl: string\n\t\tstate: string\n\t}>>([\n\t\t'issue',\n\t\t'list',\n\t\t'--state', 'open',\n\t\t'--json', 'number,title,updatedAt,url,state',\n\t\t'--limit', String(limit),\n\t\t'--search', 'sort:updated-desc',\n\t], options?.cwd ? { cwd: options.cwd } : undefined)\n\n\treturn (result ?? []).map(item => ({\n\t\tid: String(item.number),\n\t\ttitle: item.title,\n\t\tupdatedAt: item.updatedAt,\n\t\turl: item.url,\n\t\tstate: item.state.toLowerCase(),\n\t}))\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  extractIssueNumber
4
- } from "./chunk-GDS2HXSW.js";
4
+ } from "./chunk-H6ST2TGP.js";
5
5
  import {
6
6
  extractPort,
7
7
  findEnvFileContainingVariable,
@@ -108,4 +108,4 @@ export {
108
108
  calculatePortFromIdentifier,
109
109
  getWorkspacePort
110
110
  };
111
- //# sourceMappingURL=chunk-52MVUK5V.js.map
111
+ //# sourceMappingURL=chunk-USSL2X4A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/port.ts"],"sourcesContent":["import { createHash } from 'crypto'\nimport path from 'path'\nimport fs from 'fs-extra'\nimport { parseEnvFile, extractPort, findEnvFileContainingVariable } from './env.js'\nimport { extractIssueNumber } from './git.js'\nimport { logger } from './logger.js'\n\n/**\n * Wrap a raw port that exceeds 65535 into the valid port range.\n * Uses modulo arithmetic to wrap back into [basePort+1, 65535].\n *\n * @param rawPort - The calculated port (basePort + issueNumber)\n * @param basePort - The base port (default: 3000)\n * @returns Port in valid range [basePort+1, 65535]\n */\nexport function wrapPort(rawPort: number, basePort: number): number {\n\tif (rawPort <= 65535) return rawPort\n\tconst range = 65535 - basePort\n\treturn ((rawPort - basePort - 1) % range) + basePort + 1\n}\n\n/**\n * Extract numeric suffix from alphanumeric issue ID (e.g., MARK-324 -> 324)\n * @returns The numeric part or null if no trailing number found\n */\nexport function extractNumericSuffix(issueId: string): number | null {\n\t// Match trailing digits after optional separator (-, _)\n\tconst match = issueId.match(/[-_]?(\\d+)$/)\n\tconst digits = match?.[1]\n\tif (digits === undefined) return null\n\treturn parseInt(digits, 10)\n}\n\n/**\n * Generate deterministic port offset from branch name using SHA256 hash\n * Range: 1-999 (matches existing random range for branches)\n *\n * @param branchName - Branch name to generate port offset from\n * @returns Port offset in range [1, 999]\n * @throws Error if branchName is empty\n */\nexport function generatePortOffsetFromBranchName(branchName: string): number {\n\t// Validate input\n\tif (!branchName || branchName.trim().length === 0) {\n\t\tthrow new Error('Branch name cannot be empty')\n\t}\n\n\t// Generate SHA256 hash of branch name (same pattern as color.ts)\n\tconst hash = createHash('sha256').update(branchName).digest('hex')\n\n\t// Take first 8 hex characters and convert to port offset (1-999)\n\tconst hashPrefix = hash.slice(0, 8)\n\tconst hashAsInt = parseInt(hashPrefix, 16)\n\tconst portOffset = (hashAsInt % 999) + 1 // +1 ensures range is 1-999, not 0-998\n\n\treturn portOffset\n}\n\n/**\n * Calculate deterministic port for branch-based workspace\n *\n * @param branchName - Branch name\n * @param basePort - Base port (default: 3000)\n * @returns Port number\n * @throws Error if branchName is empty\n */\n// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\nexport function calculatePortForBranch(branchName: string, basePort: number = 3000): number {\n\tconst offset = generatePortOffsetFromBranchName(branchName)\n\tconst port = basePort + offset\n\n\t// Use wrap-around for port overflow\n\treturn wrapPort(port, basePort)\n}\n\n/**\n * Calculate port from an identifier (issue number, PR number, or string).\n * This is the single source of truth for port calculation logic.\n *\n * Algorithm:\n * 1. Numeric identifiers: basePort + number (with wrapPort for overflow)\n * 2. String numeric (e.g., \"42\"): parse and same as above\n * 3. Alphanumeric with suffix (e.g., \"MARK-324\"): extract suffix and same as above\n * 4. Pure strings without numeric suffix: hash-based calculation via calculatePortForBranch\n *\n * @param identifier - The identifier (issue number, PR number, or string)\n * @param basePort - Base port (default: 3000)\n * @returns Port number in valid range\n */\n// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\nexport function calculatePortFromIdentifier(\n\tidentifier: string | number,\n\tbasePort: number = 3000\n): number {\n\t// Handle numeric identifiers directly\n\tif (typeof identifier === 'number') {\n\t\treturn wrapPort(basePort + identifier, basePort)\n\t}\n\n\t// Handle string identifiers\n\t// First, try to parse as pure numeric string\n\tconst numericValue = parseInt(identifier, 10)\n\tif (!isNaN(numericValue) && String(numericValue) === identifier) {\n\t\treturn wrapPort(basePort + numericValue, basePort)\n\t}\n\n\t// Try extracting numeric suffix from alphanumeric identifiers (e.g., MARK-324 -> 324)\n\tconst numericSuffix = extractNumericSuffix(identifier)\n\tif (numericSuffix !== null) {\n\t\treturn wrapPort(basePort + numericSuffix, basePort)\n\t}\n\n\t// For non-numeric strings without numeric suffix, use hash-based calculation\n\treturn calculatePortForBranch(`issue-${identifier}`, basePort)\n}\n\nexport interface GetWorkspacePortOptions {\n\tbasePort?: number | undefined\n\tworktreePath: string\n\tworktreeBranch: string\n\t/** If true, check .env files for PORT override before calculating. Defaults to false. */\n\tcheckEnvFile?: boolean\n}\n\nexport interface GetWorkspacePortDependencies {\n\tfileExists?: (path: string) => Promise<boolean>\n\treadFile?: (path: string) => Promise<string>\n\tlistWorktrees?: () => Promise<Array<{ path: string; branch: string }>>\n}\n\n/**\n * Get port for workspace - calculates based on workspace type, optionally checking .env files first.\n * Consolidates logic previously duplicated across dev-server, run, open commands.\n *\n * Priority (when checkEnvFile is true):\n * 1. Read PORT from dotenv-flow files (if present)\n * 2. Calculate from PR pattern (_pr_N suffix in directory name)\n * 3. Calculate from issue pattern (issue-N or alphanumeric like MARK-324)\n * 4. Calculate from branch name using deterministic hash\n *\n * When checkEnvFile is false (default), skips step 1 and only calculates.\n */\nexport async function getWorkspacePort(\n\toptions: GetWorkspacePortOptions,\n\tdependencies?: GetWorkspacePortDependencies\n): Promise<number> {\n\tconst basePort = options.basePort ?? 3000\n\tconst checkEnvFile = options.checkEnvFile ?? false\n\n\t// Only check .env files if explicitly requested\n\tif (checkEnvFile) {\n\t\tconst deps = {\n\t\t\tfileExists:\n\t\t\t\tdependencies?.fileExists ?? ((p: string): Promise<boolean> => fs.pathExists(p)),\n\t\t\treadFile:\n\t\t\t\tdependencies?.readFile ?? ((p: string): Promise<string> => fs.readFile(p, 'utf8')),\n\t\t}\n\n\t\t// Try to read PORT from any dotenv-flow file (as override)\n\t\tconst envFile = await findEnvFileContainingVariable(\n\t\t\toptions.worktreePath,\n\t\t\t'PORT',\n\t\t\tasync (p) => deps.fileExists(p),\n\t\t\tasync (p, varName) => {\n\t\t\t\tconst content = await deps.readFile(p)\n\t\t\t\tconst envMap = parseEnvFile(content)\n\t\t\t\treturn envMap.get(varName) ?? null\n\t\t\t}\n\t\t)\n\n\t\tif (envFile) {\n\t\t\tconst envPath = path.join(options.worktreePath, envFile)\n\t\t\tconst envContent = await deps.readFile(envPath)\n\t\t\tconst envMap = parseEnvFile(envContent)\n\t\t\tconst port = extractPort(envMap)\n\n\t\t\tif (port) {\n\t\t\t\tlogger.debug(`Using PORT from ${envFile}: ${port}`)\n\t\t\t\treturn port\n\t\t\t}\n\t\t}\n\n\t\tlogger.debug('PORT not found in any dotenv-flow file, calculating from workspace identifier')\n\t}\n\n\t// Calculate based on workspace identifier\n\n\t// Extract identifier from worktree path/branch\n\tconst dirName = path.basename(options.worktreePath)\n\n\t// Check for PR pattern: _pr_N\n\tconst prPattern = /_pr_(\\d+)$/\n\tconst prMatch = dirName.match(prPattern)\n\tif (prMatch?.[1]) {\n\t\tconst prNumber = parseInt(prMatch[1], 10)\n\t\tconst port = calculatePortFromIdentifier(prNumber, basePort)\n\t\tlogger.debug(`Calculated PORT for PR #${prNumber}: ${port}`)\n\t\treturn port\n\t}\n\n\t// Check for issue pattern: issue-N or alphanumeric like MARK-324\n\tconst issueId = extractIssueNumber(dirName) ?? extractIssueNumber(options.worktreeBranch)\n\tif (issueId !== null) {\n\t\tconst port = calculatePortFromIdentifier(issueId, basePort)\n\t\tlogger.debug(`Calculated PORT for issue ${issueId}: ${port}`)\n\t\treturn port\n\t}\n\n\t// Branch-based workspace - use deterministic hash\n\tconst port = calculatePortForBranch(options.worktreeBranch, basePort)\n\tlogger.debug(`Calculated PORT for branch \"${options.worktreeBranch}\": ${port}`)\n\treturn port\n}\n"],"mappings":"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","names":["port"]}
@@ -140,7 +140,6 @@ function getExplicitCapabilities(pkgJson) {
140
140
  }
141
141
 
142
142
  export {
143
- readPackageJson,
144
143
  getPackageConfig,
145
144
  parseBinField,
146
145
  hasWebDependencies,
@@ -148,4 +147,4 @@ export {
148
147
  getPackageScripts,
149
148
  getExplicitCapabilities
150
149
  };
151
- //# sourceMappingURL=chunk-XPKN3QWY.js.map
150
+ //# sourceMappingURL=chunk-VOGGLPG5.js.map
@@ -16,7 +16,8 @@ var AgentSettingsSchema = z.object({
16
16
  providers: z.record(
17
17
  z.enum(["claude", "gemini", "codex"]),
18
18
  z.string()
19
- ).optional().describe('Map of review providers to model names. Keys: claude, gemini, codex. Values: model name strings (e.g., "sonnet", "gemini-3-pro-preview", "gpt-5.2-codex")')
19
+ ).optional().describe('Map of review providers to model names. Keys: claude, gemini, codex. Values: model name strings (e.g., "sonnet", "gemini-3-pro-preview", "gpt-5.2-codex")'),
20
+ review: z.boolean().optional().describe("Whether artifacts from this agent should be reviewed before posting (defaults to false)")
20
21
  });
21
22
  var SpinAgentSettingsSchema = z.object({
22
23
  model: z.enum(["sonnet", "opus", "haiku"]).default("opus").describe("Claude model shorthand for spin orchestrator")
@@ -114,7 +115,7 @@ var IloomSettingsSchema = z.object({
114
115
  copyGitIgnoredPatterns: z.array(z.string().min(1, "Pattern cannot be empty")).optional().describe(`Glob patterns for gitignored files to copy to looms (e.g., ["*.db", "data/*.sqlite"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom's and claude's local settings are automatically copied and do not need to be specified here.`),
115
116
  workflows: WorkflowsSettingsSchema.describe("Per-workflow-type permission configurations"),
116
117
  agents: z.record(z.string(), AgentSettingsSchema).optional().nullable().describe(
117
- "Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements)"
118
+ "Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements), iloom-artifact-reviewer (reviews artifacts before posting)"
118
119
  ),
119
120
  spin: SpinAgentSettingsSchema.optional().describe(
120
121
  "Spin orchestrator configuration. Model defaults to opus when not configured."
@@ -128,6 +129,7 @@ var IloomSettingsSchema = z.object({
128
129
  capabilities: CapabilitiesSettingsSchema.describe("Project capability configurations"),
129
130
  databaseProviders: DatabaseProvidersSettingsSchema.describe("Database provider configurations"),
130
131
  issueManagement: z.object({
132
+ // SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts
131
133
  provider: z.enum(["github", "linear"]).optional().default("github").describe("Issue tracker provider (github, linear)"),
132
134
  github: z.object({
133
135
  remote: z.string().min(1, "Remote name cannot be empty").describe("Git remote name to use for GitHub operations")
@@ -139,10 +141,15 @@ var IloomSettingsSchema = z.object({
139
141
  }).optional()
140
142
  }).optional().describe("Issue management configuration"),
141
143
  mergeBehavior: z.object({
144
+ // SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts
142
145
  mode: z.enum(["local", "github-pr", "github-draft-pr"]).default("local"),
143
- remote: z.string().optional()
146
+ remote: z.string().optional(),
147
+ autoCommitPush: z.boolean().optional().describe(
148
+ "Auto-commit and push after code review in draft PR mode. Defaults to true when mode is github-draft-pr."
149
+ )
144
150
  }).optional().describe("Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)"),
145
151
  ide: z.object({
152
+ // SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts
146
153
  type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf", "antigravity"]).default("vscode").describe(
147
154
  "IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), windsurf (Windsurf editor), antigravity (Antigravity IDE)."
148
155
  )
@@ -189,7 +196,7 @@ var IloomSettingsSchemaNoDefaults = z.object({
189
196
  copyGitIgnoredPatterns: z.array(z.string().min(1, "Pattern cannot be empty")).optional().describe(`Glob patterns for gitignored files to copy to looms (e.g., ["*.db", "data/*.sqlite"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom's and claude's local settings are automatically copied and do not need to be specified here.`),
190
197
  workflows: WorkflowsSettingsSchemaNoDefaults.describe("Per-workflow-type permission configurations"),
191
198
  agents: z.record(z.string(), AgentSettingsSchema).optional().nullable().describe(
192
- "Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements)"
199
+ "Per-agent configuration overrides. Available agents: iloom-issue-analyzer (analyzes issues), iloom-issue-planner (creates implementation plans), iloom-issue-analyze-and-plan (combined analysis and planning), iloom-issue-complexity-evaluator (evaluates complexity), iloom-issue-enhancer (enhances issue descriptions), iloom-issue-implementer (implements code changes), iloom-code-reviewer (reviews code changes against requirements), iloom-artifact-reviewer (reviews artifacts before posting)"
193
200
  ),
194
201
  spin: z.object({
195
202
  model: z.enum(["sonnet", "opus", "haiku"]).optional()
@@ -217,7 +224,10 @@ var IloomSettingsSchemaNoDefaults = z.object({
217
224
  }).optional().describe("Issue management configuration"),
218
225
  mergeBehavior: z.object({
219
226
  mode: z.enum(["local", "github-pr", "github-draft-pr"]).optional(),
220
- remote: z.string().optional()
227
+ remote: z.string().optional(),
228
+ autoCommitPush: z.boolean().optional().describe(
229
+ "Auto-commit and push after code review in draft PR mode. Defaults to true when mode is github-draft-pr."
230
+ )
221
231
  }).optional().describe("Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)"),
222
232
  ide: z.object({
223
233
  type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf", "antigravity"]).optional().describe(
@@ -511,4 +521,4 @@ export {
511
521
  IloomSettingsSchemaNoDefaults,
512
522
  SettingsManager
513
523
  };
514
- //# sourceMappingURL=chunk-IWIIOFEB.js.map
524
+ //# sourceMappingURL=chunk-XFEK2X2D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/SettingsManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport path from 'path'\nimport os from 'os'\nimport { z } from 'zod'\nimport deepmerge from 'deepmerge'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Valid project capability values for Zod enum validation.\n * When updating this constant, also update ProjectCapability type in src/types/loom.ts\n */\nconst PROJECT_CAPABILITIES = ['cli', 'web'] as const\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\tenabled: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Whether this agent is enabled. Defaults to true.'),\n\tproviders: z\n\t\t.record(\n\t\t\tz.enum(['claude', 'gemini', 'codex']),\n\t\t\tz.string()\n\t\t)\n\t\t.optional()\n\t\t.describe('Map of review providers to model names. Keys: claude, gemini, codex. Values: model name strings (e.g., \"sonnet\", \"gemini-3-pro-preview\", \"gpt-5.2-codex\")'),\n\treview: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Whether artifacts from this agent should be reviewed before posting (defaults to false)'),\n})\n\n/**\n * Zod schema for spin agent settings with default model\n * Used for the spin orchestrator configuration\n */\nexport const SpinAgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('opus')\n\t\t.describe('Claude model shorthand for spin orchestrator'),\n})\n\n/**\n * Zod schema for plan command settings with default model\n * Used for the plan command configuration\n */\nexport const PlanCommandSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('opus')\n\t\t.describe('Claude model shorthand for plan command'),\n\tplanner: z\n\t\t.enum(['claude', 'gemini', 'codex'])\n\t\t.default('claude')\n\t\t.describe('AI provider for creating the plan'),\n\treviewer: z\n\t\t.enum(['claude', 'gemini', 'codex', 'none'])\n\t\t.default('none')\n\t\t.describe('AI provider for reviewing the plan (none to skip review)'),\n})\n\n/**\n * Zod schema for summary settings with default model\n * Used for session summary generation configuration\n */\nexport const SummarySettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('sonnet')\n\t\t.describe('Claude model shorthand for session summary generation'),\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 commit and finish workflows'),\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\tgenerateSummary: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Generate and post Claude session summary when finishing 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 commit and finish workflows'),\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\tgenerateSummary: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Generate and post Claude session summary when finishing 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\tcapabilities: z\n\t\t\t.array(z.enum(PROJECT_CAPABILITIES))\n\t\t\t.optional()\n\t\t\t.describe('Explicitly declared project capabilities (auto-detected if not specified)'),\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\tcapabilities: z\n\t\t\t.array(z.enum(PROJECT_CAPABILITIES))\n\t\t\t.optional()\n\t\t\t.describe('Explicitly declared project capabilities (auto-detected if not specified)'),\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t})\n\t\t\t.optional(),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for Neon database provider settings\n */\nexport const NeonSettingsSchema = z.object({\n\tprojectId: z\n\t\t.string()\n\t\t.min(1)\n\t\t.regex(/^[a-zA-Z0-9-]+$/, 'Neon project ID must contain only letters, numbers, and hyphens')\n\t\t.describe('Neon project ID found in your project URL (e.g., \"fantastic-fox-3566354\")'),\n\tparentBranch: z\n\t\t.string()\n\t\t.min(1)\n\t\t.describe('Branch from which new database branches are created'),\n})\n\n/**\n * Zod schema for database provider settings\n */\nexport const DatabaseProvidersSettingsSchema = z\n\t.object({\n\t\tneon: NeonSettingsSchema.optional().describe(\n\t\t\t'Neon database configuration. Requires Neon CLI installed and authenticated for database branching.',\n\t\t),\n\t})\n\t.optional()\n\n/**\n * Zod schema for iloom settings\n */\nexport const IloomSettingsSchema = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tcopyGitIgnoredPatterns: z\n\t\t.array(z.string().min(1, 'Pattern cannot be empty'))\n\t\t.optional()\n\t\t.describe('Glob patterns for gitignored files to copy to looms (e.g., [\"*.db\", \"data/*.sqlite\"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom\\'s and claude\\'s local settings are automatically copied and do not need to be specified here.'),\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-code-reviewer (reviews code changes against requirements), ' +\n\t\t\t\t'iloom-artifact-reviewer (reviews artifacts before posting)',\n\t\t),\n\tspin: SpinAgentSettingsSchema.optional().describe(\n\t\t'Spin orchestrator configuration. Model defaults to opus when not configured.',\n\t),\n\tplan: PlanCommandSettingsSchema.optional().describe(\n\t\t'Plan command configuration. Model defaults to opus, planner to claude, reviewer to none when not configured.',\n\t),\n\tsummary: SummarySettingsSchema.optional().describe(\n\t\t'Session summary generation configuration. Model defaults to sonnet when not configured.',\n\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\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\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\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\t\tmode: z.enum(['local', 'github-pr', 'github-draft-pr']).default('local'),\n\t\t\tremote: z.string().optional(),\n\t\t\tautoCommitPush: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Auto-commit and push after code review in draft PR mode. Defaults to true when mode is github-draft-pr.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\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), antigravity (Antigravity IDE).'\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, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(false)\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control. ' +\n\t\t\t\t\t\t'Default is false for safety; enable via init or explicitly if .vscode is gitignored.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.default('upstreamOnly')\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\n\t\t),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const IloomSettingsSchemaNoDefaults = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tcopyGitIgnoredPatterns: z\n\t\t.array(z.string().min(1, 'Pattern cannot be empty'))\n\t\t.optional()\n\t\t.describe('Glob patterns for gitignored files to copy to looms (e.g., [\"*.db\", \"data/*.sqlite\"]). Great for local dbs and large test data files that are too big to commit to git. Note: .env (dotenv-flow) files, iloom\\'s and claude\\'s local settings are automatically copied and do not need to be specified here.'),\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-code-reviewer (reviews code changes against requirements), ' +\n\t\t\t\t'iloom-artifact-reviewer (reviews artifacts before posting)',\n\t\t),\n\tspin: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Spin orchestrator configuration'),\n\tplan: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t\tplanner: z.enum(['claude', 'gemini', 'codex']).optional(),\n\t\t\treviewer: z.enum(['claude', 'gemini', 'codex', 'none']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Plan command configuration'),\n\tsummary: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Session summary generation configuration'),\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', 'github-draft-pr']).optional(),\n\t\t\tremote: z.string().optional(),\n\t\t\tautoCommitPush: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Auto-commit and push after code review in draft PR mode. Defaults to true when mode is github-draft-pr.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\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), antigravity (Antigravity IDE).'\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, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\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 spin agent settings derived from Zod schema\n */\nexport type SpinAgentSettings = z.infer<typeof SpinAgentSettingsSchema>\n\n/**\n * TypeScript type for plan command settings derived from Zod schema\n */\nexport type PlanCommandSettings = z.infer<typeof PlanCommandSettingsSchema>\n\n/**\n * TypeScript type for summary settings derived from Zod schema\n */\nexport type SummarySettings = z.infer<typeof SummarySettingsSchema>\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 * TypeScript input type for iloom settings (before Zod defaults are applied)\n * Used for validation where partial/input objects need to be accepted\n */\nexport type IloomSettingsInput = z.input<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: IloomSettingsInput): 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\t// SYNC: If this default changes, update displayDefaultsBox() in src/utils/first-run-setup.ts\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\t/**\n\t * Get the spin orchestrator model with default applied\n\t * Default is defined in SpinAgentSettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSpinModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.spin?.model ?? SpinAgentSettingsSchema.parse({}).model\n\t}\n\n\t/**\n\t * Get the plan command model with default applied\n\t * Default is defined in PlanCommandSettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetPlanModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.plan?.model ?? PlanCommandSettingsSchema.parse({}).model\n\t}\n\n\t/**\n\t * Get the plan command planner with default applied\n\t * Default is 'claude'\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Planner provider ('claude', 'gemini', or 'codex')\n\t */\n\tgetPlanPlanner(settings?: IloomSettings): 'claude' | 'gemini' | 'codex' {\n\t\treturn settings?.plan?.planner ?? 'claude'\n\t}\n\n\t/**\n\t * Get the plan command reviewer with default applied\n\t * Default is 'none' (no review step)\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Reviewer provider ('claude', 'gemini', 'codex', or 'none')\n\t */\n\tgetPlanReviewer(settings?: IloomSettings): 'claude' | 'gemini' | 'codex' | 'none' {\n\t\treturn settings?.plan?.reviewer ?? 'none'\n\t}\n\n\t/**\n\t * Get the session summary model with default applied\n\t * Default is defined in SummarySettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSummaryModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.summary?.model ?? SummarySettingsSchema.parse({}).model\n\t}\n}\n"],"mappings":"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","names":["path"]}
@@ -9,7 +9,7 @@ import {
9
9
  fetchProjectList,
10
10
  hasProjectScope,
11
11
  updateProjectItemField
12
- } from "./chunk-THS5L54H.js";
12
+ } from "./chunk-TRUMP4DA.js";
13
13
  import {
14
14
  promptConfirmation
15
15
  } from "./chunk-7JDMYTFZ.js";
@@ -300,4 +300,4 @@ State: ${entity.state}`;
300
300
  export {
301
301
  GitHubService
302
302
  };
303
- //# sourceMappingURL=chunk-PVW6JE7E.js.map
303
+ //# sourceMappingURL=chunk-Y5HSSIK2.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateIssueManagementMcpConfig
4
- } from "./chunk-XJHQVOT6.js";
4
+ } from "./chunk-J7FJ6PUT.js";
5
5
  import {
6
6
  openBrowser
7
7
  } from "./chunk-YETJNRQM.js";
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-7JDMYTFZ.js";
11
11
  import {
12
12
  launchClaude
13
- } from "./chunk-IGKPPACU.js";
13
+ } from "./chunk-FO5GGFOV.js";
14
14
  import {
15
15
  getLogger
16
16
  } from "./chunk-6MLEBAYZ.js";
@@ -244,4 +244,4 @@ export {
244
244
  IssueEnhancementService,
245
245
  capitalizeFirstLetter
246
246
  };
247
- //# sourceMappingURL=chunk-4KGRPHM6.js.map
247
+ //# sourceMappingURL=chunk-Y5O2ALDZ.js.map
@@ -7,7 +7,7 @@ import {
7
7
  getClaudeVersion,
8
8
  launchClaude,
9
9
  launchClaudeInNewTerminalWindow
10
- } from "./chunk-IGKPPACU.js";
10
+ } from "./chunk-FO5GGFOV.js";
11
11
  import "./chunk-6MLEBAYZ.js";
12
12
  import "./chunk-VT4PDUYT.js";
13
13
  export {
@@ -19,4 +19,4 @@ export {
19
19
  launchClaude,
20
20
  launchClaudeInNewTerminalWindow
21
21
  };
22
- //# sourceMappingURL=claude-V4HRPR4Z.js.map
22
+ //# sourceMappingURL=claude-TP2QO3BU.js.map
@@ -5,34 +5,39 @@ import {
5
5
  EnvironmentManager,
6
6
  LoomManager,
7
7
  ResourceCleanup
8
- } from "./chunk-GWONJE3X.js";
8
+ } from "./chunk-4ZIHFUPN.js";
9
+ import "./chunk-I3HMNWQQ.js";
9
10
  import {
10
11
  ProcessManager
11
- } from "./chunk-XHNACIHO.js";
12
- import "./chunk-RD7I2Q2F.js";
13
- import "./chunk-52MVUK5V.js";
12
+ } from "./chunk-NTTSUAVM.js";
13
+ import "./chunk-USSL2X4A.js";
14
14
  import {
15
15
  IdentifierParser
16
- } from "./chunk-TVH67KEO.js";
16
+ } from "./chunk-2HZX6AMR.js";
17
17
  import {
18
18
  createNeonProviderFromSettings
19
19
  } from "./chunk-7ZEHSSUP.js";
20
+ import "./chunk-YETJNRQM.js";
21
+ import "./chunk-OK7LUTRW.js";
22
+ import "./chunk-ENGCJIYQ.js";
23
+ import "./chunk-4LKGCFGG.js";
20
24
  import {
21
25
  GitWorktreeManager
22
- } from "./chunk-HSGZW3ID.js";
23
- import "./chunk-XPKN3QWY.js";
24
- import "./chunk-GDS2HXSW.js";
26
+ } from "./chunk-4GAJJUYS.js";
27
+ import "./chunk-FXDYIV3K.js";
28
+ import "./chunk-VOGGLPG5.js";
29
+ import "./chunk-H6ST2TGP.js";
25
30
  import {
26
31
  SettingsManager
27
- } from "./chunk-IWIIOFEB.js";
32
+ } from "./chunk-XFEK2X2D.js";
28
33
  import "./chunk-KBEIQP4G.js";
29
- import "./chunk-PVW6JE7E.js";
30
- import "./chunk-THS5L54H.js";
34
+ import "./chunk-Y5HSSIK2.js";
35
+ import "./chunk-TRUMP4DA.js";
31
36
  import {
32
37
  promptConfirmation
33
38
  } from "./chunk-7JDMYTFZ.js";
34
39
  import "./chunk-433MOLAU.js";
35
- import "./chunk-IGKPPACU.js";
40
+ import "./chunk-FO5GGFOV.js";
36
41
  import {
37
42
  getLogger
38
43
  } from "./chunk-6MLEBAYZ.js";
@@ -78,10 +83,10 @@ var CleanupCommand = class {
78
83
  cliIsolationManager
79
84
  );
80
85
  if (!this.loomManager) {
81
- const { GitHubService } = await import("./GitHubService-ACZVNTJE.js");
82
- const { ClaudeContextManager } = await import("./ClaudeContextManager-RDP6CLK6.js");
83
- const { ProjectCapabilityDetector } = await import("./ProjectCapabilityDetector-IA56AUE6.js");
84
- const { DefaultBranchNamingService } = await import("./BranchNamingService-AO7BPIUJ.js");
86
+ const { GitHubService } = await import("./GitHubService-O7T6CFAJ.js");
87
+ const { ClaudeContextManager } = await import("./ClaudeContextManager-X2Y72GRL.js");
88
+ const { ProjectCapabilityDetector } = await import("./ProjectCapabilityDetector-N5L7T4IY.js");
89
+ const { DefaultBranchNamingService } = await import("./BranchNamingService-K6XNWQ6C.js");
85
90
  this.loomManager = new LoomManager(
86
91
  this.gitWorktreeManager,
87
92
  new GitHubService(),
@@ -285,7 +290,7 @@ var CleanupCommand = class {
285
290
  const { force, dryRun } = parsed.options;
286
291
  let parsedInput = await this.identifierParser.parseForPatternDetection(identifier);
287
292
  if (parsedInput.type === "branch" && parsedInput.branchName) {
288
- const { extractIssueNumber } = await import("./git-GUNOPP4Q.js");
293
+ const { extractIssueNumber } = await import("./git-W3XUIFTR.js");
289
294
  const extractedNumber = extractIssueNumber(parsedInput.branchName);
290
295
  if (extractedNumber !== null) {
291
296
  parsedInput = {
@@ -484,4 +489,4 @@ var CleanupCommand = class {
484
489
  export {
485
490
  CleanupCommand
486
491
  };
487
- //# sourceMappingURL=cleanup-NWNKWPUY.js.map
492
+ //# sourceMappingURL=cleanup-D3CSRBBZ.js.map