@iloom/cli 0.7.5 → 0.8.0

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 (171) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +32 -3
  3. package/dist/{ClaudeContextManager-Y2YJC6BU.js → ClaudeContextManager-RDP6CLK6.js} +5 -5
  4. package/dist/{ClaudeService-NDVFQRKC.js → ClaudeService-FKPOQRA4.js} +4 -4
  5. package/dist/GitHubService-ACZVNTJE.js +12 -0
  6. package/dist/{LoomLauncher-U2B3VHPC.js → LoomLauncher-NHZMEVTQ.js} +5 -5
  7. package/dist/{MetadataManager-XJ2YB762.js → MetadataManager-W3C54UYT.js} +2 -2
  8. package/dist/{PRManager-6ZJZRG5Z.js → PRManager-XLTVG6YG.js} +5 -5
  9. package/dist/{PromptTemplateManager-7L3HJQQU.js → PromptTemplateManager-OUYDHOPI.js} +2 -2
  10. package/dist/README.md +32 -3
  11. package/dist/{SettingsManager-YU4VYPTW.js → SettingsManager-VCVLL32H.js} +4 -2
  12. package/dist/{SettingsMigrationManager-KZKDG66H.js → SettingsMigrationManager-LEBMJP3B.js} +3 -3
  13. package/dist/agents/iloom-code-reviewer.md +720 -0
  14. package/dist/agents/iloom-issue-analyze-and-plan.md +1 -1
  15. package/dist/agents/iloom-issue-analyzer.md +1 -1
  16. package/dist/agents/iloom-issue-complexity-evaluator.md +1 -1
  17. package/dist/agents/iloom-issue-enhancer.md +1 -1
  18. package/dist/agents/iloom-issue-implementer.md +1 -1
  19. package/dist/agents/iloom-issue-planner.md +1 -1
  20. package/dist/{build-HQ5HGA3T.js → build-H4DK3DMQ.js} +7 -7
  21. package/dist/{chunk-N7FVXZNI.js → chunk-4BSXZ5YZ.js} +31 -9
  22. package/dist/chunk-4BSXZ5YZ.js.map +1 -0
  23. package/dist/{chunk-VYKKWU36.js → chunk-4KGRPHM6.js} +3 -3
  24. package/dist/{chunk-CFQVOTHO.js → chunk-52MVUK5V.js} +2 -2
  25. package/dist/{chunk-TIYJEEVO.js → chunk-66QOCD5N.js} +1 -1
  26. package/dist/chunk-66QOCD5N.js.map +1 -0
  27. package/dist/chunk-7JDMYTFZ.js +251 -0
  28. package/dist/chunk-7JDMYTFZ.js.map +1 -0
  29. package/dist/{chunk-7LSSNB7Y.js → chunk-7ZEHSSUP.js} +2 -2
  30. package/dist/chunk-A4UQY3M2.js +75 -0
  31. package/dist/chunk-A4UQY3M2.js.map +1 -0
  32. package/dist/{chunk-KSXA2NOJ.js → chunk-AZH27CPV.js} +10 -9
  33. package/dist/chunk-AZH27CPV.js.map +1 -0
  34. package/dist/{chunk-ELJKYFSH.js → chunk-BCQDYAOJ.js} +4 -4
  35. package/dist/{chunk-F2PWIRV4.js → chunk-BYUMEDDD.js} +2 -2
  36. package/dist/{chunk-CAXFWFV6.js → chunk-ECP77QGE.js} +4 -4
  37. package/dist/{chunk-ZA575VLF.js → chunk-GDS2HXSW.js} +4 -4
  38. package/dist/{chunk-UDRZY65Y.js → chunk-HSGZW3ID.js} +2 -2
  39. package/dist/{chunk-WFQ5CLTR.js → chunk-IWIIOFEB.js} +56 -5
  40. package/dist/chunk-IWIIOFEB.js.map +1 -0
  41. package/dist/{chunk-VWGKGNJP.js → chunk-KBEIQP4G.js} +3 -1
  42. package/dist/chunk-KBEIQP4G.js.map +1 -0
  43. package/dist/{chunk-LZBSLO6S.js → chunk-L4CN7YQT.js} +381 -8
  44. package/dist/chunk-L4CN7YQT.js.map +1 -0
  45. package/dist/{chunk-HBJITKSZ.js → chunk-LFVRG6UU.js} +159 -3
  46. package/dist/chunk-LFVRG6UU.js.map +1 -0
  47. package/dist/{chunk-64HCHVJM.js → chunk-PLI3JQWT.js} +2 -2
  48. package/dist/{chunk-USJSNHGG.js → chunk-PVW6JE7E.js} +3 -3
  49. package/dist/{chunk-3K3WY3BN.js → chunk-QJX6ICWY.js} +4 -4
  50. package/dist/{chunk-C7YW5IMS.js → chunk-RODL2HVY.js} +17 -6
  51. package/dist/{chunk-C7YW5IMS.js.map → chunk-RODL2HVY.js.map} +1 -1
  52. package/dist/{chunk-NEPH2O4C.js → chunk-SSASIBDJ.js} +3 -3
  53. package/dist/{chunk-GCPAZSGV.js → chunk-THS5L54H.js} +150 -3
  54. package/dist/chunk-THS5L54H.js.map +1 -0
  55. package/dist/{chunk-5V74K5ZA.js → chunk-TVH67KEO.js} +25 -2
  56. package/dist/chunk-TVH67KEO.js.map +1 -0
  57. package/dist/{chunk-ENMTWE74.js → chunk-VZYSM7N7.js} +2 -2
  58. package/dist/{chunk-77VLG2KP.js → chunk-WNXYC7J4.js} +18 -16
  59. package/dist/chunk-WNXYC7J4.js.map +1 -0
  60. package/dist/{chunk-WZYBHD7P.js → chunk-XHNACIHO.js} +2 -2
  61. package/dist/{chunk-XAMBIVXE.js → chunk-XJHQVOT6.js} +2 -2
  62. package/dist/{chunk-O36JLYNW.js → chunk-XU5A6BWA.js} +4 -7
  63. package/dist/chunk-XU5A6BWA.js.map +1 -0
  64. package/dist/{chunk-TB6475EW.js → chunk-YAVVDZVF.js} +3 -3
  65. package/dist/{cleanup-DB7EFBF3.js → cleanup-25PCP2EM.js} +16 -16
  66. package/dist/cli.js +107 -157
  67. package/dist/cli.js.map +1 -1
  68. package/dist/{commit-NGMDWWAP.js → commit-SS77KUNX.js} +10 -10
  69. package/dist/{compile-CT7IR7O2.js → compile-ZOAODFN2.js} +7 -7
  70. package/dist/{contribute-GXKOIA42.js → contribute-7USRBWRM.js} +6 -6
  71. package/dist/{dev-server-OAP3RZC6.js → dev-server-TYYJM3XA.js} +9 -9
  72. package/dist/{feedback-ZLAX3BVL.js → feedback-HZVLOTQJ.js} +9 -9
  73. package/dist/{git-ENLT2VNI.js → git-GUNOPP4Q.js} +4 -4
  74. package/dist/hooks/iloom-hook.js +75 -3
  75. package/dist/{ignite-HA2OJF6Z.js → ignite-CPXPZ4ZD.js} +85 -25
  76. package/dist/ignite-CPXPZ4ZD.js.map +1 -0
  77. package/dist/index.d.ts +85 -2
  78. package/dist/index.js +133 -73
  79. package/dist/index.js.map +1 -1
  80. package/dist/init-MZBIXQ7V.js +21 -0
  81. package/dist/{lint-HAVU4U34.js → lint-MDVUV3W2.js} +7 -7
  82. package/dist/mcp/issue-management-server.js +832 -7
  83. package/dist/mcp/issue-management-server.js.map +1 -1
  84. package/dist/{neon-helpers-3KBC4A3Y.js → neon-helpers-VVFFTLXE.js} +3 -3
  85. package/dist/{open-IN3LUZXX.js → open-2LPZ7XXW.js} +9 -9
  86. package/dist/plan-N3YDCOIV.js +371 -0
  87. package/dist/plan-N3YDCOIV.js.map +1 -0
  88. package/dist/{projects-CTRTTMSK.js → projects-325GEEGJ.js} +2 -2
  89. package/dist/{prompt-3SAZYRUN.js → prompt-ONNPSNKM.js} +2 -2
  90. package/dist/prompts/init-prompt.txt +57 -1
  91. package/dist/prompts/issue-prompt.txt +51 -3
  92. package/dist/prompts/plan-prompt.txt +435 -0
  93. package/dist/prompts/pr-prompt.txt +38 -0
  94. package/dist/prompts/regular-prompt.txt +53 -3
  95. package/dist/{rebase-RLEVFHWN.js → rebase-7YS3N274.js} +6 -6
  96. package/dist/{recap-ZKGHZCX6.js → recap-GSXFEOD6.js} +6 -6
  97. package/dist/{run-QEIS2EH2.js → run-XPGCMFFO.js} +9 -9
  98. package/dist/schema/settings.schema.json +57 -1
  99. package/dist/{shell-2NNSIU34.js → shell-2SPM3Z5O.js} +6 -6
  100. package/dist/{summary-2KLNHVTN.js → summary-5UWNLAI5.js} +43 -12
  101. package/dist/summary-5UWNLAI5.js.map +1 -0
  102. package/dist/{test-75WAA6DU.js → test-N2725YRI.js} +7 -7
  103. package/dist/{test-git-E2BLXR6M.js → test-git-ZPSPA2TP.js} +4 -4
  104. package/dist/{test-prefix-A7JGGYAA.js → test-prefix-6DLB2BHE.js} +4 -4
  105. package/dist/{test-webserver-J6SMNLU2.js → test-webserver-XLJ2TZFP.js} +6 -6
  106. package/package.json +1 -1
  107. package/dist/GitHubService-O7U4UQ7N.js +0 -12
  108. package/dist/agents/iloom-issue-reviewer.md +0 -139
  109. package/dist/chunk-5V74K5ZA.js.map +0 -1
  110. package/dist/chunk-77VLG2KP.js.map +0 -1
  111. package/dist/chunk-GCPAZSGV.js.map +0 -1
  112. package/dist/chunk-HBJITKSZ.js.map +0 -1
  113. package/dist/chunk-KSXA2NOJ.js.map +0 -1
  114. package/dist/chunk-LZBSLO6S.js.map +0 -1
  115. package/dist/chunk-N7FVXZNI.js.map +0 -1
  116. package/dist/chunk-O36JLYNW.js.map +0 -1
  117. package/dist/chunk-TIYJEEVO.js.map +0 -1
  118. package/dist/chunk-VWGKGNJP.js.map +0 -1
  119. package/dist/chunk-WFQ5CLTR.js.map +0 -1
  120. package/dist/chunk-ZX3GTM7O.js +0 -119
  121. package/dist/chunk-ZX3GTM7O.js.map +0 -1
  122. package/dist/ignite-HA2OJF6Z.js.map +0 -1
  123. package/dist/init-S6IEGRSX.js +0 -21
  124. package/dist/summary-2KLNHVTN.js.map +0 -1
  125. /package/dist/{ClaudeContextManager-Y2YJC6BU.js.map → ClaudeContextManager-RDP6CLK6.js.map} +0 -0
  126. /package/dist/{ClaudeService-NDVFQRKC.js.map → ClaudeService-FKPOQRA4.js.map} +0 -0
  127. /package/dist/{GitHubService-O7U4UQ7N.js.map → GitHubService-ACZVNTJE.js.map} +0 -0
  128. /package/dist/{LoomLauncher-U2B3VHPC.js.map → LoomLauncher-NHZMEVTQ.js.map} +0 -0
  129. /package/dist/{MetadataManager-XJ2YB762.js.map → MetadataManager-W3C54UYT.js.map} +0 -0
  130. /package/dist/{PRManager-6ZJZRG5Z.js.map → PRManager-XLTVG6YG.js.map} +0 -0
  131. /package/dist/{PromptTemplateManager-7L3HJQQU.js.map → PromptTemplateManager-OUYDHOPI.js.map} +0 -0
  132. /package/dist/{SettingsManager-YU4VYPTW.js.map → SettingsManager-VCVLL32H.js.map} +0 -0
  133. /package/dist/{SettingsMigrationManager-KZKDG66H.js.map → SettingsMigrationManager-LEBMJP3B.js.map} +0 -0
  134. /package/dist/{build-HQ5HGA3T.js.map → build-H4DK3DMQ.js.map} +0 -0
  135. /package/dist/{chunk-VYKKWU36.js.map → chunk-4KGRPHM6.js.map} +0 -0
  136. /package/dist/{chunk-CFQVOTHO.js.map → chunk-52MVUK5V.js.map} +0 -0
  137. /package/dist/{chunk-7LSSNB7Y.js.map → chunk-7ZEHSSUP.js.map} +0 -0
  138. /package/dist/{chunk-ELJKYFSH.js.map → chunk-BCQDYAOJ.js.map} +0 -0
  139. /package/dist/{chunk-F2PWIRV4.js.map → chunk-BYUMEDDD.js.map} +0 -0
  140. /package/dist/{chunk-CAXFWFV6.js.map → chunk-ECP77QGE.js.map} +0 -0
  141. /package/dist/{chunk-ZA575VLF.js.map → chunk-GDS2HXSW.js.map} +0 -0
  142. /package/dist/{chunk-UDRZY65Y.js.map → chunk-HSGZW3ID.js.map} +0 -0
  143. /package/dist/{chunk-64HCHVJM.js.map → chunk-PLI3JQWT.js.map} +0 -0
  144. /package/dist/{chunk-USJSNHGG.js.map → chunk-PVW6JE7E.js.map} +0 -0
  145. /package/dist/{chunk-3K3WY3BN.js.map → chunk-QJX6ICWY.js.map} +0 -0
  146. /package/dist/{chunk-NEPH2O4C.js.map → chunk-SSASIBDJ.js.map} +0 -0
  147. /package/dist/{chunk-ENMTWE74.js.map → chunk-VZYSM7N7.js.map} +0 -0
  148. /package/dist/{chunk-WZYBHD7P.js.map → chunk-XHNACIHO.js.map} +0 -0
  149. /package/dist/{chunk-XAMBIVXE.js.map → chunk-XJHQVOT6.js.map} +0 -0
  150. /package/dist/{chunk-TB6475EW.js.map → chunk-YAVVDZVF.js.map} +0 -0
  151. /package/dist/{cleanup-DB7EFBF3.js.map → cleanup-25PCP2EM.js.map} +0 -0
  152. /package/dist/{commit-NGMDWWAP.js.map → commit-SS77KUNX.js.map} +0 -0
  153. /package/dist/{compile-CT7IR7O2.js.map → compile-ZOAODFN2.js.map} +0 -0
  154. /package/dist/{contribute-GXKOIA42.js.map → contribute-7USRBWRM.js.map} +0 -0
  155. /package/dist/{dev-server-OAP3RZC6.js.map → dev-server-TYYJM3XA.js.map} +0 -0
  156. /package/dist/{feedback-ZLAX3BVL.js.map → feedback-HZVLOTQJ.js.map} +0 -0
  157. /package/dist/{git-ENLT2VNI.js.map → git-GUNOPP4Q.js.map} +0 -0
  158. /package/dist/{init-S6IEGRSX.js.map → init-MZBIXQ7V.js.map} +0 -0
  159. /package/dist/{lint-HAVU4U34.js.map → lint-MDVUV3W2.js.map} +0 -0
  160. /package/dist/{neon-helpers-3KBC4A3Y.js.map → neon-helpers-VVFFTLXE.js.map} +0 -0
  161. /package/dist/{open-IN3LUZXX.js.map → open-2LPZ7XXW.js.map} +0 -0
  162. /package/dist/{projects-CTRTTMSK.js.map → projects-325GEEGJ.js.map} +0 -0
  163. /package/dist/{prompt-3SAZYRUN.js.map → prompt-ONNPSNKM.js.map} +0 -0
  164. /package/dist/{rebase-RLEVFHWN.js.map → rebase-7YS3N274.js.map} +0 -0
  165. /package/dist/{recap-ZKGHZCX6.js.map → recap-GSXFEOD6.js.map} +0 -0
  166. /package/dist/{run-QEIS2EH2.js.map → run-XPGCMFFO.js.map} +0 -0
  167. /package/dist/{shell-2NNSIU34.js.map → shell-2SPM3Z5O.js.map} +0 -0
  168. /package/dist/{test-75WAA6DU.js.map → test-N2725YRI.js.map} +0 -0
  169. /package/dist/{test-git-E2BLXR6M.js.map → test-git-ZPSPA2TP.js.map} +0 -0
  170. /package/dist/{test-prefix-A7JGGYAA.js.map → test-prefix-6DLB2BHE.js.map} +0 -0
  171. /package/dist/{test-webserver-J6SMNLU2.js.map → test-webserver-XLJ2TZFP.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/MetadataManager.ts"],"sourcesContent":["import path from 'path'\nimport os from 'os'\nimport fs from 'fs-extra'\nimport { getLogger } from '../utils/logger-context.js'\nimport type { ProjectCapability } from '../types/loom.js'\nimport type { OneShotMode } from '../types/index.js'\n\n/**\n * Schema for metadata JSON file\n * Stored in ~/.config/iloom-ai/looms/\n */\nexport interface MetadataFile {\n description: string\n created_at?: string\n version: number\n // Additional metadata fields (v2)\n branchName?: string\n worktreePath?: string\n issueType?: 'branch' | 'issue' | 'pr'\n issue_numbers?: string[]\n pr_numbers?: string[]\n issueTracker?: string\n colorHex?: string // Stored hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId?: string // Claude Code session ID for resume support\n projectPath?: string // Main worktree path (project root) - enables project identification\n issueUrls?: Record<string, string> // Map of issue ID to URL in the issue tracker\n prUrls?: Record<string, string> // Map of PR number to URL in the issue tracker\n draftPrNumber?: number // Draft PR number if github-draft-pr mode was used\n oneShot?: OneShotMode // One-shot automation mode stored during loom creation\n capabilities?: ProjectCapability[] // Detected project capabilities\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n}\n\n/**\n * Input for writing metadata (all fields except version and created_at)\n * Note: issueTracker is required because every loom should have an associated\n * issue tracker provider (defaults to 'github' via IssueTrackerFactory)\n */\nexport interface WriteMetadataInput {\n description: string\n branchName: string\n worktreePath: string\n issueType: 'branch' | 'issue' | 'pr'\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string\n colorHex: string // Hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId: string // Claude Code session ID for resume support (required for new looms)\n projectPath: string // Main worktree path (project root) - required for new looms\n issueUrls: Record<string, string> // Map of issue ID to URL in the issue tracker\n prUrls: Record<string, string> // Map of PR number to URL in the issue tracker\n draftPrNumber?: number // Draft PR number for github-draft-pr mode\n oneShot?: OneShotMode // One-shot automation mode to persist\n capabilities: ProjectCapability[] // Detected project capabilities (required for new looms)\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n}\n\n/**\n * Result of reading metadata for a worktree\n */\nexport interface LoomMetadata {\n status?: 'active' | 'finished'\n finishedAt?: string | null\n description: string\n created_at: string | null\n branchName: string | null\n worktreePath: string | null\n issueType: 'branch' | 'issue' | 'pr' | null\n issue_numbers: string[]\n pr_numbers: string[]\n issueTracker: string | null\n colorHex: string | null // Hex color (e.g., \"#dcebff\") - robust against palette changes\n sessionId: string | null // Claude Code session ID (null for legacy looms)\n projectPath: string | null // Main worktree path (null for legacy looms)\n issueUrls: Record<string, string> // Map of issue ID to URL ({} for legacy looms)\n prUrls: Record<string, string> // Map of PR number to URL ({} for legacy looms)\n draftPrNumber: number | null // Draft PR number (null if not draft mode)\n oneShot: OneShotMode | null // One-shot mode (null for legacy looms)\n capabilities: ProjectCapability[] // Detected project capabilities (empty for legacy looms)\n parentLoom: {\n type: 'issue' | 'pr' | 'branch'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n } | null\n}\n\n/**\n * MetadataManager: Manage loom metadata persistence\n *\n * Stores loom metadata in ~/.config/iloom-ai/looms/ directory.\n * Each worktree gets a JSON file named by slugifying its absolute path.\n *\n * Per spec section 2.2:\n * - Filename derived from worktree absolute path\n * - Path separators replaced with double underscores\n * - Non-alphanumeric chars (except _ and -) replaced with hyphens\n */\nexport class MetadataManager {\n private readonly loomsDir: string\n private readonly finishedDir: string\n\n constructor() {\n this.loomsDir = path.join(os.homedir(), '.config', 'iloom-ai', 'looms')\n this.finishedDir = path.join(this.loomsDir, 'finished')\n }\n\n /**\n * Convert MetadataFile to LoomMetadata with default values for optional fields\n */\n private toMetadata(data: MetadataFile): LoomMetadata {\n return {\n description: data.description,\n created_at: data.created_at ?? null,\n branchName: data.branchName ?? null,\n worktreePath: data.worktreePath ?? null,\n issueType: data.issueType ?? null,\n issue_numbers: data.issue_numbers ?? [],\n pr_numbers: data.pr_numbers ?? [],\n issueTracker: data.issueTracker ?? null,\n colorHex: data.colorHex ?? null,\n sessionId: data.sessionId ?? null,\n projectPath: data.projectPath ?? null,\n issueUrls: data.issueUrls ?? {},\n prUrls: data.prUrls ?? {},\n draftPrNumber: data.draftPrNumber ?? null,\n oneShot: data.oneShot ?? null,\n capabilities: data.capabilities ?? [],\n parentLoom: data.parentLoom ?? null,\n }\n }\n\n /**\n * Convert worktree path to filename slug per spec section 2.2\n *\n * Algorithm:\n * 1. Trim trailing slashes\n * 2. Replace all path separators (/ or \\) with __ (double underscore)\n * 3. Replace any other non-alphanumeric characters (except _ and -) with -\n * 4. Append .json\n *\n * Example:\n * - Worktree: /Users/jane/dev/repo\n * - Filename: _Users__jane__dev__repo.json\n */\n slugifyPath(worktreePath: string): string {\n // 1. Trim trailing slashes\n let slug = worktreePath.replace(/[/\\\\]+$/, '')\n\n // 2. Replace path separators with triple underscores\n slug = slug.replace(/[/\\\\]/g, '___')\n\n // 3. Replace non-alphanumeric chars (except _ and -) with hyphens\n slug = slug.replace(/[^a-zA-Z0-9_-]/g, '-')\n\n // 4. Append .json\n return `${slug}.json`\n }\n\n /**\n * Get the full path to the metadata file for a worktree\n */\n private getFilePath(worktreePath: string): string {\n const filename = this.slugifyPath(worktreePath)\n return path.join(this.loomsDir, filename)\n }\n\n /**\n * Get the full path to the metadata file for a worktree (public API)\n * Used by other services that need to reference the metadata file location\n * (e.g., MCP servers that need to read loom context)\n */\n getMetadataFilePath(worktreePath: string): string {\n return this.getFilePath(worktreePath)\n }\n\n /**\n * Write metadata for a worktree (spec section 3.1)\n *\n * @param worktreePath - Absolute path to the worktree (used for file naming)\n * @param input - Metadata to write (description plus additional fields)\n */\n async writeMetadata(worktreePath: string, input: WriteMetadataInput): Promise<void> {\n try {\n // 1. Ensure looms directory exists\n await fs.ensureDir(this.loomsDir, { mode: 0o755 })\n\n // 2. Create JSON content\n const content: MetadataFile = {\n description: input.description,\n created_at: new Date().toISOString(),\n version: 1,\n branchName: input.branchName,\n worktreePath: input.worktreePath,\n issueType: input.issueType,\n issue_numbers: input.issue_numbers,\n pr_numbers: input.pr_numbers,\n issueTracker: input.issueTracker,\n colorHex: input.colorHex,\n sessionId: input.sessionId,\n projectPath: input.projectPath,\n issueUrls: input.issueUrls,\n prUrls: input.prUrls,\n capabilities: input.capabilities,\n ...(input.draftPrNumber && { draftPrNumber: input.draftPrNumber }),\n ...(input.oneShot && { oneShot: input.oneShot }),\n ...(input.parentLoom && { parentLoom: input.parentLoom }),\n }\n\n // 3. Write to slugified filename\n const filePath = this.getFilePath(worktreePath)\n await fs.writeFile(filePath, JSON.stringify(content, null, 2), { mode: 0o644 })\n\n getLogger().debug(`Metadata written for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning but don't throw - metadata is supplementary\n getLogger().warn(\n `Failed to write metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * Read metadata for a worktree (spec section 3.2)\n *\n * @param worktreePath - Absolute path to the worktree\n * @returns The metadata object with all fields, or null if not found/invalid\n */\n async readMetadata(worktreePath: string): Promise<LoomMetadata | null> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists\n if (!(await fs.pathExists(filePath))) {\n return null\n }\n\n // Read and parse JSON\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n if (!data.description) {\n return null\n }\n\n return this.toMetadata(data)\n } catch (error) {\n // Return null on any error (graceful degradation per spec)\n getLogger().debug(\n `Could not read metadata for worktree ${worktreePath}: ${error instanceof Error ? error.message : String(error)}`\n )\n return null\n }\n }\n\n /**\n * List all stored loom metadata files\n *\n * Returns an array of LoomMetadata objects for all valid metadata files\n * in the looms directory. Invalid or unreadable files are skipped.\n *\n * @returns Array of LoomMetadata objects from all stored files\n */\n async listAllMetadata(): Promise<LoomMetadata[]> {\n const results: LoomMetadata[] = []\n\n try {\n // Check if looms directory exists\n if (!(await fs.pathExists(this.loomsDir))) {\n return results\n }\n\n // Read all files in looms directory\n const files = await fs.readdir(this.loomsDir)\n\n // Filter to only .json files and read each\n for (const file of files) {\n if (!file.endsWith('.json')) {\n continue\n }\n\n try {\n const filePath = path.join(this.loomsDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Skip files without required description field\n if (!data.description) {\n continue\n }\n\n results.push(this.toMetadata(data))\n } catch (error) {\n // Skip individual files that fail to parse (graceful degradation)\n getLogger().debug(\n `Skipping metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n } catch (error) {\n // Log error but return empty array (graceful degradation)\n getLogger().debug(\n `Could not list metadata files: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n\n return results\n }\n\n /**\n * Delete metadata for a worktree (spec section 3.3)\n *\n * Idempotent: silently succeeds if file doesn't exist\n * Non-fatal: logs warning on permission errors but doesn't throw\n *\n * @param worktreePath - Absolute path to the worktree\n */\n async deleteMetadata(worktreePath: string): Promise<void> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists - silently return if not\n if (!(await fs.pathExists(filePath))) {\n getLogger().debug(`No metadata file to delete for worktree: ${worktreePath}`)\n return\n }\n\n // Delete the file\n await fs.unlink(filePath)\n getLogger().debug(`Metadata deleted for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning on permission error but don't throw (per spec section 3.3)\n getLogger().warn(\n `Failed to delete metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * Archive metadata for a finished worktree\n *\n * Moves the metadata file to the finished/ subdirectory and adds\n * status: 'finished' and finishedAt timestamp fields.\n *\n * Idempotent: silently succeeds if source file doesn't exist\n * Non-fatal: logs warning on errors but doesn't throw\n *\n * @param worktreePath - Absolute path to the worktree\n */\n async archiveMetadata(worktreePath: string): Promise<void> {\n try {\n const filename = this.slugifyPath(worktreePath)\n const sourcePath = path.join(this.loomsDir, filename)\n\n // Check if source file exists - silently return if not (idempotent)\n if (!(await fs.pathExists(sourcePath))) {\n getLogger().debug(`No metadata file to archive for worktree: ${worktreePath}`)\n return\n }\n\n // Read existing metadata\n const content = await fs.readFile(sourcePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Add finished status and timestamp\n const finishedData = {\n ...data,\n status: 'finished' as const,\n finishedAt: new Date().toISOString(),\n }\n\n // Ensure finished directory exists\n await fs.ensureDir(this.finishedDir, { mode: 0o755 })\n\n // Write to finished subdirectory\n const destPath = path.join(this.finishedDir, filename)\n await fs.writeFile(destPath, JSON.stringify(finishedData, null, 2), { mode: 0o644 })\n\n // Delete original file\n await fs.unlink(sourcePath)\n\n getLogger().debug(`Metadata archived for worktree: ${worktreePath}`)\n } catch (error) {\n // Log warning but don't throw - archiving is supplementary\n getLogger().warn(\n `Failed to archive metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n /**\n * List all finished loom metadata files\n *\n * Returns an array of LoomMetadata objects for all finished looms\n * in the finished/ subdirectory, sorted by finishedAt in descending order\n * (most recently finished first).\n *\n * @returns Array of LoomMetadata objects from finished files, sorted by finishedAt desc\n */\n async listFinishedMetadata(): Promise<LoomMetadata[]> {\n const results: LoomMetadata[] = []\n\n try {\n // Check if finished directory exists\n if (!(await fs.pathExists(this.finishedDir))) {\n return results\n }\n\n // Read all files in finished directory\n const files = await fs.readdir(this.finishedDir)\n\n // Filter to only .json files and read each\n for (const file of files) {\n if (!file.endsWith('.json')) {\n continue\n }\n\n try {\n const filePath = path.join(this.finishedDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const data = JSON.parse(content) as MetadataFile & { status?: string; finishedAt?: string }\n\n // Skip files without required description field\n if (!data.description) {\n continue\n }\n\n const metadata = this.toMetadata(data)\n // Add finished-specific fields\n metadata.status = (data.status as 'active' | 'finished') ?? 'finished'\n metadata.finishedAt = data.finishedAt ?? null\n\n results.push(metadata)\n } catch (error) {\n // Skip individual files that fail to parse (graceful degradation)\n getLogger().warn(\n `Skipping finished metadata file ${file}: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n }\n\n // Sort by finishedAt descending (most recently finished first)\n results.sort((a, b) => {\n const aTime = a.finishedAt ? new Date(a.finishedAt).getTime() : 0\n const bTime = b.finishedAt ? new Date(b.finishedAt).getTime() : 0\n return bTime - aTime\n })\n } catch (error) {\n // Log error but return empty array (graceful degradation)\n getLogger().warn(\n `Could not list finished metadata files: ${error instanceof Error ? error.message : String(error)}`\n )\n }\n\n return results\n }\n}\n"],"mappings":"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","names":[]}
@@ -3,20 +3,271 @@ import {
3
3
  createLinearChildIssue,
4
4
  createLinearComment,
5
5
  createLinearIssue,
6
+ createLinearIssueRelation,
7
+ deleteLinearIssueRelation,
6
8
  fetchLinearIssue,
7
9
  fetchLinearIssueComments,
10
+ findLinearIssueRelation,
11
+ getLinearChildIssues,
8
12
  getLinearComment,
13
+ getLinearIssueDependencies,
9
14
  updateLinearComment
10
- } from "./chunk-HBJITKSZ.js";
15
+ } from "./chunk-LFVRG6UU.js";
11
16
  import {
12
17
  addSubIssue,
13
18
  createIssue,
14
19
  createIssueComment,
20
+ createIssueDependency,
15
21
  createPRComment,
16
22
  executeGhCommand,
23
+ getIssueDatabaseId,
24
+ getIssueDependencies,
17
25
  getIssueNodeId,
26
+ getSubIssues,
27
+ removeIssueDependency,
18
28
  updateIssueComment
19
- } from "./chunk-GCPAZSGV.js";
29
+ } from "./chunk-THS5L54H.js";
30
+ import {
31
+ logger
32
+ } from "./chunk-VT4PDUYT.js";
33
+
34
+ // src/utils/image-processor.ts
35
+ import { tmpdir } from "os";
36
+ import { join, extname } from "path";
37
+ import { existsSync, mkdirSync, createWriteStream, unlinkSync } from "fs";
38
+ import { pipeline } from "stream/promises";
39
+ import { Readable } from "stream";
40
+ import { createHash } from "crypto";
41
+ import { execa } from "execa";
42
+ var SUPPORTED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
43
+ var MAX_IMAGE_SIZE = 10 * 1024 * 1024;
44
+ var REQUEST_TIMEOUT_MS = 3e4;
45
+ var CACHE_DIR = join(tmpdir(), "iloom-images");
46
+ var cachedGitHubToken;
47
+ function extractMarkdownImageUrls(content) {
48
+ if (!content) {
49
+ return [];
50
+ }
51
+ const matches = [];
52
+ const markdownRegex = /!\[([^\]]*)\]\(((?:[^()\s]|\((?:[^()\s]|\([^()]*\))*\))+)\)/g;
53
+ let match;
54
+ while ((match = markdownRegex.exec(content)) !== null) {
55
+ const url = match[2];
56
+ if (url) {
57
+ matches.push({
58
+ fullMatch: match[0],
59
+ url,
60
+ isMarkdown: true
61
+ });
62
+ }
63
+ }
64
+ const htmlImgRegex = /<img\s+[^>]*src=["']([^"']+)["'][^>]*\/?>/gi;
65
+ while ((match = htmlImgRegex.exec(content)) !== null) {
66
+ const url = match[1];
67
+ if (url) {
68
+ matches.push({
69
+ fullMatch: match[0],
70
+ url,
71
+ isMarkdown: false
72
+ });
73
+ }
74
+ }
75
+ return matches;
76
+ }
77
+ function isAuthenticatedImageUrl(url) {
78
+ try {
79
+ const parsedUrl = new URL(url);
80
+ const hostname = parsedUrl.hostname.toLowerCase();
81
+ if (hostname === "uploads.linear.app") {
82
+ return true;
83
+ }
84
+ if (hostname === "private-user-images.githubusercontent.com") {
85
+ return true;
86
+ }
87
+ if (hostname === "github.com" && parsedUrl.pathname.startsWith("/user-attachments/assets/")) {
88
+ return true;
89
+ }
90
+ return false;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function getExtensionFromUrl(url) {
96
+ try {
97
+ const parsedUrl = new URL(url);
98
+ const pathname = parsedUrl.pathname;
99
+ const ext = extname(pathname).toLowerCase();
100
+ if (SUPPORTED_EXTENSIONS.includes(ext)) {
101
+ return ext;
102
+ }
103
+ return null;
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ function getCacheKey(url) {
109
+ const parsedUrl = new URL(url);
110
+ if (parsedUrl.hostname === "private-user-images.githubusercontent.com") {
111
+ parsedUrl.searchParams.delete("jwt");
112
+ }
113
+ const stableUrl = parsedUrl.toString();
114
+ const hash = createHash("sha256").update(stableUrl).digest("hex").slice(0, 16);
115
+ const ext = getExtensionFromUrl(url) ?? ".png";
116
+ return `${hash}${ext}`;
117
+ }
118
+ function getCachedImagePath(url) {
119
+ const cacheKey = getCacheKey(url);
120
+ const cachedPath = join(CACHE_DIR, cacheKey);
121
+ if (existsSync(cachedPath)) {
122
+ return cachedPath;
123
+ }
124
+ return void 0;
125
+ }
126
+ async function getAuthToken(provider) {
127
+ if (provider === "github") {
128
+ if (cachedGitHubToken !== void 0) {
129
+ return cachedGitHubToken;
130
+ }
131
+ try {
132
+ const result = await execa("gh", ["auth", "token"]);
133
+ cachedGitHubToken = result.stdout.trim();
134
+ return cachedGitHubToken;
135
+ } catch (error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ logger.warn(`Failed to get GitHub auth token via gh CLI: ${message}`);
138
+ return void 0;
139
+ }
140
+ }
141
+ if (provider === "linear") {
142
+ return process.env.LINEAR_API_TOKEN;
143
+ }
144
+ return void 0;
145
+ }
146
+ async function downloadAndSaveImage(url, destPath, authHeader) {
147
+ const headers = {};
148
+ if (authHeader) {
149
+ headers["Authorization"] = authHeader;
150
+ }
151
+ const controller = new AbortController();
152
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
153
+ try {
154
+ const response = await fetch(url, { headers, signal: controller.signal });
155
+ if (!response.ok) {
156
+ throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
157
+ }
158
+ const contentLength = response.headers.get("Content-Length");
159
+ if (contentLength && parseInt(contentLength, 10) > MAX_IMAGE_SIZE) {
160
+ throw new Error(`Image too large: ${contentLength} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`);
161
+ }
162
+ if (!response.body) {
163
+ throw new Error("Response body is null");
164
+ }
165
+ const reader = response.body.getReader();
166
+ let bytesWritten = 0;
167
+ const nodeReadable = new Readable({
168
+ async read() {
169
+ try {
170
+ const { done, value } = await reader.read();
171
+ if (done) {
172
+ this.push(null);
173
+ return;
174
+ }
175
+ bytesWritten += value.byteLength;
176
+ if (bytesWritten > MAX_IMAGE_SIZE) {
177
+ reader.cancel();
178
+ this.destroy(new Error(`Image too large: ${bytesWritten} bytes exceeds ${MAX_IMAGE_SIZE} byte limit`));
179
+ return;
180
+ }
181
+ this.push(Buffer.from(value));
182
+ } catch (err) {
183
+ this.destroy(err instanceof Error ? err : new Error(String(err)));
184
+ }
185
+ }
186
+ });
187
+ if (!existsSync(CACHE_DIR)) {
188
+ mkdirSync(CACHE_DIR, { recursive: true });
189
+ }
190
+ const writeStream = createWriteStream(destPath);
191
+ try {
192
+ await pipeline(nodeReadable, writeStream);
193
+ } catch (pipelineError) {
194
+ try {
195
+ if (existsSync(destPath)) {
196
+ unlinkSync(destPath);
197
+ }
198
+ } catch {
199
+ }
200
+ throw pipelineError;
201
+ }
202
+ } catch (error) {
203
+ if (error instanceof Error && error.name === "AbortError") {
204
+ throw new Error(`Image download timed out after ${REQUEST_TIMEOUT_MS}ms`);
205
+ }
206
+ throw error;
207
+ } finally {
208
+ clearTimeout(timeoutId);
209
+ }
210
+ }
211
+ function getCacheDestPath(url) {
212
+ if (!existsSync(CACHE_DIR)) {
213
+ mkdirSync(CACHE_DIR, { recursive: true });
214
+ }
215
+ const cacheKey = getCacheKey(url);
216
+ return join(CACHE_DIR, cacheKey);
217
+ }
218
+ function rewriteMarkdownUrls(content, urlMap) {
219
+ let result = content;
220
+ for (const [originalUrl, localPath] of urlMap) {
221
+ const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
222
+ const urlRegex = new RegExp(escapedUrl, "g");
223
+ result = result.replace(urlRegex, localPath);
224
+ }
225
+ return result;
226
+ }
227
+ async function processMarkdownImages(content, provider) {
228
+ if (!content) {
229
+ return "";
230
+ }
231
+ const images = extractMarkdownImageUrls(content);
232
+ if (images.length === 0) {
233
+ return content;
234
+ }
235
+ const authImages = images.filter((img) => isAuthenticatedImageUrl(img.url));
236
+ if (authImages.length === 0) {
237
+ return content;
238
+ }
239
+ const authToken = await getAuthToken(provider);
240
+ const uniqueUrls = [...new Set(authImages.map((img) => img.url))];
241
+ const urlMap = /* @__PURE__ */ new Map();
242
+ const downloadPromises = uniqueUrls.map(async (url) => {
243
+ try {
244
+ const cachedPath = getCachedImagePath(url);
245
+ if (cachedPath) {
246
+ logger.debug(`Using cached image: ${cachedPath}`);
247
+ return { url, localPath: cachedPath };
248
+ }
249
+ logger.debug(`Downloading image: ${url}`);
250
+ const destPath = getCacheDestPath(url);
251
+ await downloadAndSaveImage(
252
+ url,
253
+ destPath,
254
+ authToken ? `Bearer ${authToken}` : void 0
255
+ );
256
+ return { url, localPath: destPath };
257
+ } catch (error) {
258
+ const message = error instanceof Error ? error.message : String(error);
259
+ logger.warn(`Failed to download image ${url}: ${message}`);
260
+ return null;
261
+ }
262
+ });
263
+ const results = await Promise.all(downloadPromises);
264
+ for (const result of results) {
265
+ if (result !== null) {
266
+ urlMap.set(result.url, result.localPath);
267
+ }
268
+ }
269
+ return rewriteMarkdownUrls(content, urlMap);
270
+ }
20
271
 
21
272
  // src/mcp/GitHubIssueManagementProvider.ts
22
273
  function normalizeAuthor(author) {
@@ -96,6 +347,12 @@ var GitHubIssueManagementProvider = class {
96
347
  ...comment.updatedAt && { updatedAt: comment.updatedAt }
97
348
  }));
98
349
  }
350
+ result.body = await processMarkdownImages(result.body, "github");
351
+ if (result.comments) {
352
+ for (const comment of result.comments) {
353
+ comment.body = await processMarkdownImages(comment.body, "github");
354
+ }
355
+ }
99
356
  return result;
100
357
  }
101
358
  /**
@@ -164,6 +421,12 @@ var GitHubIssueManagementProvider = class {
164
421
  ...comment.updatedAt && { updatedAt: comment.updatedAt }
165
422
  }));
166
423
  }
424
+ result.body = await processMarkdownImages(result.body, "github");
425
+ if (result.comments) {
426
+ for (const comment of result.comments) {
427
+ comment.body = await processMarkdownImages(comment.body, "github");
428
+ }
429
+ }
167
430
  return result;
168
431
  }
169
432
  /**
@@ -183,9 +446,10 @@ var GitHubIssueManagementProvider = class {
183
446
  "--jq",
184
447
  "{id: .id, body: .body, user: .user, created_at: .created_at, updated_at: .updated_at, html_url: .html_url, reactions: .reactions}"
185
448
  ]);
449
+ const processedBody = await processMarkdownImages(raw.body, "github");
186
450
  return {
187
451
  id: String(raw.id),
188
- body: raw.body,
452
+ body: processedBody,
189
453
  author: normalizeAuthor(raw.user),
190
454
  created_at: raw.created_at,
191
455
  ...raw.updated_at && { updated_at: raw.updated_at },
@@ -258,11 +522,77 @@ var GitHubIssueManagementProvider = class {
258
522
  number: childNumber
259
523
  };
260
524
  }
525
+ /**
526
+ * Create a blocking dependency between two issues (A blocks B)
527
+ * Uses GitHub's sub-issues API: blocking issue becomes parent, blocked issue becomes sub-issue
528
+ */
529
+ async createDependency(input) {
530
+ const { blockingIssue, blockedIssue, repo } = input;
531
+ const blockingNumber = parseInt(blockingIssue, 10);
532
+ if (isNaN(blockingNumber)) {
533
+ throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
534
+ }
535
+ const blockedNumber = parseInt(blockedIssue, 10);
536
+ if (isNaN(blockedNumber)) {
537
+ throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
538
+ }
539
+ const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
540
+ await createIssueDependency(blockedNumber, blockingDatabaseId, repo);
541
+ }
542
+ /**
543
+ * Get dependencies for an issue
544
+ */
545
+ async getDependencies(input) {
546
+ const { number, direction, repo } = input;
547
+ const issueNumber = parseInt(number, 10);
548
+ if (isNaN(issueNumber)) {
549
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
550
+ }
551
+ const result = {
552
+ blocking: [],
553
+ blockedBy: []
554
+ };
555
+ if (direction === "blocking" || direction === "both") {
556
+ result.blocking = await getIssueDependencies(issueNumber, "blocking", repo);
557
+ }
558
+ if (direction === "blocked_by" || direction === "both") {
559
+ result.blockedBy = await getIssueDependencies(issueNumber, "blocked_by", repo);
560
+ }
561
+ return result;
562
+ }
563
+ /**
564
+ * Remove a blocking dependency between two issues (A blocks B)
565
+ * Uses GitHub's sub-issues API: blocking issue is parent, blocked issue is sub-issue
566
+ */
567
+ async removeDependency(input) {
568
+ const { blockingIssue, blockedIssue, repo } = input;
569
+ const blockingNumber = parseInt(blockingIssue, 10);
570
+ if (isNaN(blockingNumber)) {
571
+ throw new Error(`Invalid GitHub issue number: ${blockingIssue}. GitHub issue IDs must be numeric.`);
572
+ }
573
+ const blockedNumber = parseInt(blockedIssue, 10);
574
+ if (isNaN(blockedNumber)) {
575
+ throw new Error(`Invalid GitHub issue number: ${blockedIssue}. GitHub issue IDs must be numeric.`);
576
+ }
577
+ const blockingDatabaseId = await getIssueDatabaseId(blockingNumber, repo);
578
+ await removeIssueDependency(blockedNumber, blockingDatabaseId, repo);
579
+ }
580
+ /**
581
+ * Get child issues (sub-issues) of a parent issue
582
+ */
583
+ async getChildIssues(input) {
584
+ const { number, repo } = input;
585
+ const issueNumber = parseInt(number, 10);
586
+ if (isNaN(issueNumber)) {
587
+ throw new Error(`Invalid GitHub issue number: ${number}. GitHub issue IDs must be numeric.`);
588
+ }
589
+ return await getSubIssues(issueNumber, repo);
590
+ }
261
591
  };
262
592
 
263
593
  // src/utils/linear-markup-converter.ts
264
594
  import { appendFileSync } from "fs";
265
- import { join, dirname, basename, extname } from "path";
595
+ import { join as join2, dirname, basename, extname as extname2 } from "path";
266
596
  var LinearMarkupConverter = class {
267
597
  /**
268
598
  * Convert HTML details/summary blocks to Linear's collapsible format
@@ -398,7 +728,7 @@ ${content}
398
728
  */
399
729
  static getTimestampedLogPath(logFilePath) {
400
730
  const dir = dirname(logFilePath);
401
- const ext = extname(logFilePath);
731
+ const ext = extname2(logFilePath);
402
732
  const base = basename(logFilePath, ext);
403
733
  const now = /* @__PURE__ */ new Date();
404
734
  const timestamp = [
@@ -410,7 +740,7 @@ ${content}
410
740
  String(now.getMinutes()).padStart(2, "0"),
411
741
  String(now.getSeconds()).padStart(2, "0")
412
742
  ].join("");
413
- return join(dir, `${base}-${timestamp}${ext}`);
743
+ return join2(dir, `${base}-${timestamp}${ext}`);
414
744
  }
415
745
  };
416
746
 
@@ -459,6 +789,12 @@ var LinearIssueManagementProvider = class {
459
789
  } catch {
460
790
  }
461
791
  }
792
+ result.body = await processMarkdownImages(result.body, "linear");
793
+ if (result.comments) {
794
+ for (const comment of result.comments) {
795
+ comment.body = await processMarkdownImages(comment.body, "linear");
796
+ }
797
+ }
462
798
  return result;
463
799
  }
464
800
  /**
@@ -492,9 +828,10 @@ var LinearIssueManagementProvider = class {
492
828
  async getComment(input) {
493
829
  const { commentId } = input;
494
830
  const raw = await getLinearComment(commentId);
831
+ const processedBody = await processMarkdownImages(raw.body, "linear");
495
832
  return {
496
833
  id: raw.id,
497
- body: raw.body,
834
+ body: processedBody,
498
835
  author: null,
499
836
  // Linear SDK doesn't return comment author info in basic fetch
500
837
  created_at: raw.createdAt
@@ -567,6 +904,42 @@ var LinearIssueManagementProvider = class {
567
904
  url: result.url
568
905
  };
569
906
  }
907
+ /**
908
+ * Create a blocking dependency between two issues
909
+ */
910
+ async createDependency(input) {
911
+ const { blockingIssue, blockedIssue } = input;
912
+ const [blockingIssueData, blockedIssueData] = await Promise.all([
913
+ fetchLinearIssue(blockingIssue),
914
+ fetchLinearIssue(blockedIssue)
915
+ ]);
916
+ await createLinearIssueRelation(blockingIssueData.id, blockedIssueData.id);
917
+ }
918
+ /**
919
+ * Get dependencies for an issue
920
+ */
921
+ async getDependencies(input) {
922
+ const { number, direction } = input;
923
+ return await getLinearIssueDependencies(number, direction);
924
+ }
925
+ /**
926
+ * Remove a blocking dependency between two issues
927
+ */
928
+ async removeDependency(input) {
929
+ const { blockingIssue, blockedIssue } = input;
930
+ const relationId = await findLinearIssueRelation(blockingIssue, blockedIssue);
931
+ if (!relationId) {
932
+ throw new Error(`No blocking dependency found from ${blockingIssue} to ${blockedIssue}`);
933
+ }
934
+ await deleteLinearIssueRelation(relationId);
935
+ }
936
+ /**
937
+ * Get child issues of a parent issue
938
+ */
939
+ async getChildIssues(input) {
940
+ const { number } = input;
941
+ return await getLinearChildIssues(number);
942
+ }
570
943
  };
571
944
 
572
945
  // src/mcp/IssueManagementProviderFactory.ts
@@ -589,4 +962,4 @@ var IssueManagementProviderFactory = class {
589
962
  export {
590
963
  IssueManagementProviderFactory
591
964
  };
592
- //# sourceMappingURL=chunk-LZBSLO6S.js.map
965
+ //# sourceMappingURL=chunk-L4CN7YQT.js.map