@iloom/cli 0.6.1 → 0.7.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 (178) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +35 -18
  3. package/dist/{BranchNamingService-B5PVRR7F.js → BranchNamingService-FLPUUFOB.js} +2 -2
  4. package/dist/ClaudeContextManager-KE5TBZVZ.js +14 -0
  5. package/dist/ClaudeService-CRSETT3A.js +13 -0
  6. package/dist/{GitHubService-S2OGUTDR.js → GitHubService-O7U4UQ7N.js} +3 -3
  7. package/dist/{LoomLauncher-5LFM4LXB.js → LoomLauncher-NL65LSKP.js} +6 -6
  8. package/dist/{MetadataManager-DFI73J3G.js → MetadataManager-XJ2YB762.js} +2 -2
  9. package/dist/PRManager-2ABCWXHW.js +16 -0
  10. package/dist/{ProjectCapabilityDetector-S5FLNCFI.js → ProjectCapabilityDetector-IA56AUE6.js} +3 -3
  11. package/dist/{PromptTemplateManager-C3DK6XZL.js → PromptTemplateManager-7L3HJQQU.js} +2 -2
  12. package/dist/README.md +35 -18
  13. package/dist/{SettingsManager-35F5RUJH.js → SettingsManager-YU4VYPTW.js} +2 -2
  14. package/dist/agents/iloom-framework-detector.md +78 -9
  15. package/dist/agents/iloom-issue-analyze-and-plan.md +42 -17
  16. package/dist/agents/iloom-issue-analyzer.md +14 -14
  17. package/dist/agents/iloom-issue-complexity-evaluator.md +38 -15
  18. package/dist/agents/iloom-issue-enhancer.md +15 -15
  19. package/dist/agents/iloom-issue-implementer.md +44 -15
  20. package/dist/agents/iloom-issue-planner.md +121 -17
  21. package/dist/agents/iloom-issue-reviewer.md +15 -15
  22. package/dist/{build-FJVYP7EV.js → build-HQ5HGA3T.js} +9 -9
  23. package/dist/{chunk-VU3QMIP2.js → chunk-453NC377.js} +91 -15
  24. package/dist/chunk-453NC377.js.map +1 -0
  25. package/dist/{chunk-UQIXZ3BA.js → chunk-5V74K5ZA.js} +2 -2
  26. package/dist/{chunk-7WANFUIK.js → chunk-6TL3BYH6.js} +2 -2
  27. package/dist/{chunk-ZPSTA5PR.js → chunk-7GLZVDPQ.js} +2 -2
  28. package/dist/{chunk-64O2UIWO.js → chunk-AFRICMSW.js} +4 -4
  29. package/dist/{chunk-5TXLVEXT.js → chunk-C3AKFAIR.js} +2 -2
  30. package/dist/{chunk-K7SEEHKO.js → chunk-CNSTXBJ3.js} +7 -419
  31. package/dist/chunk-CNSTXBJ3.js.map +1 -0
  32. package/dist/{chunk-2A7WQKBE.js → chunk-DAOS6EC3.js} +96 -6
  33. package/dist/chunk-DAOS6EC3.js.map +1 -0
  34. package/dist/{chunk-TRQ76ISK.js → chunk-ELJKYFSH.js} +9 -9
  35. package/dist/{chunk-VDA5JMB4.js → chunk-EPPPDVHD.js} +21 -8
  36. package/dist/chunk-EPPPDVHD.js.map +1 -0
  37. package/dist/{chunk-LVBRMTE6.js → chunk-FEAJR6PN.js} +6 -6
  38. package/dist/{chunk-6YSFTPKW.js → chunk-FM4KBPVA.js} +18 -13
  39. package/dist/chunk-FM4KBPVA.js.map +1 -0
  40. package/dist/{chunk-AEIMYF4P.js → chunk-FP7G7DG3.js} +6 -2
  41. package/dist/chunk-FP7G7DG3.js.map +1 -0
  42. package/dist/{chunk-LT3SGBR7.js → chunk-GCPAZSGV.js} +36 -2
  43. package/dist/{chunk-LT3SGBR7.js.map → chunk-GCPAZSGV.js.map} +1 -1
  44. package/dist/chunk-GJMEKEI5.js +517 -0
  45. package/dist/chunk-GJMEKEI5.js.map +1 -0
  46. package/dist/{chunk-7Q66W4OH.js → chunk-HBJITKSZ.js} +37 -1
  47. package/dist/chunk-HBJITKSZ.js.map +1 -0
  48. package/dist/{chunk-7HIRPCKU.js → chunk-HVQNVRAF.js} +2 -2
  49. package/dist/{chunk-6U6VI4SZ.js → chunk-KVS4XGBQ.js} +4 -4
  50. package/dist/{chunk-SN3Z6EZO.js → chunk-N7FVXZNI.js} +2 -2
  51. package/dist/{chunk-I75JMBNB.js → chunk-QQFBMCAH.js} +54 -43
  52. package/dist/chunk-QQFBMCAH.js.map +1 -0
  53. package/dist/{chunk-AXX3QIKK.js → chunk-RD7I2Q2F.js} +2 -2
  54. package/dist/chunk-TIYJEEVO.js +79 -0
  55. package/dist/chunk-TIYJEEVO.js.map +1 -0
  56. package/dist/{chunk-EK3XCAAS.js → chunk-UDRZY65Y.js} +2 -2
  57. package/dist/{chunk-3PT7RKL5.js → chunk-USJSNHGG.js} +2 -2
  58. package/dist/{chunk-CFUWQHCJ.js → chunk-VWGKGNJP.js} +114 -35
  59. package/dist/chunk-VWGKGNJP.js.map +1 -0
  60. package/dist/{chunk-F6WVM437.js → chunk-WFQ5CLTR.js} +6 -3
  61. package/dist/chunk-WFQ5CLTR.js.map +1 -0
  62. package/dist/{chunk-BXCPJJYM.js → chunk-XPKN3QWY.js} +24 -6
  63. package/dist/chunk-XPKN3QWY.js.map +1 -0
  64. package/dist/chunk-YQNSZKKT.js +822 -0
  65. package/dist/chunk-YQNSZKKT.js.map +1 -0
  66. package/dist/{chunk-GEXP5IOF.js → chunk-ZA575VLF.js} +21 -8
  67. package/dist/chunk-ZA575VLF.js.map +1 -0
  68. package/dist/{claude-H33OQMXO.js → claude-6H36IBHO.js} +4 -2
  69. package/dist/{cleanup-BRUAINKE.js → cleanup-77U5ATYI.js} +20 -16
  70. package/dist/cleanup-77U5ATYI.js.map +1 -0
  71. package/dist/cli.js +361 -954
  72. package/dist/cli.js.map +1 -1
  73. package/dist/commit-ONRXU67O.js +237 -0
  74. package/dist/commit-ONRXU67O.js.map +1 -0
  75. package/dist/{compile-ULNO5F7Q.js → compile-CT7IR7O2.js} +9 -9
  76. package/dist/{contribute-Q6GX6AXK.js → contribute-GXKOIA42.js} +5 -5
  77. package/dist/{dev-server-4RCDJ5MU.js → dev-server-UKAPBGUR.js} +22 -74
  78. package/dist/dev-server-UKAPBGUR.js.map +1 -0
  79. package/dist/{feedback-O4Q55SVS.js → feedback-K3A4QUSG.js} +10 -10
  80. package/dist/{git-FVMGBHC2.js → git-ENLT2VNI.js} +6 -4
  81. package/dist/hooks/iloom-hook.js +30 -2
  82. package/dist/{ignite-VHV65WEZ.js → ignite-YUAOJ5PP.js} +20 -20
  83. package/dist/ignite-YUAOJ5PP.js.map +1 -0
  84. package/dist/index.d.ts +71 -27
  85. package/dist/index.js +196 -266
  86. package/dist/index.js.map +1 -1
  87. package/dist/init-XQQMFDM6.js +21 -0
  88. package/dist/{lint-5JMCWE4Y.js → lint-HAVU4U34.js} +9 -9
  89. package/dist/mcp/issue-management-server.js +359 -13
  90. package/dist/mcp/issue-management-server.js.map +1 -1
  91. package/dist/mcp/recap-server.js +13 -4
  92. package/dist/mcp/recap-server.js.map +1 -1
  93. package/dist/{open-WHVUYGPY.js → open-QI63XQ4F.js} +25 -76
  94. package/dist/open-QI63XQ4F.js.map +1 -0
  95. package/dist/{projects-SA76I4TZ.js → projects-TWY4RT2Z.js} +11 -4
  96. package/dist/projects-TWY4RT2Z.js.map +1 -0
  97. package/dist/prompts/init-prompt.txt +119 -51
  98. package/dist/prompts/issue-prompt.txt +132 -63
  99. package/dist/prompts/pr-prompt.txt +3 -3
  100. package/dist/prompts/regular-prompt.txt +16 -18
  101. package/dist/prompts/session-summary-prompt.txt +13 -13
  102. package/dist/{rebase-Y4AS6LQW.js → rebase-QYCRF7JG.js} +53 -8
  103. package/dist/rebase-QYCRF7JG.js.map +1 -0
  104. package/dist/{recap-VOOUXOGP.js → recap-ZKGHZCX6.js} +6 -6
  105. package/dist/{run-NCRK5NPR.js → run-YDVYORT2.js} +25 -76
  106. package/dist/run-YDVYORT2.js.map +1 -0
  107. package/dist/schema/settings.schema.json +14 -3
  108. package/dist/{shell-SBLXVOVJ.js → shell-2NNSIU34.js} +6 -6
  109. package/dist/{summary-CVFAMDOJ.js → summary-G6L3VAKK.js} +11 -10
  110. package/dist/{summary-CVFAMDOJ.js.map → summary-G6L3VAKK.js.map} +1 -1
  111. package/dist/{test-3KIVXI6J.js → test-75WAA6DU.js} +9 -9
  112. package/dist/{test-git-ZB6AGGRW.js → test-git-E2BLXR6M.js} +4 -4
  113. package/dist/{test-prefix-FBGXKMPA.js → test-prefix-A7JGGYAA.js} +4 -4
  114. package/dist/{test-webserver-YVQD42W6.js → test-webserver-NRMGT2HB.js} +29 -8
  115. package/dist/test-webserver-NRMGT2HB.js.map +1 -0
  116. package/package.json +3 -1
  117. package/dist/ClaudeContextManager-6J2EB4QU.js +0 -14
  118. package/dist/ClaudeService-O2PB22GX.js +0 -13
  119. package/dist/PRManager-GB3FOJ2W.js +0 -14
  120. package/dist/chunk-2A7WQKBE.js.map +0 -1
  121. package/dist/chunk-6YSFTPKW.js.map +0 -1
  122. package/dist/chunk-7Q66W4OH.js.map +0 -1
  123. package/dist/chunk-AEIMYF4P.js.map +0 -1
  124. package/dist/chunk-BXCPJJYM.js.map +0 -1
  125. package/dist/chunk-CFUWQHCJ.js.map +0 -1
  126. package/dist/chunk-F6WVM437.js.map +0 -1
  127. package/dist/chunk-GEXP5IOF.js.map +0 -1
  128. package/dist/chunk-I75JMBNB.js.map +0 -1
  129. package/dist/chunk-K7SEEHKO.js.map +0 -1
  130. package/dist/chunk-VDA5JMB4.js.map +0 -1
  131. package/dist/chunk-VU3QMIP2.js.map +0 -1
  132. package/dist/chunk-W6WVRHJ6.js +0 -251
  133. package/dist/chunk-W6WVRHJ6.js.map +0 -1
  134. package/dist/cleanup-BRUAINKE.js.map +0 -1
  135. package/dist/dev-server-4RCDJ5MU.js.map +0 -1
  136. package/dist/ignite-VHV65WEZ.js.map +0 -1
  137. package/dist/init-UTYRHNJJ.js +0 -21
  138. package/dist/open-WHVUYGPY.js.map +0 -1
  139. package/dist/projects-SA76I4TZ.js.map +0 -1
  140. package/dist/rebase-Y4AS6LQW.js.map +0 -1
  141. package/dist/run-NCRK5NPR.js.map +0 -1
  142. package/dist/test-webserver-YVQD42W6.js.map +0 -1
  143. /package/dist/{BranchNamingService-B5PVRR7F.js.map → BranchNamingService-FLPUUFOB.js.map} +0 -0
  144. /package/dist/{ClaudeContextManager-6J2EB4QU.js.map → ClaudeContextManager-KE5TBZVZ.js.map} +0 -0
  145. /package/dist/{ClaudeService-O2PB22GX.js.map → ClaudeService-CRSETT3A.js.map} +0 -0
  146. /package/dist/{GitHubService-S2OGUTDR.js.map → GitHubService-O7U4UQ7N.js.map} +0 -0
  147. /package/dist/{LoomLauncher-5LFM4LXB.js.map → LoomLauncher-NL65LSKP.js.map} +0 -0
  148. /package/dist/{MetadataManager-DFI73J3G.js.map → MetadataManager-XJ2YB762.js.map} +0 -0
  149. /package/dist/{PRManager-GB3FOJ2W.js.map → PRManager-2ABCWXHW.js.map} +0 -0
  150. /package/dist/{ProjectCapabilityDetector-S5FLNCFI.js.map → ProjectCapabilityDetector-IA56AUE6.js.map} +0 -0
  151. /package/dist/{PromptTemplateManager-C3DK6XZL.js.map → PromptTemplateManager-7L3HJQQU.js.map} +0 -0
  152. /package/dist/{SettingsManager-35F5RUJH.js.map → SettingsManager-YU4VYPTW.js.map} +0 -0
  153. /package/dist/{build-FJVYP7EV.js.map → build-HQ5HGA3T.js.map} +0 -0
  154. /package/dist/{chunk-UQIXZ3BA.js.map → chunk-5V74K5ZA.js.map} +0 -0
  155. /package/dist/{chunk-7WANFUIK.js.map → chunk-6TL3BYH6.js.map} +0 -0
  156. /package/dist/{chunk-ZPSTA5PR.js.map → chunk-7GLZVDPQ.js.map} +0 -0
  157. /package/dist/{chunk-64O2UIWO.js.map → chunk-AFRICMSW.js.map} +0 -0
  158. /package/dist/{chunk-5TXLVEXT.js.map → chunk-C3AKFAIR.js.map} +0 -0
  159. /package/dist/{chunk-TRQ76ISK.js.map → chunk-ELJKYFSH.js.map} +0 -0
  160. /package/dist/{chunk-LVBRMTE6.js.map → chunk-FEAJR6PN.js.map} +0 -0
  161. /package/dist/{chunk-7HIRPCKU.js.map → chunk-HVQNVRAF.js.map} +0 -0
  162. /package/dist/{chunk-6U6VI4SZ.js.map → chunk-KVS4XGBQ.js.map} +0 -0
  163. /package/dist/{chunk-SN3Z6EZO.js.map → chunk-N7FVXZNI.js.map} +0 -0
  164. /package/dist/{chunk-AXX3QIKK.js.map → chunk-RD7I2Q2F.js.map} +0 -0
  165. /package/dist/{chunk-EK3XCAAS.js.map → chunk-UDRZY65Y.js.map} +0 -0
  166. /package/dist/{chunk-3PT7RKL5.js.map → chunk-USJSNHGG.js.map} +0 -0
  167. /package/dist/{claude-H33OQMXO.js.map → claude-6H36IBHO.js.map} +0 -0
  168. /package/dist/{compile-ULNO5F7Q.js.map → compile-CT7IR7O2.js.map} +0 -0
  169. /package/dist/{contribute-Q6GX6AXK.js.map → contribute-GXKOIA42.js.map} +0 -0
  170. /package/dist/{feedback-O4Q55SVS.js.map → feedback-K3A4QUSG.js.map} +0 -0
  171. /package/dist/{git-FVMGBHC2.js.map → git-ENLT2VNI.js.map} +0 -0
  172. /package/dist/{init-UTYRHNJJ.js.map → init-XQQMFDM6.js.map} +0 -0
  173. /package/dist/{lint-5JMCWE4Y.js.map → lint-HAVU4U34.js.map} +0 -0
  174. /package/dist/{recap-VOOUXOGP.js.map → recap-ZKGHZCX6.js.map} +0 -0
  175. /package/dist/{shell-SBLXVOVJ.js.map → shell-2NNSIU34.js.map} +0 -0
  176. /package/dist/{test-3KIVXI6J.js.map → test-75WAA6DU.js.map} +0 -0
  177. /package/dist/{test-git-ZB6AGGRW.js.map → test-git-E2BLXR6M.js.map} +0 -0
  178. /package/dist/{test-prefix-FBGXKMPA.js.map → test-prefix-A7JGGYAA.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, or --jq 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\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}"],"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, or --jq 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\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}"],"mappings":"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","names":[]}
@@ -0,0 +1,517 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createLinearChildIssue,
4
+ createLinearComment,
5
+ createLinearIssue,
6
+ fetchLinearIssue,
7
+ fetchLinearIssueComments,
8
+ getLinearComment,
9
+ updateLinearComment
10
+ } from "./chunk-HBJITKSZ.js";
11
+ import {
12
+ addSubIssue,
13
+ createIssue,
14
+ createIssueComment,
15
+ createPRComment,
16
+ executeGhCommand,
17
+ getIssueNodeId,
18
+ updateIssueComment
19
+ } from "./chunk-GCPAZSGV.js";
20
+
21
+ // src/mcp/GitHubIssueManagementProvider.ts
22
+ function normalizeAuthor(author) {
23
+ if (!author) return null;
24
+ return {
25
+ id: author.id ? String(author.id) : author.login,
26
+ displayName: author.login,
27
+ // GitHub uses login as primary identifier
28
+ login: author.login,
29
+ // Preserve original GitHub field
30
+ ...author.avatarUrl && { avatarUrl: author.avatarUrl },
31
+ ...author.url && { url: author.url }
32
+ };
33
+ }
34
+ function extractNumericIdFromUrl(url) {
35
+ const match = url.match(/#issuecomment-(\d+)$/);
36
+ if (!(match == null ? void 0 : match[1])) {
37
+ throw new Error(`Cannot extract comment ID from URL: ${url}`);
38
+ }
39
+ return match[1];
40
+ }
41
+ var GitHubIssueManagementProvider = class {
42
+ constructor() {
43
+ this.providerName = "github";
44
+ this.issuePrefix = "#";
45
+ }
46
+ /**
47
+ * Fetch issue details using gh CLI
48
+ * Normalizes GitHub-specific fields to provider-agnostic format
49
+ */
50
+ async getIssue(input) {
51
+ const { number, includeComments = true, repo } = input;
52
+ const issueNumber = parseInt(number, 10);
53
+ if (isNaN(issueNumber)) {
54
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
55
+ }
56
+ const fields = includeComments ? "body,title,comments,labels,assignees,milestone,author,state,number,url" : "body,title,labels,assignees,milestone,author,state,number,url";
57
+ const args = [
58
+ "issue",
59
+ "view",
60
+ String(issueNumber),
61
+ "--json",
62
+ fields
63
+ ];
64
+ if (repo) {
65
+ args.push("--repo", repo);
66
+ }
67
+ const raw = await executeGhCommand(args);
68
+ const result = {
69
+ // Core fields
70
+ id: String(raw.number),
71
+ title: raw.title,
72
+ body: raw.body,
73
+ state: raw.state,
74
+ url: raw.url,
75
+ provider: "github",
76
+ // Normalized author
77
+ author: normalizeAuthor(raw.author),
78
+ // Optional flexible fields
79
+ ...raw.assignees && {
80
+ assignees: raw.assignees.map((a) => normalizeAuthor(a)).filter((a) => a !== null)
81
+ },
82
+ ...raw.labels && {
83
+ labels: raw.labels
84
+ },
85
+ // GitHub-specific passthrough fields
86
+ ...raw.milestone && {
87
+ milestone: raw.milestone
88
+ }
89
+ };
90
+ if (raw.comments !== void 0) {
91
+ result.comments = raw.comments.map((comment) => ({
92
+ id: extractNumericIdFromUrl(comment.url),
93
+ body: comment.body,
94
+ createdAt: comment.createdAt,
95
+ author: normalizeAuthor(comment.author),
96
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
97
+ }));
98
+ }
99
+ return result;
100
+ }
101
+ /**
102
+ * Fetch a specific comment by ID using gh API
103
+ * Normalizes author to FlexibleAuthor format
104
+ */
105
+ async getComment(input) {
106
+ const { commentId, repo } = input;
107
+ const numericCommentId = parseInt(commentId, 10);
108
+ if (isNaN(numericCommentId)) {
109
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
110
+ }
111
+ const apiPath = repo ? `repos/${repo}/issues/comments/${numericCommentId}` : `repos/:owner/:repo/issues/comments/${numericCommentId}`;
112
+ const raw = await executeGhCommand([
113
+ "api",
114
+ apiPath,
115
+ "--jq",
116
+ "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
117
+ ]);
118
+ return {
119
+ id: String(raw.id),
120
+ body: raw.body,
121
+ author: normalizeAuthor(raw.user),
122
+ created_at: raw.created_at,
123
+ ...raw.updated_at && { updated_at: raw.updated_at },
124
+ // Passthrough GitHub-specific fields
125
+ ...raw.html_url && { html_url: raw.html_url },
126
+ ...raw.reactions && { reactions: raw.reactions }
127
+ };
128
+ }
129
+ /**
130
+ * Create a new comment on an issue or PR
131
+ */
132
+ async createComment(input) {
133
+ const { number, body, type } = input;
134
+ const numericId = parseInt(number, 10);
135
+ if (isNaN(numericId)) {
136
+ throw new Error(`Invalid GitHub ${type} number: ${number}. GitHub IDs must be numeric.`);
137
+ }
138
+ const result = type === "issue" ? await createIssueComment(numericId, body) : await createPRComment(numericId, body);
139
+ return {
140
+ ...result,
141
+ id: String(result.id)
142
+ };
143
+ }
144
+ /**
145
+ * Update an existing comment
146
+ */
147
+ async updateComment(input) {
148
+ const { commentId, body } = input;
149
+ const numericCommentId = parseInt(commentId, 10);
150
+ if (isNaN(numericCommentId)) {
151
+ throw new Error(`Invalid GitHub comment ID: ${commentId}. GitHub comment IDs must be numeric.`);
152
+ }
153
+ const result = await updateIssueComment(numericCommentId, body);
154
+ return {
155
+ ...result,
156
+ id: String(result.id)
157
+ };
158
+ }
159
+ /**
160
+ * Create a new issue
161
+ */
162
+ async createIssue(input) {
163
+ const { title, body, labels, repo } = input;
164
+ const result = await createIssue(title, body, { labels, repo });
165
+ const issueNumber = typeof result.number === "number" ? result.number : parseInt(String(result.number), 10);
166
+ return {
167
+ id: String(issueNumber),
168
+ url: result.url,
169
+ number: issueNumber
170
+ };
171
+ }
172
+ /**
173
+ * Create a child issue linked to a parent issue
174
+ * GitHub requires two-step process: create issue, then link via GraphQL
175
+ */
176
+ async createChildIssue(input) {
177
+ const { parentId, title, body, labels, repo } = input;
178
+ const parentNumber = parseInt(parentId, 10);
179
+ if (isNaN(parentNumber)) {
180
+ throw new Error(`Invalid GitHub parent issue number: ${parentId}. GitHub issue IDs must be numeric.`);
181
+ }
182
+ const parentNodeId = await getIssueNodeId(parentNumber, repo);
183
+ const childResult = await createIssue(title, body, { labels, repo });
184
+ const childNumber = typeof childResult.number === "number" ? childResult.number : parseInt(String(childResult.number), 10);
185
+ const childNodeId = await getIssueNodeId(childNumber, repo);
186
+ await addSubIssue(parentNodeId, childNodeId);
187
+ return {
188
+ id: String(childNumber),
189
+ url: childResult.url,
190
+ number: childNumber
191
+ };
192
+ }
193
+ };
194
+
195
+ // src/utils/linear-markup-converter.ts
196
+ import { appendFileSync } from "fs";
197
+ import { join, dirname, basename, extname } from "path";
198
+ var LinearMarkupConverter = class {
199
+ /**
200
+ * Convert HTML details/summary blocks to Linear's collapsible format
201
+ * Handles nested details blocks recursively
202
+ *
203
+ * @param text - Text containing HTML details/summary blocks
204
+ * @returns Text with details/summary converted to Linear format
205
+ */
206
+ static convertDetailsToLinear(text) {
207
+ if (!text) {
208
+ return text;
209
+ }
210
+ let previousText = "";
211
+ let currentText = text;
212
+ while (previousText !== currentText) {
213
+ previousText = currentText;
214
+ currentText = this.convertSinglePass(currentText);
215
+ }
216
+ return currentText;
217
+ }
218
+ /**
219
+ * Perform a single pass of details block conversion
220
+ * Converts the innermost details blocks first
221
+ */
222
+ static convertSinglePass(text) {
223
+ const detailsRegex = /<details[^>]*>\s*<summary[^>]*>(.*?)<\/summary>\s*(.*?)\s*<\/details>/gis;
224
+ return text.replace(detailsRegex, (_match, summary, content) => {
225
+ const cleanSummary = this.cleanText(summary);
226
+ const cleanContent = this.cleanContent(content);
227
+ if (cleanContent) {
228
+ return `+++ ${cleanSummary}
229
+
230
+ ${cleanContent}
231
+
232
+ +++`;
233
+ } else {
234
+ return `+++ ${cleanSummary}
235
+
236
+ +++`;
237
+ }
238
+ });
239
+ }
240
+ /**
241
+ * Clean text by trimming whitespace and decoding common HTML entities
242
+ */
243
+ static cleanText(text) {
244
+ return text.trim().replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
245
+ }
246
+ /**
247
+ * Clean content while preserving internal structure
248
+ * - Removes leading/trailing whitespace
249
+ * - Normalizes internal blank lines (max 2 consecutive newlines)
250
+ * - Preserves code blocks and other formatting
251
+ */
252
+ static cleanContent(content) {
253
+ if (!content) {
254
+ return "";
255
+ }
256
+ let cleaned = content.trim();
257
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
258
+ return cleaned;
259
+ }
260
+ /**
261
+ * Check if text contains HTML details/summary blocks
262
+ * Useful for conditional conversion
263
+ */
264
+ static hasDetailsBlocks(text) {
265
+ if (!text) {
266
+ return false;
267
+ }
268
+ const detailsRegex = /<details[^>]*>.*?<summary[^>]*>.*?<\/summary>.*?<\/details>/is;
269
+ return detailsRegex.test(text);
270
+ }
271
+ /**
272
+ * Remove wrapper tags from code sample details blocks
273
+ * Identifies details blocks where summary contains "X lines" pattern
274
+ * and removes the details/summary tags while preserving the content
275
+ *
276
+ * @param text - Text containing potential code sample details blocks
277
+ * @returns Text with code sample wrappers removed
278
+ */
279
+ static removeCodeSampleWrappers(text) {
280
+ if (!text) {
281
+ return text;
282
+ }
283
+ const codeSampleRegex = /<details[^>]*>\s*<summary[^>]*>([^<]*\d+\s+lines[^<]*)<\/summary>\s*([\s\S]*?)<\/details>/gi;
284
+ return text.replace(codeSampleRegex, (_match, _summary, content) => {
285
+ return content.trim();
286
+ });
287
+ }
288
+ /**
289
+ * Convert text for Linear - applies all necessary conversions
290
+ * Currently only converts details/summary blocks, but can be extended
291
+ * for other HTML to Linear markdown conversions
292
+ */
293
+ static convertToLinear(text) {
294
+ if (!text) {
295
+ return text;
296
+ }
297
+ this.logConversion("INPUT", text);
298
+ let converted = text;
299
+ converted = this.removeCodeSampleWrappers(converted);
300
+ converted = this.convertDetailsToLinear(converted);
301
+ this.logConversion("OUTPUT", converted);
302
+ return converted;
303
+ }
304
+ /**
305
+ * Log conversion input/output if LINEAR_MARKDOWN_LOG_FILE is set
306
+ */
307
+ static logConversion(label, content) {
308
+ const logFilePath = process.env.LINEAR_MARKDOWN_LOG_FILE;
309
+ if (!logFilePath) {
310
+ return;
311
+ }
312
+ try {
313
+ const timestampedPath = this.getTimestampedLogPath(logFilePath);
314
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
315
+ const separator = "================================";
316
+ const logEntry = `${separator}
317
+ [${timestamp}] CONVERSION ${label}
318
+ ${separator}
319
+ ${label}:
320
+ ${content}
321
+
322
+ `;
323
+ appendFileSync(timestampedPath, logEntry, "utf-8");
324
+ } catch {
325
+ }
326
+ }
327
+ /**
328
+ * Generate timestamped log file path
329
+ * Example: debug.log -> debug-20231202-161234.log
330
+ */
331
+ static getTimestampedLogPath(logFilePath) {
332
+ const dir = dirname(logFilePath);
333
+ const ext = extname(logFilePath);
334
+ const base = basename(logFilePath, ext);
335
+ const now = /* @__PURE__ */ new Date();
336
+ const timestamp = [
337
+ now.getFullYear(),
338
+ String(now.getMonth() + 1).padStart(2, "0"),
339
+ String(now.getDate()).padStart(2, "0")
340
+ ].join("") + "-" + [
341
+ String(now.getHours()).padStart(2, "0"),
342
+ String(now.getMinutes()).padStart(2, "0"),
343
+ String(now.getSeconds()).padStart(2, "0")
344
+ ].join("");
345
+ return join(dir, `${base}-${timestamp}${ext}`);
346
+ }
347
+ };
348
+
349
+ // src/mcp/LinearIssueManagementProvider.ts
350
+ var LinearIssueManagementProvider = class {
351
+ constructor() {
352
+ this.providerName = "linear";
353
+ this.issuePrefix = "";
354
+ /**
355
+ * Cached team key extracted from issue identifiers (e.g., "ENG-123" -> "ENG")
356
+ * Used as fallback when teamKey is not explicitly provided to createIssue()
357
+ */
358
+ this.cachedTeamKey = void 0;
359
+ }
360
+ /**
361
+ * Fetch issue details using Linear SDK
362
+ */
363
+ async getIssue(input) {
364
+ const { number, includeComments = true } = input;
365
+ const match = number.match(/^([A-Z]{2,})-\d+$/i);
366
+ if (match == null ? void 0 : match[1]) {
367
+ this.cachedTeamKey = match[1].toUpperCase();
368
+ }
369
+ const raw = await fetchLinearIssue(number);
370
+ const state = raw.state && (raw.state.toLowerCase().includes("done") || raw.state.toLowerCase().includes("completed") || raw.state.toLowerCase().includes("canceled")) ? "closed" : "open";
371
+ const result = {
372
+ id: raw.identifier,
373
+ title: raw.title,
374
+ body: raw.description ?? "",
375
+ state,
376
+ url: raw.url,
377
+ provider: "linear",
378
+ author: null,
379
+ // Linear SDK doesn't return author in basic fetch
380
+ // Linear-specific fields
381
+ linearState: raw.state,
382
+ createdAt: raw.createdAt,
383
+ updatedAt: raw.updatedAt
384
+ };
385
+ if (includeComments) {
386
+ try {
387
+ const comments = await this.fetchIssueComments(number);
388
+ if (comments) {
389
+ result.comments = comments;
390
+ }
391
+ } catch {
392
+ }
393
+ }
394
+ return result;
395
+ }
396
+ /**
397
+ * Fetch comments for an issue
398
+ */
399
+ async fetchIssueComments(identifier) {
400
+ try {
401
+ const comments = await fetchLinearIssueComments(identifier);
402
+ return comments.map((comment) => ({
403
+ id: comment.id,
404
+ body: comment.body,
405
+ createdAt: comment.createdAt,
406
+ author: null,
407
+ // Linear SDK doesn't return comment author info in basic fetch
408
+ ...comment.updatedAt && { updatedAt: comment.updatedAt }
409
+ }));
410
+ } catch {
411
+ return [];
412
+ }
413
+ }
414
+ /**
415
+ * Fetch a specific comment by ID
416
+ */
417
+ async getComment(input) {
418
+ const { commentId } = input;
419
+ const raw = await getLinearComment(commentId);
420
+ return {
421
+ id: raw.id,
422
+ body: raw.body,
423
+ author: null,
424
+ // Linear SDK doesn't return comment author info in basic fetch
425
+ created_at: raw.createdAt
426
+ };
427
+ }
428
+ /**
429
+ * Create a new comment on an issue
430
+ */
431
+ async createComment(input) {
432
+ const { number, body } = input;
433
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
434
+ const result = await createLinearComment(number, convertedBody);
435
+ return {
436
+ id: result.id,
437
+ url: result.url,
438
+ created_at: result.createdAt
439
+ };
440
+ }
441
+ /**
442
+ * Update an existing comment
443
+ */
444
+ async updateComment(input) {
445
+ const { commentId, body } = input;
446
+ const convertedBody = LinearMarkupConverter.convertToLinear(body);
447
+ const result = await updateLinearComment(commentId, convertedBody);
448
+ return {
449
+ id: result.id,
450
+ url: result.url,
451
+ updated_at: result.updatedAt
452
+ };
453
+ }
454
+ /**
455
+ * Create a new issue
456
+ */
457
+ async createIssue(input) {
458
+ const { title, body, labels, teamKey } = input;
459
+ const effectiveTeamKey = teamKey ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey;
460
+ if (!effectiveTeamKey) {
461
+ throw new Error("teamKey is required for Linear issue creation. Configure issueManagement.linear.teamId in settings, or call getIssue first to extract the team from an issue identifier.");
462
+ }
463
+ const result = await createLinearIssue(title, body, effectiveTeamKey, labels);
464
+ return {
465
+ id: result.identifier,
466
+ url: result.url
467
+ };
468
+ }
469
+ /**
470
+ * Create a child issue linked to a parent issue
471
+ * Linear supports atomic creation with parentId field
472
+ */
473
+ async createChildIssue(input) {
474
+ var _a;
475
+ const { parentId, title, body, labels, teamKey } = input;
476
+ const parentIssue = await fetchLinearIssue(parentId);
477
+ const match = parentId.match(/^([A-Z]{2,})-\d+$/i);
478
+ const effectiveTeamKey = teamKey ?? ((_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.toUpperCase()) ?? process.env.LINEAR_TEAM_KEY ?? this.cachedTeamKey;
479
+ if (!effectiveTeamKey) {
480
+ throw new Error("teamKey is required for Linear child issue creation. Provide teamKey parameter or use a parent identifier with team prefix.");
481
+ }
482
+ const result = await createLinearChildIssue(
483
+ title,
484
+ body,
485
+ effectiveTeamKey,
486
+ parentIssue.id,
487
+ // UUID, not identifier
488
+ labels
489
+ );
490
+ return {
491
+ id: result.identifier,
492
+ url: result.url
493
+ };
494
+ }
495
+ };
496
+
497
+ // src/mcp/IssueManagementProviderFactory.ts
498
+ var IssueManagementProviderFactory = class {
499
+ /**
500
+ * Create an issue management provider based on the provider type
501
+ */
502
+ static create(provider) {
503
+ switch (provider) {
504
+ case "github":
505
+ return new GitHubIssueManagementProvider();
506
+ case "linear":
507
+ return new LinearIssueManagementProvider();
508
+ default:
509
+ throw new Error(`Unsupported issue management provider: ${provider}`);
510
+ }
511
+ }
512
+ };
513
+
514
+ export {
515
+ IssueManagementProviderFactory
516
+ };
517
+ //# sourceMappingURL=chunk-GJMEKEI5.js.map