@iloom/cli 0.9.2 → 0.10.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.
- package/LICENSE +1 -1
- package/README.md +160 -41
- package/dist/{BranchNamingService-K6XNWQ6C.js → BranchNamingService-25KSZAEM.js} +2 -2
- package/dist/ClaudeContextManager-66GR4BGM.js +14 -0
- package/dist/ClaudeService-7KM5NA5Z.js +13 -0
- package/dist/{GitHubService-TGWJN4V4.js → GitHubService-MEHKHUQP.js} +4 -4
- package/dist/IssueTrackerFactory-NG53YX5S.js +14 -0
- package/dist/{LoomLauncher-73NXL2CL.js → LoomLauncher-TDLZSYG2.js} +9 -9
- package/dist/{MetadataManager-W3C54UYT.js → MetadataManager-5QZSTKNN.js} +2 -2
- package/dist/{ProjectCapabilityDetector-N5L7T4IY.js → ProjectCapabilityDetector-5KSYUTBJ.js} +3 -3
- package/dist/{PromptTemplateManager-36YLQRHP.js → PromptTemplateManager-YOE2SIPG.js} +2 -2
- package/dist/README.md +160 -41
- package/dist/{SettingsManager-AW3JTJHD.js → SettingsManager-FNKCOZMQ.js} +4 -2
- package/dist/agents/iloom-artifact-reviewer.md +11 -0
- package/dist/agents/iloom-code-reviewer.md +14 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +55 -12
- package/dist/agents/iloom-issue-analyzer.md +49 -6
- package/dist/agents/iloom-issue-complexity-evaluator.md +47 -6
- package/dist/agents/iloom-issue-enhancer.md +86 -7
- package/dist/agents/iloom-issue-implementer.md +48 -7
- package/dist/agents/iloom-issue-planner.md +115 -62
- package/dist/{build-THZI572G.js → build-VHGEMXBA.js} +9 -9
- package/dist/chunk-4232AHNQ.js +35 -0
- package/dist/chunk-4232AHNQ.js.map +1 -0
- package/dist/chunk-4E7LCFUG.js +24 -0
- package/dist/chunk-4E7LCFUG.js.map +1 -0
- package/dist/{chunk-AR5QKYNE.js → chunk-4FGEGQW4.js} +4 -4
- package/dist/{chunk-R4YWBGY6.js → chunk-5FJWO4IT.js} +67 -22
- package/dist/chunk-5FJWO4IT.js.map +1 -0
- package/dist/{chunk-VPTAX5TR.js → chunk-5RPBYK5Q.js} +35 -30
- package/dist/chunk-5RPBYK5Q.js.map +1 -0
- package/dist/{chunk-YKFCCV6S.js → chunk-63QWFWH3.js} +7 -7
- package/dist/chunk-63QWFWH3.js.map +1 -0
- package/dist/{chunk-RI2YL6TK.js → chunk-7VHJNVLF.js} +80 -23
- package/dist/chunk-7VHJNVLF.js.map +1 -0
- package/dist/{chunk-B7U6OKUR.js → chunk-C6HNNJIV.js} +11 -3
- package/dist/chunk-C6HNNJIV.js.map +1 -0
- package/dist/{chunk-A7NJF73J.js → chunk-CVCTIDDK.js} +4 -4
- package/dist/{chunk-Z2TWEXR7.js → chunk-E6KOWMKA.js} +6 -6
- package/dist/chunk-E6KOWMKA.js.map +1 -0
- package/dist/{chunk-3I4ONZRT.js → chunk-EVPZFV3K.js} +10 -10
- package/dist/chunk-EVPZFV3K.js.map +1 -0
- package/dist/{chunk-IZIYLYPK.js → chunk-G5V75JD5.js} +2 -2
- package/dist/chunk-GRISNU6G.js +651 -0
- package/dist/chunk-GRISNU6G.js.map +1 -0
- package/dist/chunk-HEXKPKCK.js +1396 -0
- package/dist/chunk-HEXKPKCK.js.map +1 -0
- package/dist/{chunk-TC7APDKU.js → chunk-I5T677EA.js} +2 -2
- package/dist/{chunk-KBEIQP4G.js → chunk-KB64WNBZ.js} +43 -3
- package/dist/chunk-KB64WNBZ.js.map +1 -0
- package/dist/{chunk-NWMORW3U.js → chunk-KIK2ZFAL.js} +2 -2
- package/dist/{chunk-CWRI4JC3.js → chunk-KKV5WH5M.js} +30 -31
- package/dist/chunk-KKV5WH5M.js.map +1 -0
- package/dist/{chunk-DGG2VY7B.js → chunk-KVHIAWVT.js} +9 -9
- package/dist/chunk-KVHIAWVT.js.map +1 -0
- package/dist/{chunk-OFDN5NKS.js → chunk-KXDRI47U.js} +69 -12
- package/dist/chunk-KXDRI47U.js.map +1 -0
- package/dist/{chunk-NUACL52E.js → chunk-LLHXQS3C.js} +2 -2
- package/dist/chunk-LUKXJSRI.js +73 -0
- package/dist/chunk-LUKXJSRI.js.map +1 -0
- package/dist/{chunk-TL72BGP6.js → chunk-MORRVYPT.js} +2 -2
- package/dist/chunk-OTGH2HRS.js +1427 -0
- package/dist/chunk-OTGH2HRS.js.map +1 -0
- package/dist/{chunk-7ZEHSSUP.js → chunk-P4O6EH46.js} +4 -4
- package/dist/{chunk-KAYXR544.js → chunk-QVLPWNE3.js} +2 -2
- package/dist/chunk-QZWEJVWV.js +207 -0
- package/dist/chunk-QZWEJVWV.js.map +1 -0
- package/dist/chunk-RJ3VBUFK.js +781 -0
- package/dist/chunk-RJ3VBUFK.js.map +1 -0
- package/dist/chunk-RSYT7MVI.js +202 -0
- package/dist/chunk-RSYT7MVI.js.map +1 -0
- package/dist/{chunk-6IIL5M2L.js → chunk-S7PZA6IV.js} +10 -8
- package/dist/{chunk-6IIL5M2L.js.map → chunk-S7PZA6IV.js.map} +1 -1
- package/dist/chunk-SKSYYBCU.js +229 -0
- package/dist/chunk-SKSYYBCU.js.map +1 -0
- package/dist/{chunk-ULSWCPQG.js → chunk-SWSJWA2S.js} +476 -5
- package/dist/chunk-SWSJWA2S.js.map +1 -0
- package/dist/{chunk-KXGQYLFZ.js → chunk-UKBAJ2QQ.js} +61 -7
- package/dist/chunk-UKBAJ2QQ.js.map +1 -0
- package/dist/{chunk-FO5GGFOV.js → chunk-UR5DGNUO.js} +71 -9
- package/dist/chunk-UR5DGNUO.js.map +1 -0
- package/dist/{chunk-QN47QVBX.js → chunk-UUEW5KWB.js} +1 -1
- package/dist/chunk-UUEW5KWB.js.map +1 -0
- package/dist/{chunk-4CO6KG5S.js → chunk-VG45TUYK.js} +53 -7
- package/dist/{chunk-4CO6KG5S.js.map → chunk-VG45TUYK.js.map} +1 -1
- package/dist/{chunk-4LKGCFGG.js → chunk-WWKOVDWC.js} +2 -2
- package/dist/{chunk-KJTVU3HZ.js → chunk-WXIM2WS7.js} +8 -8
- package/dist/chunk-WXIM2WS7.js.map +1 -0
- package/dist/{chunk-VOGGLPG5.js → chunk-YQ57ORTV.js} +14 -1
- package/dist/chunk-YQ57ORTV.js.map +1 -0
- package/dist/{chunk-SOSQILHO.js → chunk-ZNMPGMHY.js} +44 -797
- package/dist/chunk-ZNMPGMHY.js.map +1 -0
- package/dist/{claude-TP2QO3BU.js → claude-7GGEWVEM.js} +2 -2
- package/dist/{cleanup-PJRIFFU4.js → cleanup-6PVAC4NI.js} +85 -34
- package/dist/cleanup-6PVAC4NI.js.map +1 -0
- package/dist/cli.js +630 -801
- package/dist/cli.js.map +1 -1
- package/dist/{commit-IVP3M4HG.js → commit-FZR5XDQG.js} +26 -23
- package/dist/commit-FZR5XDQG.js.map +1 -0
- package/dist/{compile-R2J65HBQ.js → compile-7ALJHZ4N.js} +9 -9
- package/dist/{contribute-VDZXHK5Y.js → contribute-5GKLK3BQ.js} +14 -6
- package/dist/contribute-5GKLK3BQ.js.map +1 -0
- package/dist/{dev-server-7F622OEO.js → dev-server-7SMIB7OF.js} +29 -15
- package/dist/dev-server-7SMIB7OF.js.map +1 -0
- package/dist/{feedback-E7VET7CL.js → feedback-G2GJFN2F.js} +18 -16
- package/dist/{feedback-E7VET7CL.js.map → feedback-G2GJFN2F.js.map} +1 -1
- package/dist/{git-2QDQ2X2S.js → git-GTLKAZRJ.js} +4 -4
- package/dist/hooks/iloom-hook.js +15 -0
- package/dist/ignite-H2O5Y5A2.js +34 -0
- package/dist/ignite-H2O5Y5A2.js.map +1 -0
- package/dist/index.d.ts +482 -58
- package/dist/index.js +1340 -44
- package/dist/index.js.map +1 -1
- package/dist/{init-676DHF6R.js → init-32YOKXRL.js} +57 -21
- package/dist/init-32YOKXRL.js.map +1 -0
- package/dist/{issues-PJSOLOBJ.js → issues-4UUAQ5K6.js} +61 -20
- package/dist/issues-4UUAQ5K6.js.map +1 -0
- package/dist/{lint-CJM7BAIM.js → lint-AAN2NZWG.js} +9 -9
- package/dist/mcp/harness-server.js +140 -0
- package/dist/mcp/harness-server.js.map +1 -0
- package/dist/mcp/issue-management-server.js +2599 -262
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/mcp/recap-server.js +144 -21
- package/dist/mcp/recap-server.js.map +1 -1
- package/dist/{neon-helpers-VVFFTLXE.js → neon-helpers-CQN2PB4S.js} +3 -3
- package/dist/neon-helpers-CQN2PB4S.js.map +1 -0
- package/dist/{open-544H7JF5.js → open-FXWW3VI4.js} +15 -15
- package/dist/open-FXWW3VI4.js.map +1 -0
- package/dist/{plan-Q7ELXDLC.js → plan-RQ5FPIGF.js} +358 -40
- package/dist/plan-RQ5FPIGF.js.map +1 -0
- package/dist/{projects-LH362JZQ.js → projects-2UOXFLNZ.js} +4 -4
- package/dist/prompts/CLAUDE.md +62 -0
- package/dist/prompts/init-prompt.txt +430 -34
- package/dist/prompts/issue-prompt.txt +473 -54
- package/dist/prompts/plan-prompt.txt +140 -19
- package/dist/prompts/pr-prompt.txt +44 -1
- package/dist/prompts/regular-prompt.txt +42 -1
- package/dist/prompts/session-summary-prompt.txt +14 -0
- package/dist/prompts/swarm-orchestrator-prompt.txt +464 -0
- package/dist/{rebase-YND35CIE.js → rebase-6NVLX5V7.js} +21 -12
- package/dist/rebase-6NVLX5V7.js.map +1 -0
- package/dist/{recap-3W7COH7D.js → recap-OMBOKJST.js} +47 -19
- package/dist/recap-OMBOKJST.js.map +1 -0
- package/dist/{run-QUXJKDQQ.js → run-BBXLRIZB.js} +15 -15
- package/dist/run-BBXLRIZB.js.map +1 -0
- package/dist/schema/package-iloom.schema.json +58 -0
- package/dist/schema/settings.schema.json +149 -15
- package/dist/{shell-QGECBLST.js → shell-RF7LTND5.js} +14 -7
- package/dist/shell-RF7LTND5.js.map +1 -0
- package/dist/{summary-G2T4452H.js → summary-WTQZ7XG2.js} +27 -25
- package/dist/summary-WTQZ7XG2.js.map +1 -0
- package/dist/{test-EA5NQFDC.js → test-SGO6I5Z7.js} +9 -9
- package/dist/{test-git-M7LSLEFL.js → test-git-XM4TM65W.js} +4 -4
- package/dist/test-jira-LDTOYFSD.js +96 -0
- package/dist/test-jira-LDTOYFSD.js.map +1 -0
- package/dist/{test-prefix-64NAAUON.js → test-prefix-GBO37XCN.js} +4 -4
- package/dist/{test-webserver-OK6Z5FJM.js → test-webserver-NZ3JTVLL.js} +6 -6
- package/dist/{vscode-AR5NNXXI.js → vscode-6XUGHJKL.js} +7 -7
- package/package.json +5 -1
- package/dist/ClaudeContextManager-HR5JQKAI.js +0 -14
- package/dist/ClaudeService-TK7FMC2X.js +0 -13
- package/dist/chunk-3I4ONZRT.js.map +0 -1
- package/dist/chunk-B7U6OKUR.js.map +0 -1
- package/dist/chunk-CWRI4JC3.js.map +0 -1
- package/dist/chunk-DGG2VY7B.js.map +0 -1
- package/dist/chunk-FJDRTVJX.js +0 -520
- package/dist/chunk-FJDRTVJX.js.map +0 -1
- package/dist/chunk-FO5GGFOV.js.map +0 -1
- package/dist/chunk-KBEIQP4G.js.map +0 -1
- package/dist/chunk-KJTVU3HZ.js.map +0 -1
- package/dist/chunk-KXGQYLFZ.js.map +0 -1
- package/dist/chunk-OFDN5NKS.js.map +0 -1
- package/dist/chunk-QN47QVBX.js.map +0 -1
- package/dist/chunk-R4YWBGY6.js.map +0 -1
- package/dist/chunk-RI2YL6TK.js.map +0 -1
- package/dist/chunk-SOSQILHO.js.map +0 -1
- package/dist/chunk-ULSWCPQG.js.map +0 -1
- package/dist/chunk-VOGGLPG5.js.map +0 -1
- package/dist/chunk-VPTAX5TR.js.map +0 -1
- package/dist/chunk-W6DP5RVR.js +0 -101
- package/dist/chunk-W6DP5RVR.js.map +0 -1
- package/dist/chunk-WHI5KEOX.js +0 -121
- package/dist/chunk-WHI5KEOX.js.map +0 -1
- package/dist/chunk-YKFCCV6S.js.map +0 -1
- package/dist/chunk-Z2TWEXR7.js.map +0 -1
- package/dist/cleanup-PJRIFFU4.js.map +0 -1
- package/dist/commit-IVP3M4HG.js.map +0 -1
- package/dist/contribute-VDZXHK5Y.js.map +0 -1
- package/dist/dev-server-7F622OEO.js.map +0 -1
- package/dist/ignite-IW35CDBD.js +0 -784
- package/dist/ignite-IW35CDBD.js.map +0 -1
- package/dist/init-676DHF6R.js.map +0 -1
- package/dist/issues-PJSOLOBJ.js.map +0 -1
- package/dist/open-544H7JF5.js.map +0 -1
- package/dist/plan-Q7ELXDLC.js.map +0 -1
- package/dist/rebase-YND35CIE.js.map +0 -1
- package/dist/recap-3W7COH7D.js.map +0 -1
- package/dist/run-QUXJKDQQ.js.map +0 -1
- package/dist/shell-QGECBLST.js.map +0 -1
- package/dist/summary-G2T4452H.js.map +0 -1
- /package/dist/{BranchNamingService-K6XNWQ6C.js.map → BranchNamingService-25KSZAEM.js.map} +0 -0
- /package/dist/{ClaudeContextManager-HR5JQKAI.js.map → ClaudeContextManager-66GR4BGM.js.map} +0 -0
- /package/dist/{ClaudeService-TK7FMC2X.js.map → ClaudeService-7KM5NA5Z.js.map} +0 -0
- /package/dist/{GitHubService-TGWJN4V4.js.map → GitHubService-MEHKHUQP.js.map} +0 -0
- /package/dist/{MetadataManager-W3C54UYT.js.map → IssueTrackerFactory-NG53YX5S.js.map} +0 -0
- /package/dist/{LoomLauncher-73NXL2CL.js.map → LoomLauncher-TDLZSYG2.js.map} +0 -0
- /package/dist/{ProjectCapabilityDetector-N5L7T4IY.js.map → MetadataManager-5QZSTKNN.js.map} +0 -0
- /package/dist/{PromptTemplateManager-36YLQRHP.js.map → ProjectCapabilityDetector-5KSYUTBJ.js.map} +0 -0
- /package/dist/{SettingsManager-AW3JTJHD.js.map → PromptTemplateManager-YOE2SIPG.js.map} +0 -0
- /package/dist/{claude-TP2QO3BU.js.map → SettingsManager-FNKCOZMQ.js.map} +0 -0
- /package/dist/{build-THZI572G.js.map → build-VHGEMXBA.js.map} +0 -0
- /package/dist/{chunk-AR5QKYNE.js.map → chunk-4FGEGQW4.js.map} +0 -0
- /package/dist/{chunk-A7NJF73J.js.map → chunk-CVCTIDDK.js.map} +0 -0
- /package/dist/{chunk-IZIYLYPK.js.map → chunk-G5V75JD5.js.map} +0 -0
- /package/dist/{chunk-TC7APDKU.js.map → chunk-I5T677EA.js.map} +0 -0
- /package/dist/{chunk-NWMORW3U.js.map → chunk-KIK2ZFAL.js.map} +0 -0
- /package/dist/{chunk-NUACL52E.js.map → chunk-LLHXQS3C.js.map} +0 -0
- /package/dist/{chunk-TL72BGP6.js.map → chunk-MORRVYPT.js.map} +0 -0
- /package/dist/{chunk-7ZEHSSUP.js.map → chunk-P4O6EH46.js.map} +0 -0
- /package/dist/{chunk-KAYXR544.js.map → chunk-QVLPWNE3.js.map} +0 -0
- /package/dist/{chunk-4LKGCFGG.js.map → chunk-WWKOVDWC.js.map} +0 -0
- /package/dist/{git-2QDQ2X2S.js.map → claude-7GGEWVEM.js.map} +0 -0
- /package/dist/{compile-R2J65HBQ.js.map → compile-7ALJHZ4N.js.map} +0 -0
- /package/dist/{neon-helpers-VVFFTLXE.js.map → git-GTLKAZRJ.js.map} +0 -0
- /package/dist/{lint-CJM7BAIM.js.map → lint-AAN2NZWG.js.map} +0 -0
- /package/dist/{projects-LH362JZQ.js.map → projects-2UOXFLNZ.js.map} +0 -0
- /package/dist/{test-EA5NQFDC.js.map → test-SGO6I5Z7.js.map} +0 -0
- /package/dist/{test-git-M7LSLEFL.js.map → test-git-XM4TM65W.js.map} +0 -0
- /package/dist/{test-prefix-64NAAUON.js.map → test-prefix-GBO37XCN.js.map} +0 -0
- /package/dist/{test-webserver-OK6Z5FJM.js.map → test-webserver-NZ3JTVLL.js.map} +0 -0
- /package/dist/{vscode-AR5NNXXI.js.map → vscode-6XUGHJKL.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/linear.ts","../src/types/linear.ts","../src/lib/providers/jira/JiraApiClient.ts","../src/lib/providers/jira/AdfMarkdownConverter.ts","../src/lib/providers/jira/JiraIssueTracker.ts"],"sourcesContent":["/**\n * Linear SDK utilities\n * Wrapper functions for the @linear/sdk\n */\n\nimport { LinearClient, IssueRelationType } from '@linear/sdk'\nimport type { LinearIssue, LinearComment } from '../types/linear.js'\nimport { LinearServiceError } from '../types/linear.js'\nimport { logger } from './logger.js'\n\n/**\n * Slugify a title for use in Linear URLs\n * Converts to lowercase, replaces non-alphanumeric with hyphens, truncates to reasonable length\n * @param title - Issue title\n * @param maxLength - Maximum slug length (default: 50)\n * @returns Slugified title\n */\nexport function slugifyTitle(title: string, maxLength: number = 50): string {\n // Convert to lowercase, replace non-alphanumeric chars with hyphens\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n\n // If already short enough, return as-is\n if (slug.length <= maxLength) {\n return slug\n }\n\n // Split by hyphens and rebuild until we hit the limit\n const parts = slug.split('-')\n let result = ''\n for (const part of parts) {\n const candidate = result ? `${result}-${part}` : part\n if (candidate.length > maxLength) {\n break\n }\n result = candidate\n }\n\n return result || slug.slice(0, maxLength) // fallback if first part is too long\n}\n\n/**\n * Build a Linear issue URL with optional title slug\n * @param identifier - Issue identifier (e.g., \"ENG-123\")\n * @param title - Optional issue title for slug\n * @returns Linear URL\n */\nexport function buildLinearIssueUrl(identifier: string, title?: string): string {\n const base = `https://linear.app/issue/${identifier}`\n if (title) {\n const slug = slugifyTitle(title)\n return slug ? `${base}/${slug}` : base\n }\n return base\n}\n\n/**\n * Get Linear API token from environment\n * @returns API token\n * @throws LinearServiceError if token not found\n */\nfunction getLinearApiToken(): string {\n const token = process.env.LINEAR_API_TOKEN\n if (!token) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable.',\n )\n }\n return token\n}\n\n/**\n * Create a Linear SDK client instance\n * @param apiToken - Optional API token (takes precedence over env var)\n * @returns Configured LinearClient\n */\nfunction createLinearClient(apiToken?: string): LinearClient {\n const token = apiToken ?? getLinearApiToken()\n return new LinearClient({ apiKey: token })\n}\n\n/**\n * Handle SDK errors and convert to LinearServiceError\n * @param error - Error from SDK\n * @param context - Context string for debugging\n * @throws LinearServiceError\n */\nfunction handleLinearError(error: unknown, context: string): never {\n logger.debug(`${context}: Handling error`, { error })\n\n // SDK errors typically have a message property\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n // Map common error patterns\n if (errorMessage.includes('not found') || errorMessage.includes('Not found')) {\n throw new LinearServiceError('NOT_FOUND', 'Linear issue or resource not found', { error })\n }\n\n if (\n errorMessage.includes('unauthorized') ||\n errorMessage.includes('Unauthorized') ||\n errorMessage.includes('Invalid API key')\n ) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'Linear authentication failed. Check LINEAR_API_TOKEN.',\n { error },\n )\n }\n\n if (errorMessage.includes('rate limit')) {\n throw new LinearServiceError('RATE_LIMITED', 'Linear API rate limit exceeded', { error })\n }\n\n // Generic SDK error\n throw new LinearServiceError('CLI_ERROR', `Linear SDK error: ${errorMessage}`, { error })\n}\n\n/**\n * Fetch a Linear issue by identifier\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Linear issue details\n * @throws LinearServiceError if issue not found or SDK error\n */\nexport async function fetchLinearIssue(identifier: string): Promise<LinearIssue> {\n try {\n logger.debug(`Fetching Linear issue: ${identifier}`)\n const client = createLinearClient()\n const issue = await client.issue(identifier)\n\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Convert SDK issue to our LinearIssue type\n const result: LinearIssue = {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n createdAt: issue.createdAt.toISOString(),\n updatedAt: issue.updatedAt.toISOString(),\n }\n\n // Add optional fields if present\n if (issue.description) {\n result.description = issue.description\n }\n\n if (issue.state) {\n const state = await issue.state\n if (state?.name) {\n result.state = state.name\n }\n }\n\n return result\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssue')\n }\n}\n\n/**\n * Create a new Linear issue\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param teamKey - Team key (e.g., \"ENG\", \"PLAT\")\n * @param labels - Optional label names to apply\n * @returns Created issue identifier and URL\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearIssue(\n title: string,\n body: string,\n teamKey: string,\n _labels?: string[],\n): Promise<{ identifier: string; url: string }> {\n try {\n logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`)\n const client = createLinearClient()\n\n // Get team by key\n const teams = await client.teams()\n const team = teams.nodes.find((t) => t.key === teamKey)\n\n if (!team) {\n throw new LinearServiceError('NOT_FOUND', `Linear team ${teamKey} not found`)\n }\n\n // Create issue\n const issueInput: { teamId: string; title: string; description?: string } = {\n teamId: team.id,\n title,\n }\n\n if (body) {\n issueInput.description = body\n }\n\n const payload = await client.createIssue(issueInput)\n\n const issue = await payload.issue\n\n if (!issue) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear issue')\n }\n\n // Construct URL\n const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title)\n\n return {\n identifier: issue.identifier,\n url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearIssue')\n }\n}\n\n/**\n * Create a child issue linked to a parent issue\n * Linear supports atomic creation with parentId field\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param teamKey - Team key (e.g., \"ENG\")\n * @param parentId - Parent issue UUID (from issue.id, not identifier)\n * @param labels - Optional label names to apply\n * @returns Created issue identifier and URL\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearChildIssue(\n title: string,\n body: string,\n teamKey: string,\n parentId: string,\n _labels?: string[],\n): Promise<{ identifier: string; url: string }> {\n try {\n logger.debug(`Creating Linear child issue in team ${teamKey}: ${title}`)\n const client = createLinearClient()\n\n // Get team by key\n const teams = await client.teams()\n const team = teams.nodes.find((t) => t.key === teamKey)\n\n if (!team) {\n throw new LinearServiceError('NOT_FOUND', `Linear team ${teamKey} not found`)\n }\n\n // Create issue with parentId for atomic parent-child relationship\n const issueInput: { teamId: string; title: string; description?: string; parentId: string } = {\n teamId: team.id,\n title,\n parentId, // UUID of parent issue\n }\n\n if (body) {\n issueInput.description = body\n }\n\n const payload = await client.createIssue(issueInput)\n\n const issue = await payload.issue\n\n if (!issue) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear child issue')\n }\n\n // Construct URL\n const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title)\n\n return {\n identifier: issue.identifier,\n url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearChildIssue')\n }\n}\n\n/**\n * Create a comment on a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param body - Comment body (markdown)\n * @returns Created comment details\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearComment(\n identifier: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Creating comment on Linear issue ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier to get its ID\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Create comment using issue ID\n const payload = await client.createComment({\n issueId: issue.id,\n body,\n })\n\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearComment')\n }\n}\n\n/**\n * Update a Linear issue's workflow state\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param stateName - Target state name (e.g., \"In Progress\", \"Done\")\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearIssueState(\n identifier: string,\n stateName: string,\n): Promise<void> {\n try {\n logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Get team to find state\n const team = await issue.team\n if (!team) {\n throw new LinearServiceError('CLI_ERROR', 'Issue has no team')\n }\n\n // Find state by name\n const states = await team.states()\n const state = states.nodes.find((s) => s.name === stateName)\n\n if (!state) {\n throw new LinearServiceError(\n 'NOT_FOUND',\n `State \"${stateName}\" not found in team ${team.key}`,\n )\n }\n\n // Update issue state\n await client.updateIssue(issue.id, {\n stateId: state.id,\n })\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearIssueState')\n }\n}\n\n/**\n * Edit a Linear issue's properties (title, description)\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param updates - Fields to update\n * @throws LinearServiceError on update failure\n */\nexport async function editLinearIssue(\n identifier: string,\n updates: { title?: string; description?: string },\n): Promise<void> {\n try {\n logger.debug(`Editing Linear issue ${identifier}`, { updates })\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Build update payload\n const updatePayload: { title?: string; description?: string } = {}\n if (updates.title !== undefined) {\n updatePayload.title = updates.title\n }\n if (updates.description !== undefined) {\n updatePayload.description = updates.description\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await client.updateIssue(issue.id, updatePayload)\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'editLinearIssue')\n }\n}\n\n/**\n * Get a specific comment by ID\n * @param commentId - Linear comment UUID\n * @returns Comment details\n * @throws LinearServiceError if comment not found\n */\nexport async function getLinearComment(commentId: string): Promise<LinearComment> {\n try {\n logger.debug(`Fetching Linear comment: ${commentId}`)\n const client = createLinearClient()\n const comment = await client.comment({ id: commentId })\n\n if (!comment) {\n throw new LinearServiceError('NOT_FOUND', `Linear comment ${commentId} not found`)\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'getLinearComment')\n }\n}\n\n/**\n * Update an existing comment\n * @param commentId - Linear comment UUID\n * @param body - New comment body (markdown)\n * @returns Updated comment details\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearComment(\n commentId: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Updating Linear comment: ${commentId}`)\n const client = createLinearClient()\n\n const payload = await client.updateComment(commentId, { body })\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to update Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearComment')\n }\n}\n\n/**\n * Fetch all comments for a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Array of comments\n * @throws LinearServiceError on fetch failure\n */\nexport async function fetchLinearIssueComments(identifier: string): Promise<LinearComment[]> {\n try {\n logger.debug(`Fetching comments for Linear issue: ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Fetch comments\n const comments = await issue.comments({ first: 100 })\n\n return comments.nodes.map((comment) => ({\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }))\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssueComments')\n }\n}\n\n/**\n * Get child issues of a parent Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param options - Optional settings\n * @param options.apiToken - Optional API token (takes precedence over env var)\n * @returns Array of child issues\n * @throws LinearServiceError on fetch failure\n */\nexport async function getLinearChildIssues(\n identifier: string,\n options?: { apiToken?: string },\n): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n try {\n logger.debug(`Fetching child issues for Linear issue: ${identifier}`)\n const client = createLinearClient(options?.apiToken)\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Fetch child issues\n const children = await issue.children({ first: 100 })\n\n // Build results, fetching state in parallel\n const results = await Promise.all(\n children.nodes.map(async (child) => {\n const stateObj = await child.state\n const state = stateObj?.name ?? 'unknown'\n\n return {\n id: child.identifier,\n title: child.title,\n url: child.url,\n state,\n }\n }),\n )\n\n return results\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'getLinearChildIssues')\n }\n}\n\n// Linear Issue Dependency Operations\n\n/**\n * Dependency result for Linear issues\n */\nexport interface LinearDependencyResult {\n id: string\n title: string\n url: string\n state: string\n}\n\n/**\n * Create a blocking relationship between two issues\n * @param blockingIssueId - UUID of the issue that blocks (the blocker)\n * @param blockedIssueId - UUID of the issue being blocked\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearIssueRelation(\n blockingIssueId: string,\n blockedIssueId: string,\n): Promise<void> {\n try {\n logger.debug(`Creating Linear issue relation: ${blockingIssueId} blocks ${blockedIssueId}`)\n const client = createLinearClient()\n\n // Create a \"blocks\" relation from blockingIssue to blockedIssue\n // In Linear, the relation is created on the blocking issue pointing to the blocked issue\n const payload = await client.createIssueRelation({\n issueId: blockingIssueId,\n relatedIssueId: blockedIssueId,\n type: IssueRelationType.Blocks,\n })\n\n if (!payload.success) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear issue relation')\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearIssueRelation')\n }\n}\n\n/**\n * Get dependencies for a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param direction - 'blocking' for issues this blocks, 'blocked_by' for blockers, 'both' for all\n * @returns Object with blocking and blockedBy arrays\n * @throws LinearServiceError on fetch failure\n */\nexport async function getLinearIssueDependencies(\n identifier: string,\n direction: 'blocking' | 'blocked_by' | 'both',\n): Promise<{ blocking: LinearDependencyResult[]; blockedBy: LinearDependencyResult[] }> {\n try {\n logger.debug(`Fetching Linear issue dependencies: ${identifier} (direction: ${direction})`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Fetch relations and inverse relations in parallel\n const [relations, inverseRelations] = await Promise.all([\n issue.relations(),\n issue.inverseRelations(),\n ])\n\n const blocking: LinearDependencyResult[] = []\n const blockedBy: LinearDependencyResult[] = []\n\n // Helper to build dependency result from a resolved issue\n const buildDependencyResult = async (\n relatedIssue: { identifier: string; title: string; url: string; state: unknown } | null | undefined,\n ): Promise<LinearDependencyResult | null> => {\n if (!relatedIssue) return null\n\n const stateObj = await (relatedIssue.state as Promise<{ name?: string } | null | undefined>)\n const state = stateObj?.name ?? 'unknown'\n\n return {\n id: relatedIssue.identifier,\n title: relatedIssue.title,\n url: relatedIssue.url,\n state,\n }\n }\n\n // Process blocking relations (this issue blocks the related issue)\n // relations with type 'blocks' mean this issue blocks the related issue\n if (direction === 'blocking' || direction === 'both') {\n const blockingRelations = relations.nodes.filter(\n (r) => r.type === IssueRelationType.Blocks,\n )\n // Resolve all related issues in parallel, then build results\n // Filter out undefined relatedIssue before Promise.all (LinearFetch<Issue> | undefined)\n const relatedIssuePromises = blockingRelations\n .map((r) => r.relatedIssue)\n .filter((p): p is NonNullable<typeof p> => p !== undefined)\n const relatedIssues = await Promise.all(relatedIssuePromises)\n const blockingResults = await Promise.all(\n relatedIssues.map((issue) => buildDependencyResult(issue)),\n )\n blocking.push(...blockingResults.filter((r): r is LinearDependencyResult => r !== null))\n }\n\n // Process blocked by relations (the related issue blocks this issue)\n // inverseRelations with type 'blocks' mean the related issue blocks this issue\n if (direction === 'blocked_by' || direction === 'both') {\n const blockedByRelations = inverseRelations.nodes.filter(\n (r) => r.type === IssueRelationType.Blocks,\n )\n // Resolve all source issues in parallel, then build results\n // Filter out undefined issue before Promise.all (LinearFetch<Issue> | undefined)\n const sourceIssuePromises = blockedByRelations\n .map((r) => r.issue)\n .filter((p): p is NonNullable<typeof p> => p !== undefined)\n const sourceIssues = await Promise.all(sourceIssuePromises)\n const blockedByResults = await Promise.all(\n sourceIssues.map((issue) => buildDependencyResult(issue)),\n )\n blockedBy.push(...blockedByResults.filter((r): r is LinearDependencyResult => r !== null))\n }\n\n return { blocking, blockedBy }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'getLinearIssueDependencies')\n }\n}\n\n/**\n * Delete an issue relation by ID\n * @param relationId - UUID of the relation to delete\n * @throws LinearServiceError on deletion failure\n */\nexport async function deleteLinearIssueRelation(relationId: string): Promise<void> {\n try {\n logger.debug(`Deleting Linear issue relation: ${relationId}`)\n const client = createLinearClient()\n\n const payload = await client.deleteIssueRelation(relationId)\n\n if (!payload.success) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to delete Linear issue relation')\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'deleteLinearIssueRelation')\n }\n}\n\n// Issue List Operations (for il issues command)\n\nexport interface LinearIssueListItem {\n id: string\n title: string\n updatedAt: string\n url: string\n state: string\n}\n\n/**\n * Fetch a list of active Linear issues for a team, sorted by recently updated\n * @param teamKey - Team key (e.g., \"ENG\", \"PLAT\")\n * @param options - Fetch options\n * @param options.limit - Maximum number of issues to return (default: 100)\n * @param options.apiToken - Optional API token (takes precedence over env var)\n * @returns Array of issues\n * @throws LinearServiceError on fetch failure\n */\nexport async function fetchLinearIssueList(\n teamKey: string,\n options?: { limit?: number; apiToken?: string; mine?: boolean },\n): Promise<LinearIssueListItem[]> {\n try {\n const limit = options?.limit ?? 100\n\n logger.debug(`Fetching Linear issue list for team ${teamKey}`, { limit, mine: options?.mine })\n const client = createLinearClient(options?.apiToken)\n\n // Get team by key\n const teams = await client.teams()\n const team = teams.nodes.find((t) => t.key === teamKey)\n\n if (!team) {\n throw new LinearServiceError('NOT_FOUND', `Linear team ${teamKey} not found`)\n }\n\n // Build filter: always exclude completed/canceled states\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Linear SDK filter types are complex and not fully exported\n const filter: any = {\n state: {\n type: {\n nin: ['completed', 'canceled'],\n },\n },\n }\n\n // When --mine is set, filter to issues assigned to the authenticated user\n if (options?.mine) {\n filter.assignee = { isMe: { eq: true } }\n }\n\n // Fetch issues: filter out completed and canceled states, order by updatedAt\n const issues = await team.issues({\n first: limit,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- PaginationOrderBy is a const enum incompatible with isolatedModules\n orderBy: 'updatedAt' as any,\n filter,\n })\n\n // Build results, resolving state names in parallel\n const results = await Promise.all(\n issues.nodes.map(async (issue) => {\n const stateObj = await issue.state\n const state = stateObj?.name ?? 'unknown'\n\n return {\n id: issue.identifier,\n title: issue.title,\n updatedAt: issue.updatedAt.toISOString(),\n url: issue.url,\n state,\n }\n }),\n )\n\n return results\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssueList')\n }\n}\n\n/**\n * Find the relation ID between two issues for removal\n * @param blockingIdentifier - Identifier of the blocking issue\n * @param blockedIdentifier - Identifier of the blocked issue\n * @returns The relation ID if found, null otherwise\n * @throws LinearServiceError on fetch failure\n */\nexport async function findLinearIssueRelation(\n blockingIdentifier: string,\n blockedIdentifier: string,\n): Promise<string | null> {\n try {\n logger.debug(`Finding Linear issue relation: ${blockingIdentifier} blocks ${blockedIdentifier}`)\n const client = createLinearClient()\n\n // Get the blocking issue\n const blockingIssue = await client.issue(blockingIdentifier)\n if (!blockingIssue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${blockingIdentifier} not found`)\n }\n\n // Get the blocked issue to get its ID\n const blockedIssue = await client.issue(blockedIdentifier)\n if (!blockedIssue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${blockedIdentifier} not found`)\n }\n\n // Find the relation from blockingIssue that blocks blockedIssue\n const relations = await blockingIssue.relations()\n\n // Filter to only 'blocks' relations and fetch related issues in parallel\n const blockingRelations = relations.nodes.filter(\n (r) => r.type === IssueRelationType.Blocks,\n )\n\n const relationsWithIssues = await Promise.all(\n blockingRelations.map(async (relation) => ({\n relation,\n relatedIssue: await relation.relatedIssue,\n })),\n )\n\n const matchingRelation = relationsWithIssues.find(\n ({ relatedIssue }) => relatedIssue?.id === blockedIssue.id,\n )\n\n return matchingRelation?.relation.id ?? null\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'findLinearIssueRelation')\n }\n}\n","/**\n * Linear API response types (from @linear/sdk)\n */\n\n/**\n * Linear issue response from SDK\n */\nexport interface LinearIssue {\n /** Linear internal UUID */\n id: string\n /** Issue identifier in TEAM-NUMBER format (e.g., ENG-123) */\n identifier: string\n /** Issue title */\n title: string\n /** Issue description (markdown) */\n description?: string\n /** Current workflow state name */\n state?: string\n /** Linear web URL */\n url: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n}\n\n/**\n * Linear comment response from SDK\n */\nexport interface LinearComment {\n /** Comment UUID */\n id: string\n /** Comment body (markdown) */\n body: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n /** Comment URL */\n url: string\n}\n\n/**\n * Linear error codes\n */\nexport type LinearErrorCode =\n | 'NOT_FOUND'\n | 'UNAUTHORIZED'\n | 'INVALID_STATE'\n | 'RATE_LIMITED'\n | 'CLI_NOT_FOUND'\n | 'CLI_ERROR'\n\n/**\n * Linear error details\n */\nexport interface LinearError {\n code: LinearErrorCode\n message: string\n details?: unknown\n}\n\n/**\n * Custom error class for Linear operations\n */\nexport class LinearServiceError extends Error {\n constructor(\n public code: LinearErrorCode,\n message: string,\n public details?: unknown,\n ) {\n super(message)\n this.name = 'LinearServiceError'\n // Maintain proper stack trace for where error was thrown (V8 only)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LinearServiceError)\n }\n }\n}\n","// JiraApiClient - REST API wrapper for Jira operations\n// Handles authentication and common API request patterns\n\nimport https from 'node:https'\nimport { getLogger } from '../../../utils/logger-context.js'\nimport { markdownToAdf } from './AdfMarkdownConverter.js'\n\n/**\n * Jira API configuration\n */\nexport interface JiraConfig {\n\thost: string // e.g., \"https://yourcompany.atlassian.net\"\n\tusername: string // email address or username\n\tapiToken: string // API token from Atlassian account\n}\n\n/**\n * Jira issue response from API\n */\n/**\n * Jira issue link (relationship between issues)\n */\nexport interface JiraIssueLink {\n\tid: string\n\ttype: {\n\t\tid: string\n\t\tname: string\n\t\tinward: string\n\t\toutward: string\n\t}\n\tinwardIssue?: {\n\t\tid: string\n\t\tkey: string\n\t\tfields: {\n\t\t\tsummary: string\n\t\t\tstatus: { name: string }\n\t\t}\n\t}\n\toutwardIssue?: {\n\t\tid: string\n\t\tkey: string\n\t\tfields: {\n\t\t\tsummary: string\n\t\t\tstatus: { name: string }\n\t\t}\n\t}\n}\n\nexport interface JiraIssue {\n\tid: string\n\tkey: string\n\tfields: {\n\t\tsummary: string\n\t\tdescription: string | null | unknown // Can be string, ADF object, or null\n\t\tstatus: {\n\t\t\tname: string\n\t\t}\n\t\tissuetype: {\n\t\t\tname: string\n\t\t}\n\t\tproject: {\n\t\t\tkey: string\n\t\t\tname: string\n\t\t}\n\t\tassignee: {\n\t\t\tdisplayName: string\n\t\t\temailAddress: string\n\t\t\taccountId: string\n\t\t} | null\n\t\treporter: {\n\t\t\tdisplayName: string\n\t\t\temailAddress: string\n\t\t\taccountId: string\n\t\t}\n\t\tlabels: string[]\n\t\tcreated: string\n\t\tupdated: string\n\t\tissuelinks?: JiraIssueLink[]\n\t\tparent?: {\n\t\t\tid: string\n\t\t\tkey: string\n\t\t\tfields: {\n\t\t\t\tsummary: string\n\t\t\t\tstatus: { name: string }\n\t\t\t}\n\t\t}\n\t\t[key: string]: unknown // Allow additional fields\n\t}\n\t[key: string]: unknown // Allow additional top-level fields\n}\n\n/**\n * Jira comment response from API\n */\nexport interface JiraComment {\n\tid: string\n\tauthor: {\n\t\tdisplayName: string\n\t\temailAddress: string\n\t\taccountId: string\n\t}\n\tbody: string | unknown // Can be string or ADF object\n\tcreated: string\n\tupdated: string\n\t[key: string]: unknown\n}\n\n/**\n * Jira transition response from API\n */\nexport interface JiraTransition {\n\tid: string\n\tname: string\n\tto: {\n\t\tid: string\n\t\tname: string\n\t}\n}\n\n/**\n * JiraApiClient provides low-level REST API access to Jira\n * \n * Authentication: Basic Auth with username and API token\n * API Reference: https://developer.atlassian.com/cloud/jira/platform/rest/v3/\n */\nexport class JiraApiClient {\n\tprivate readonly baseUrl: string\n\tprivate readonly authHeader: string\n\n\tconstructor(config: JiraConfig) {\n\t\tthis.baseUrl = `${config.host.replace(/\\/$/, '')}/rest/api/3`\n\t\t\n\t\t// Create Basic Auth header\n\t\tconst credentials = Buffer.from(`${config.username}:${config.apiToken}`).toString('base64')\n\t\tthis.authHeader = `Basic ${credentials}`\n\t}\n\n\t/**\n\t * Make an HTTP request to Jira API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown\n\t): Promise<T> {\n\t\tconst url = new URL(`${this.baseUrl}${endpoint}`)\n\t\tgetLogger().debug(`Jira API ${method} request`, { url: url.toString() })\n\t\tif (body) {\n\t\t\tgetLogger().debug('Jira API request body', JSON.stringify(body, null, 2))\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst options: https.RequestOptions = {\n\t\t\t\thostname: url.hostname,\n\t\t\t\tport: url.port || 443,\n\t\t\t\tpath: url.pathname + url.search,\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\t'Authorization': this.authHeader,\n\t\t\t\t\t'Accept': 'application/json',\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tconst req = https.request({ ...options, timeout: 30000 }, (res) => {\n\t\t\t\tconst chunks: Buffer[] = []\n\n\t\t\t\tres.on('data', (chunk: Buffer) => {\n\t\t\t\t\tchunks.push(chunk)\n\t\t\t\t})\n\n\t\t\t\tres.on('end', () => {\n\t\t\t\t\tconst data = Buffer.concat(chunks).toString('utf8')\n\n\t\t\t\t\tif (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {\n\t\t\t\t\t\tlet errorDetail = data\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(data)\n\t\t\t\t\t\t\tconst parts: string[] = []\n\t\t\t\t\t\t\tif (parsed.errorMessages?.length) {\n\t\t\t\t\t\t\t\tparts.push(`messages: ${parsed.errorMessages.join(', ')}`)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (parsed.errors && Object.keys(parsed.errors).length) {\n\t\t\t\t\t\t\t\tparts.push(`field errors: ${JSON.stringify(parsed.errors)}`)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (parts.length) {\n\t\t\t\t\t\t\t\terrorDetail = parts.join('; ')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Use raw data if not JSON\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(new Error(`Jira API error (${res.statusCode}): ${errorDetail}`))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle empty response (e.g., 204 No Content)\n\t\t\t\t\tif (res.statusCode === 204 || !data) {\n\t\t\t\t\t\tresolve({} as T)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresolve(JSON.parse(data) as T)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treject(new Error(`Failed to parse Jira API response: ${error}`))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\treq.on('timeout', () => {\n\t\t\t\treq.destroy()\n\t\t\t\treject(new Error('Jira API request timed out after 30 seconds'))\n\t\t\t})\n\n\t\t\treq.on('error', (error) => {\n\t\t\t\treject(new Error(`Jira API request failed: ${error.message}`))\n\t\t\t})\n\n\t\t\tif (body) {\n\t\t\t\treq.write(JSON.stringify(body))\n\t\t\t}\n\n\t\t\treq.end()\n\t\t})\n\t}\n\n\t/**\n\t * Make a GET request to Jira API\n\t */\n\tprivate async get<T>(endpoint: string): Promise<T> {\n\t\treturn this.request<T>('GET', endpoint)\n\t}\n\n\t/**\n\t * Make a POST request to Jira API\n\t */\n\tprivate async post<T>(endpoint: string, body: unknown): Promise<T> {\n\t\treturn this.request<T>('POST', endpoint, body)\n\t}\n\n\t/**\n\t * Make a PUT request to Jira API\n\t */\n\tprivate async put<T>(endpoint: string, body: unknown): Promise<T> {\n\t\treturn this.request<T>('PUT', endpoint, body)\n\t}\n\n\t/**\n\t * Make a DELETE request to Jira API\n\t */\n\tprivate async delete(endpoint: string): Promise<void> {\n\t\tawait this.request('DELETE', endpoint)\n\t}\n\n\t/**\n\t * Fetch an issue by key (e.g., \"PROJ-123\")\n\t */\n\tasync getIssue(issueKey: string): Promise<JiraIssue> {\n\t\treturn this.get<JiraIssue>(`/issue/${issueKey}`)\n\t}\n\n\t/**\n\t * Add a comment to an issue\n\t * Accepts Markdown content which is converted to ADF for Jira\n\t */\n\tasync addComment(issueKey: string, body: string): Promise<JiraComment> {\n\t\tconst adfBody = markdownToAdf(body);\n\t\tgetLogger().debug('Adding comment to Jira issue', { issueKey, bodyLength: body.length })\n\t\treturn this.post<JiraComment>(`/issue/${issueKey}/comment`, {\n\t\t\tbody: adfBody\n\t\t})\n\t}\n\n\t/**\n\t * Get all comments for an issue\n\t */\n\tasync getComments(issueKey: string): Promise<JiraComment[]> {\n\t\tconst response = await this.get<{ comments: JiraComment[]; total: number; maxResults: number }>(`/issue/${issueKey}/comment?maxResults=5000`)\n\t\tif (response.total > response.comments.length) {\n\t\t\tgetLogger().warn(`Comments truncated for issue ${issueKey}: returned ${response.comments.length} of ${response.total} total comments`)\n\t\t}\n\t\treturn response.comments\n\t}\n\n\t/**\n\t * Update a comment on an issue\n\t * Accepts Markdown content which is converted to ADF for Jira\n\t */\n\tasync updateComment(issueKey: string, commentId: string, body: string): Promise<JiraComment> {\n\t\treturn this.put<JiraComment>(`/issue/${issueKey}/comment/${commentId}`, {\n\t\t\tbody: markdownToAdf(body),\n\t\t})\n\t}\n\n\t/**\n\t * Get available transitions for an issue\n\t */\n\tasync getTransitions(issueKey: string): Promise<JiraTransition[]> {\n\t\tconst response = await this.get<{ transitions: JiraTransition[] }>(`/issue/${issueKey}/transitions`)\n\t\treturn response.transitions\n\t}\n\n\t/**\n\t * Transition an issue to a new state\n\t */\n\tasync transitionIssue(issueKey: string, transitionId: string): Promise<void> {\n\t\tawait this.post(`/issue/${issueKey}/transitions`, {\n\t\t\ttransition: {\n\t\t\t\tid: transitionId,\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * Create a new issue\n\t * Accepts Markdown description which is converted to ADF for Jira\n\t */\n\tasync createIssue(projectKey: string, summary: string, description: string, issueType = 'Task'): Promise<JiraIssue> {\n\t\treturn this.post<JiraIssue>('/issue', {\n\t\t\tfields: {\n\t\t\t\tproject: {\n\t\t\t\t\tkey: projectKey,\n\t\t\t\t},\n\t\t\t\tsummary,\n\t\t\t\tdescription: markdownToAdf(description),\n\t\t\t\tissuetype: {\n\t\t\t\t\tname: issueType,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * Update an issue's fields (summary, description)\n\t * @param issueKey - Jira issue key (e.g., \"PROJ-123\")\n\t * @param fields - Fields to update\n\t */\n\tasync updateIssue(issueKey: string, fields: { summary?: string; description?: string }): Promise<void> {\n\t\tconst updateFields: Record<string, unknown> = {}\n\t\tif (fields.summary !== undefined) {\n\t\t\tupdateFields.summary = fields.summary\n\t\t}\n\t\tif (fields.description !== undefined) {\n\t\t\tupdateFields.description = markdownToAdf(fields.description)\n\t\t}\n\n\t\tawait this.put(`/issue/${issueKey}`, { fields: updateFields })\n\t}\n\n\t/**\n\t * Create an issue with a parent (subtask or child issue)\n\t * Accepts Markdown description which is converted to ADF for Jira\n\t */\n\tasync createIssueWithParent(\n\t\tprojectKey: string,\n\t\tsummary: string,\n\t\tdescription: string,\n\t\tparentKey: string,\n\t\tissueType = 'Subtask'\n\t): Promise<JiraIssue> {\n\t\treturn this.post<JiraIssue>('/issue', {\n\t\t\tfields: {\n\t\t\t\tproject: {\n\t\t\t\t\tkey: projectKey,\n\t\t\t\t},\n\t\t\t\tsummary,\n\t\t\t\tdescription: markdownToAdf(description),\n\t\t\t\tissuetype: {\n\t\t\t\t\tname: issueType,\n\t\t\t\t},\n\t\t\t\tparent: {\n\t\t\t\t\tkey: parentKey,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * Create an issue link (dependency/relationship between issues)\n\t * @param inwardKey - The issue key for the inward side (e.g., the blocked issue)\n\t * @param outwardKey - The issue key for the outward side (e.g., the blocking issue)\n\t * @param linkType - The link type name (e.g., \"Blocks\")\n\t */\n\tasync createIssueLink(inwardKey: string, outwardKey: string, linkType: string): Promise<void> {\n\t\tawait this.post('/issueLink', {\n\t\t\ttype: {\n\t\t\t\tname: linkType,\n\t\t\t},\n\t\t\tinwardIssue: {\n\t\t\t\tkey: inwardKey,\n\t\t\t},\n\t\t\toutwardIssue: {\n\t\t\t\tkey: outwardKey,\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * Delete an issue link by ID\n\t */\n\tasync deleteIssueLink(linkId: string): Promise<void> {\n\t\tawait this.delete(`/issueLink/${linkId}`)\n\t}\n\n\t/**\n\t * Search issues using JQL\n\t * Automatically paginates through all results up to MAX_SEARCH_RESULTS.\n\t */\n\tasync searchIssues(jql: string): Promise<JiraIssue[]> {\n\t\tconst MAX_SEARCH_RESULTS = 5000\n\t\tconst allIssues: JiraIssue[] = []\n\t\tlet nextPageToken: string | undefined\n\t\tconst maxResults = 100\n\n\t\twhile (allIssues.length < MAX_SEARCH_RESULTS) {\n\t\t\tconst body: Record<string, unknown> = {\n\t\t\t\tjql,\n\t\t\t\tmaxResults,\n\t\t\t\tfields: [\n\t\t\t\t\t'summary', 'description', 'status', 'issuetype', 'project',\n\t\t\t\t\t'assignee', 'reporter', 'labels', 'created', 'updated',\n\t\t\t\t\t'issuelinks', 'parent',\n\t\t\t\t],\n\t\t\t}\n\t\t\tif (nextPageToken) {\n\t\t\t\tbody.nextPageToken = nextPageToken\n\t\t\t}\n\t\t\tconst response = await this.post<{ issues: JiraIssue[]; nextPageToken?: string }>(\n\t\t\t\t'/search/jql',\n\t\t\t\tbody\n\t\t\t)\n\t\t\tallIssues.push(...response.issues)\n\n\t\t\tif (!response.nextPageToken || response.issues.length === 0) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tnextPageToken = response.nextPageToken\n\t\t}\n\n\t\tif (allIssues.length >= MAX_SEARCH_RESULTS) {\n\t\t\tgetLogger().warn(`Search results truncated at ${MAX_SEARCH_RESULTS} issues. The query matched more results than the safety cap allows.`, { jql, returnedCount: allIssues.length })\n\t\t}\n\n\t\treturn allIssues\n\t}\n\n\t/**\n\t * Test connection to Jira API\n\t */\n\tasync testConnection(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.get('/myself')\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error)\n\t\t\tif (message.includes('Jira API error (401)') || message.includes('Jira API error (403)')) {\n\t\t\t\tgetLogger().error('Jira connection test failed: authentication error', { error })\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","// AdfMarkdownConverter - Converts between Atlassian Document Format (ADF) and Markdown\n// Uses extended-markdown-adf-parser for bidirectional conversion\n\nimport { ADFDocument, Parser } from 'extended-markdown-adf-parser'\n\nconst parser = new Parser()\n\n/**\n * Represents a node in the ADF tree structure\n */\ninterface AdfNode {\n\ttype: string\n\tcontent?: AdfNode[]\n\tmarks?: Array<{ type: string; attrs?: Record<string, unknown> }>\n\ttext?: string\n\tattrs?: Record<string, unknown>\n}\n\n/**\n * Recursively traverse ADF tree and ensure code-marked text only has the code mark.\n * ADF specification requires that code marks are standalone - no other marks allowed.\n */\nfunction sanitizeCodeMarks(node: AdfNode): AdfNode {\n\t// If node has marks and one of them is 'code', keep only the code mark\n\tif (node.marks?.some((mark) => mark.type === 'code')) {\n\t\tnode.marks = [{ type: 'code' }]\n\t}\n\n\t// Recursively process child nodes\n\tif (node.content && Array.isArray(node.content)) {\n\t\tnode.content = node.content.map((child) => sanitizeCodeMarks(child))\n\t}\n\n\treturn node\n}\n\n/**\n * Block-level ADF node types that can appear directly inside table cells.\n * Any node type NOT in this set is considered inline and must be wrapped in a paragraph.\n */\nconst BLOCK_LEVEL_TYPES = new Set([\n\t'paragraph',\n\t'bulletList',\n\t'orderedList',\n\t'codeBlock',\n\t'heading',\n\t'blockquote',\n\t'rule',\n\t'mediaGroup',\n\t'nestedExpand',\n\t'panel',\n\t'table',\n\t'taskList',\n\t'decisionList',\n\t'mediaSingle',\n])\n\n/**\n * Recursively traverse ADF tree and ensure tableCell/tableHeader content\n * is wrapped in block-level nodes. Jira's ADF spec requires that table cells\n * only contain block-level nodes (like paragraph), not inline nodes (like text).\n */\nfunction wrapTableCellContent(node: AdfNode): AdfNode {\n\t// Recursively process child nodes first\n\tif (node.content && Array.isArray(node.content)) {\n\t\tnode.content = node.content.map((child) => wrapTableCellContent(child))\n\t}\n\n\t// Only process tableCell and tableHeader nodes\n\tif (node.type !== 'tableCell' && node.type !== 'tableHeader') {\n\t\treturn node\n\t}\n\n\tif (!node.content || node.content.length === 0) {\n\t\treturn node\n\t}\n\n\tconst allInline = node.content.every((child) => !BLOCK_LEVEL_TYPES.has(child.type))\n\n\tif (allInline) {\n\t\t// All children are inline - wrap them all in a single paragraph\n\t\tnode.content = [{ type: 'paragraph', content: node.content }]\n\t} else {\n\t\t// Mixed block and inline nodes - wrap consecutive inline runs in paragraphs\n\t\tconst newContent: AdfNode[] = []\n\t\tlet inlineRun: AdfNode[] = []\n\n\t\tfor (const child of node.content) {\n\t\t\tif (BLOCK_LEVEL_TYPES.has(child.type)) {\n\t\t\t\t// Flush any accumulated inline nodes as a paragraph\n\t\t\t\tif (inlineRun.length > 0) {\n\t\t\t\t\tnewContent.push({ type: 'paragraph', content: inlineRun })\n\t\t\t\t\tinlineRun = []\n\t\t\t\t}\n\t\t\t\tnewContent.push(child)\n\t\t\t} else {\n\t\t\t\tinlineRun.push(child)\n\t\t\t}\n\t\t}\n\n\t\t// Flush remaining inline nodes\n\t\tif (inlineRun.length > 0) {\n\t\t\tnewContent.push({ type: 'paragraph', content: inlineRun })\n\t\t}\n\n\t\tnode.content = newContent\n\t}\n\n\treturn node\n}\n\n/**\n * Counter for generating unique task item local IDs\n */\nlet taskIdCounter = 0\n\n/**\n * Represents a contiguous block of checkbox bullet items extracted from markdown.\n */\ninterface CheckboxBlock {\n\tstates: Array<'DONE' | 'TODO'>\n\ttexts: string[]\n}\n\n/**\n * Get the canonical plain text for a markdown string by parsing it through\n * the same ADF parser used for the full document. This ensures consistent\n * text matching regardless of markdown formatting quirks (e.g., literal\n * asterisks being misinterpreted as italic markers).\n */\nfunction getCanonicalPlainText(text: string): string {\n\tconst miniAdf = parser.markdownToAdf(text)\n\treturn getPlainText(miniAdf as AdfNode)\n}\n\n/**\n * Parse the original markdown to find contiguous blocks of bullet items where\n * ALL items use `- [x] ` or `- [ ] ` syntax (also supports `*` and `+` markers).\n * Returns the blocks in document order. The text field stores the canonical plain\n * text (obtained by mini-parsing through the ADF parser) for matching against ADF content.\n *\n * Only groups items at the same indentation level into a block. When indent changes,\n * the current block is broken to avoid mismatching nested checkbox lists.\n */\nfunction extractCheckboxBlocks(markdown: string): CheckboxBlock[] {\n\tconst lines = markdown.split('\\n')\n\tconst blocks: CheckboxBlock[] = []\n\n\tlet i = 0\n\twhile (i < lines.length) {\n\t\t// Collect contiguous bullet lines (checkbox or regular) at the same indent level\n\t\t// Store raw text first, then compute canonical text after collecting continuations\n\t\tconst bulletLines: Array<{ isCheckbox: boolean; state: 'DONE' | 'TODO' | null; rawText: string }> = []\n\t\tlet blockIndent: number | null = null\n\n\t\twhile (i < lines.length) {\n\t\t\tconst line = lines[i] ?? ''\n\t\t\tconst checkboxMatch = line.match(/^(\\s*)[-*+] \\[([ xX])\\] (.*)$/)\n\t\t\tif (checkboxMatch) {\n\t\t\t\tconst indent = checkboxMatch[1]?.length ?? 0\n\t\t\t\tif (blockIndent === null) {\n\t\t\t\t\tblockIndent = indent\n\t\t\t\t} else if (indent !== blockIndent) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tconst state = checkboxMatch[2] === ' ' ? 'TODO' : 'DONE'\n\t\t\t\tbulletLines.push({ isCheckbox: true, state, rawText: checkboxMatch[3] ?? '' })\n\t\t\t\ti++\n\t\t\t} else if (line.match(/^\\s*[-*+] /)) {\n\t\t\t\t// Regular bullet item (no checkbox) - check indent\n\t\t\t\tconst indentMatch = line.match(/^(\\s*)/)\n\t\t\t\tconst indent = indentMatch?.[1]?.length ?? 0\n\t\t\t\tif (blockIndent === null) {\n\t\t\t\t\tblockIndent = indent\n\t\t\t\t} else if (indent !== blockIndent) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tbulletLines.push({ isCheckbox: false, state: null, rawText: '' })\n\t\t\t\ti++\n\t\t\t} else if (bulletLines.length > 0 && line.match(/^\\s/) && line.trim() !== '') {\n\t\t\t\t// Continuation line of the previous list item (indented, non-empty)\n\t\t\t\t// Append to the last bullet item's raw text\n\t\t\t\tconst lastItem = bulletLines[bulletLines.length - 1]\n\t\t\t\tif (lastItem) {\n\t\t\t\t\tlastItem.rawText += '\\n' + line.trim()\n\t\t\t\t}\n\t\t\t\ti++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (bulletLines.length > 0) {\n\t\t\tconst allCheckboxes = bulletLines.every((l) => l.isCheckbox)\n\t\t\tif (allCheckboxes) {\n\t\t\t\tblocks.push({\n\t\t\t\t\tstates: bulletLines.map((l) => l.state as 'DONE' | 'TODO'),\n\t\t\t\t\ttexts: bulletLines.map((l) => getCanonicalPlainText(l.rawText)),\n\t\t\t\t})\n\t\t\t}\n\t\t} else {\n\t\t\ti++\n\t\t}\n\t}\n\n\treturn blocks\n}\n\n/**\n * Recursively extract all plain text from an ADF node,\n * handling formatted content (bold, italic, code, links).\n */\nfunction getPlainText(node: AdfNode): string {\n\tif (node.type === 'text' && node.text !== undefined) return node.text\n\tif (!node.content) return ''\n\treturn node.content.map(getPlainText).join('')\n}\n\n/**\n * Recursively traverse ADF tree and convert bulletList nodes that match\n * extracted checkbox blocks into taskList/taskItem nodes.\n *\n * Uses a cursor into the blocks array to match bulletLists in document order.\n * The cursor is shared across the entire tree traversal.\n */\nfunction convertCheckboxesToTaskList(node: AdfNode, blocks: CheckboxBlock[]): AdfNode {\n\tconst cursor = { index: 0 }\n\treturn convertCheckboxesRecursive(node, blocks, cursor)\n}\n\nfunction convertCheckboxesRecursive(\n\tnode: AdfNode,\n\tblocks: CheckboxBlock[],\n\tcursor: { index: number }\n): AdfNode {\n\tif (node.type === 'bulletList' && node.content && node.content.length > 0 && cursor.index < blocks.length) {\n\t\tconst block = blocks[cursor.index]\n\t\tif (!block) return node\n\n\t\t// Check if this bulletList matches the next checkbox block\n\t\tif (node.content.length === block.states.length) {\n\t\t\tconst plaintexts = node.content.map((listItem) => getPlainText(listItem))\n\t\t\tconst matches = plaintexts.every((text, i) => text === block.texts[i])\n\n\t\t\tif (matches) {\n\t\t\t\t// Guard: all listItems must have simple structure (single paragraph child)\n\t\t\t\t// Multi-paragraph or complex items cannot be safely converted to taskItem\n\t\t\t\tconst allSimple = node.content.every((item) => {\n\t\t\t\t\treturn item.content?.length === 1 && item.content[0]?.type === 'paragraph'\n\t\t\t\t})\n\t\t\t\tif (!allSimple) {\n\t\t\t\t\tcursor.index++ // Consume the block even though we can't convert it\n\t\t\t\t\treturn node // Items too complex for taskItem\n\t\t\t\t}\n\n\t\t\t\t// Convert bulletList -> taskList\n\t\t\t\tcursor.index++\n\t\t\t\tnode.type = 'taskList'\n\t\t\t\tnode.attrs = { localId: `tasklist-${++taskIdCounter}` }\n\n\t\t\t\tfor (const [i, listItem] of node.content.entries()) {\n\t\t\t\t\tlistItem.type = 'taskItem'\n\t\t\t\t\tlistItem.attrs = {\n\t\t\t\t\t\tlocalId: `task-${++taskIdCounter}`,\n\t\t\t\t\t\tstate: block.states[i],\n\t\t\t\t\t}\n\n\t\t\t\t\t// Unwrap paragraph: listItem has paragraph > [inline nodes],\n\t\t\t\t\t// but taskItem should have inline nodes directly\n\t\t\t\t\tconst firstChild = listItem.content?.[0]\n\t\t\t\t\tif (firstChild?.type === 'paragraph' && firstChild.content) {\n\t\t\t\t\t\tlistItem.content = firstChild.content\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn node\n\t\t\t}\n\t\t}\n\t}\n\n\t// Recursively process child nodes\n\tif (node.content && Array.isArray(node.content)) {\n\t\tnode.content = node.content.map((child) => convertCheckboxesRecursive(child, blocks, cursor))\n\t}\n\n\treturn node\n}\n\n/**\n * Convert HTML details/summary blocks to ADF expand fence syntax\n * The extended-markdown-adf-parser library supports ~~~expand title=\"...\"~~~ syntax\n * but not HTML <details><summary> tags\n *\n * @param markdown - Markdown string potentially containing HTML details/summary blocks\n * @returns Markdown with details/summary converted to ADF expand fence syntax\n */\nexport function convertDetailsToExpandSyntax(markdown: string): string {\n\tif (!markdown) return markdown\n\n\t// Process from innermost to outermost to handle nesting correctly\n\tlet previousText = ''\n\tlet currentText = markdown\n\n\twhile (previousText !== currentText) {\n\t\tpreviousText = currentText\n\t\t// Match <details> blocks with optional attributes on the tags\n\t\tcurrentText = currentText.replace(\n\t\t\t/<details[^>]*>\\s*<summary[^>]*>([\\s\\S]*?)<\\/summary>([\\s\\S]*?)<\\/details>/gi,\n\t\t\t(_match, summary, content) => {\n\t\t\t\t// Clean up the summary - trim whitespace and decode HTML entities\n\t\t\t\tconst cleanSummary = summary\n\t\t\t\t\t.trim()\n\t\t\t\t\t.replace(/</g, '<')\n\t\t\t\t\t.replace(/>/g, '>')\n\t\t\t\t\t.replace(/&/g, '&')\n\t\t\t\t\t.replace(/"/g, '\"')\n\t\t\t\t\t.replace(/'/g, \"'\")\n\n\t\t\t\t// Clean up the content - trim and normalize excessive blank lines\n\t\t\t\tlet cleanContent = content.trim()\n\t\t\t\tcleanContent = cleanContent.replace(/\\n{3,}/g, '\\n\\n')\n\n\t\t\t\t// Build ADF expand fence syntax\n\t\t\t\tif (cleanContent) {\n\t\t\t\t\treturn `~~~expand title=\"${cleanSummary}\"\\n${cleanContent}\\n~~~`\n\t\t\t\t} else {\n\t\t\t\t\treturn `~~~expand title=\"${cleanSummary}\"\\n~~~`\n\t\t\t\t}\n\t\t\t}\n\t\t)\n\t}\n\n\treturn currentText\n}\n\n/**\n * Convert ADF (Atlassian Document Format) to Markdown\n * Used when reading issue descriptions and comments from Jira\n *\n * @param adf - ADF object, string, null, or undefined\n * @returns Markdown string\n */\nexport function adfToMarkdown(adf: unknown): string {\n\t// Handle null/undefined\n\tif (!adf) return ''\n\n\t// Handle plain string (already text, not ADF)\n\tif (typeof adf === 'string') return adf\n\n\t// Convert ADF object to markdown\n\treturn parser.adfToMarkdown(adf as ADFDocument)\n}\n\n/**\n * Convert Markdown to ADF (Atlassian Document Format)\n * Used when writing issue descriptions and comments to Jira\n *\n * @param markdown - Markdown string\n * @returns ADF object suitable for Jira API v3\n */\nexport function markdownToAdf(markdown: string): object {\n\tif (!markdown) {\n\t\treturn { type: 'doc', version: 1, content: [] }\n\t}\n\t// Reset task ID counter for deterministic output\n\ttaskIdCounter = 0\n\t// Extract checkbox info BEFORE conversion (library will strip [x]/[ ])\n\tconst checkboxBlocks = extractCheckboxBlocks(markdown)\n\t// Pre-process: convert details/summary to expand syntax\n\tconst preprocessed = convertDetailsToExpandSyntax(markdown)\n\tconst adf = parser.markdownToAdf(preprocessed)\n\t// Post-process the ADF tree\n\tlet result = sanitizeCodeMarks(adf as AdfNode)\n\tresult = wrapTableCellContent(result)\n\tresult = convertCheckboxesToTaskList(result, checkboxBlocks)\n\treturn result\n}\n","// JiraIssueTracker - Implements IssueTracker interface for Jira\n// Provides issue management operations via Jira REST API\n\nimport type { IssueTracker } from '../../IssueTracker.js'\nimport type { Issue, IssueTrackerInputDetection } from '../../../types/index.js'\nimport { JiraApiClient, type JiraConfig, type JiraIssue, type JiraTransition } from './JiraApiClient.js'\nimport { getLogger } from '../../../utils/logger-context.js'\nimport { promptConfirmation } from '../../../utils/prompt.js'\nimport { adfToMarkdown } from './AdfMarkdownConverter.js'\n\n/**\n * Jira-specific configuration\n */\nexport interface JiraTrackerConfig extends JiraConfig {\n\tprojectKey: string\n\ttransitionMappings?: Record<string, string> // Map iloom states to Jira transition names\n\tdefaultIssueType?: string // Default issue type for creating issues (e.g., \"Task\", \"Story\")\n\tdefaultSubtaskType?: string // Default issue type for creating subtasks (e.g., \"Subtask\", \"Sub-task\")\n}\n\n/**\n * JiraIssueTracker implements IssueTracker for Jira\n * \n * Key differences from GitHub/Linear:\n * - Issue identifiers are strings (e.g., \"PROJ-123\")\n * - No issue prefix (unlike GitHub's \"#\")\n * - State changes require workflow transitions (not direct status updates)\n * - Content uses Atlassian Document Format (ADF), converted to/from Markdown\n */\nexport class JiraIssueTracker implements IssueTracker {\n\treadonly providerName = 'jira'\n\treadonly supportsPullRequests = false\n\n\tprivate readonly client: JiraApiClient\n\tprivate readonly config: JiraTrackerConfig\n\tprivate prompter: (message: string) => Promise<boolean>\n\n\tconstructor(config: JiraTrackerConfig, options?: {\n\t\tprompter?: (message: string) => Promise<boolean>\n\t}) {\n\t\tthis.config = config\n\t\tthis.client = new JiraApiClient({\n\t\t\thost: config.host,\n\t\t\tusername: config.username,\n\t\t\tapiToken: config.apiToken,\n\t\t})\n\t\tthis.prompter = options?.prompter ?? promptConfirmation\n\t}\n\n\t/**\n\t * Normalize identifier to canonical uppercase form\n\t * Jira issue keys are case-sensitive in the API (must be uppercase)\n\t */\n\tnormalizeIdentifier(identifier: string | number): string {\n\t\treturn String(identifier).toUpperCase()\n\t}\n\n\t/**\n\t * Detect input type from user input\n\t * Jira issues follow pattern: PROJECTKEY-123 (case-insensitive)\n\t */\n\tasync detectInputType(input: string): Promise<IssueTrackerInputDetection> {\n\t\t// Pattern: PROJECTKEY-123 (case-insensitive to accept lowercase from branch names or user input)\n\t\tconst jiraPattern = /^([A-Z][A-Z0-9]+)-(\\d+)$/i\n\t\tconst match = input.match(jiraPattern)\n\n\t\tif (!match) {\n\t\t\treturn { type: 'unknown', identifier: null, rawInput: input }\n\t\t}\n\n\t\tconst issueKey = this.normalizeIdentifier(input)\n\t\tgetLogger().debug('Checking if input is a Jira issue', { issueKey })\n\n\t\t// Verify the issue exists\n\t\ttry {\n\t\t\tawait this.client.getIssue(issueKey)\n\t\t\treturn { type: 'issue', identifier: issueKey, rawInput: input }\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && (/404/.test(error.message) || /not found/i.test(error.message))) {\n\t\t\t\tgetLogger().debug('Issue not found', { issueKey, error })\n\t\t\t\treturn { type: 'unknown', identifier: null, rawInput: input }\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Fetch issue details\n\t */\n\tasync fetchIssue(identifier: string | number): Promise<Issue> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Fetching Jira issue', { issueKey })\n\n\t\tconst jiraIssue = await this.client.getIssue(issueKey)\n\t\treturn this.mapJiraIssueToIssue(jiraIssue)\n\t}\n\n\t/**\n\t * Check if issue exists (silent validation)\n\t */\n\tasync isValidIssue(identifier: string | number): Promise<Issue | false> {\n\t\ttry {\n\t\t\treturn await this.fetchIssue(identifier)\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && (/404/.test(error.message) || /not found/i.test(error.message))) {\n\t\t\t\tgetLogger().debug('Issue validation failed: not found', { identifier, error })\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Validate issue state\n\t * Note: Jira doesn't have a simple \"closed\" state - depends on workflow\n\t */\n\tasync validateIssueState(issue: Issue): Promise<void> {\n\t\tgetLogger().debug('Jira issue state', { issueKey: issue.number, state: issue.state })\n\t\tif (issue.state === 'closed') {\n\t\t\tconst shouldContinue = await this.prompter(\n\t\t\t\t`Issue ${issue.number} is in a completed state. Continue anyway?`\n\t\t\t)\n\t\t\tif (!shouldContinue) {\n\t\t\t\tthrow new Error('User cancelled due to completed issue')\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create a new issue\n\t */\n\tasync createIssue(\n\t\ttitle: string,\n\t\tbody: string,\n\t\t_repository?: string,\n\t\t_labels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\tgetLogger().debug('Creating Jira issue', { title, projectKey: this.config.projectKey })\n\n\t\t// Convert markdown body to plain text for Jira description\n\t\t// Note: Jira API expects Atlassian Document Format (ADF)\n\t\t// We use a simplified plain text approach here\n\t\tconst jiraIssue = await this.client.createIssue(\n\t\t\tthis.config.projectKey,\n\t\t\ttitle,\n\t\t\tbody,\n\t\t\tthis.config.defaultIssueType\n\t\t)\n\n\t\treturn {\n\t\t\tnumber: jiraIssue.key,\n\t\t\turl: `${this.config.host}/browse/${jiraIssue.key}`,\n\t\t}\n\t}\n\n\t/**\n\t * Get issue URL\n\t */\n\tasync getIssueUrl(identifier: string | number): Promise<string> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\treturn `${this.config.host}/browse/${issueKey}`\n\t}\n\n\t/**\n\t * Move issue to \"In Progress\" state\n\t * Uses configured transition mapping or default transition name\n\t */\n\tasync moveIssueToInProgress(identifier: string | number): Promise<void> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Moving Jira issue to In Progress', { issueKey })\n\n\t\t// Get available transitions\n\t\tconst transitions = await this.client.getTransitions(issueKey)\n\t\t\n\t\t// Look for the transition in config mapping or use default names\n\t\tconst transitionName = this.config.transitionMappings?.['In Progress'] \n\t\t\t?? this.findTransitionByName(transitions, ['In Progress', 'Start Progress', 'Start'])\n\n\t\tif (!transitionName) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not find \"In Progress\" transition for ${issueKey}. ` +\n\t\t\t\t`Available transitions: ${transitions.map(t => t.name).join(', ')}. ` +\n\t\t\t\t`Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`\n\t\t\t)\n\t\t}\n\n\t\t// Find transition ID\n\t\tconst transition = transitions.find(t => t.name === transitionName)\n\t\tif (!transition) {\n\t\t\tthrow new Error(`Transition \"${transitionName}\" not found`)\n\t\t}\n\n\t\tawait this.client.transitionIssue(issueKey, transition.id)\n\t\tgetLogger().info('Issue transitioned successfully', { issueKey, transition: transitionName })\n\t}\n\n\t/**\n\t * Move issue to \"Ready for Review\" state\n\t * Uses configured transition mapping or default transition name\n\t */\n\tasync moveIssueToReadyForReview(identifier: string | number): Promise<void> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Moving Jira issue to Ready for Review', { issueKey })\n\n\t\t// Get available transitions\n\t\tconst transitions = await this.client.getTransitions(issueKey)\n\n\t\t// Look for the transition in config mapping or use default names\n\t\tconst transitionName = this.config.transitionMappings?.['Ready for Review']\n\t\t\t?? this.findTransitionByName(transitions, ['Ready for Review', 'In Review', 'Code Review', 'Review'])\n\n\t\tif (!transitionName) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not find \"Ready for Review\" transition for ${issueKey}. ` +\n\t\t\t\t`Available transitions: ${transitions.map(t => t.name).join(', ')}. ` +\n\t\t\t\t`Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`\n\t\t\t)\n\t\t}\n\n\t\t// Find transition ID\n\t\tconst transition = transitions.find(t => t.name === transitionName)\n\t\tif (!transition) {\n\t\t\tthrow new Error(`Transition \"${transitionName}\" not found`)\n\t\t}\n\n\t\tawait this.client.transitionIssue(issueKey, transition.id)\n\t\tgetLogger().info('Issue transitioned to Ready for Review', { issueKey, transition: transitionName })\n\t}\n\n\t/**\n\t * Close an issue by transitioning to \"Done\" state\n\t * Uses configured transition mapping or default transition names\n\t */\n\tasync closeIssue(identifier: string | number): Promise<void> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Closing Jira issue', { issueKey })\n\n\t\t// Get available transitions\n\t\tconst transitions = await this.client.getTransitions(issueKey)\n\n\t\t// Look for the transition in config mapping or use default names\n\t\tconst transitionName = this.config.transitionMappings?.['Done']\n\t\t\t?? this.findTransitionByName(transitions, ['Done', 'Close', 'Closed', 'Resolve', 'Resolved'])\n\n\t\tif (!transitionName) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not find \"Done\" transition for ${issueKey}. ` +\n\t\t\t\t`Available transitions: ${transitions.map(t => t.name).join(', ')}. ` +\n\t\t\t\t`Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`\n\t\t\t)\n\t\t}\n\n\t\t// Find transition ID\n\t\tconst transition = transitions.find(t => t.name === transitionName)\n\t\tif (!transition) {\n\t\t\tthrow new Error(`Transition \"${transitionName}\" not found`)\n\t\t}\n\n\t\tawait this.client.transitionIssue(issueKey, transition.id)\n\t\tgetLogger().info('Issue closed successfully', { issueKey, transition: transitionName })\n\t}\n\n\t/**\n\t * Reopen an issue by transitioning back to an open state\n\t * Uses configured transition mapping or default transition names\n\t */\n\tasync reopenIssue(identifier: string | number): Promise<void> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Reopening Jira issue', { issueKey })\n\n\t\t// Get available transitions\n\t\tconst transitions = await this.client.getTransitions(issueKey)\n\n\t\t// Look for the transition in config mapping or use default names\n\t\tconst transitionName = this.config.transitionMappings?.['Reopen']\n\t\t\t?? this.findTransitionByName(transitions, ['Reopen', 'To Do', 'Open', 'Backlog'])\n\n\t\tif (!transitionName) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not find \"Reopen\" transition for ${issueKey}. ` +\n\t\t\t\t`Available transitions: ${transitions.map(t => t.name).join(', ')}. ` +\n\t\t\t\t`Configure custom mapping in settings.json: issueManagement.jira.transitionMappings`\n\t\t\t)\n\t\t}\n\n\t\t// Find transition ID\n\t\tconst transition = transitions.find(t => t.name === transitionName)\n\t\tif (!transition) {\n\t\t\tthrow new Error(`Transition \"${transitionName}\" not found`)\n\t\t}\n\n\t\tawait this.client.transitionIssue(issueKey, transition.id)\n\t\tgetLogger().info('Issue reopened successfully', { issueKey, transition: transitionName })\n\t}\n\n\t/**\n\t * Extract context from issue for AI prompts\n\t */\n\textractContext(entity: Issue): string {\n\t\treturn `Issue: ${entity.number}\nTitle: ${entity.title}\nStatus: ${entity.state}\nURL: ${entity.url}\n\nDescription:\n${entity.body}\n\n${entity.labels.length > 0 ? `Labels: ${entity.labels.join(', ')}` : ''}\n${entity.assignees.length > 0 ? `Assignees: ${entity.assignees.join(', ')}` : ''}`\n\t}\n\n\t/**\n\t * Fetch child issues of a Jira parent issue using JQL\n\t * @param parentIdentifier - Jira issue key (e.g., \"PROJ-123\")\n\t * @param _repo - Repository (unused for Jira)\n\t * @returns Array of child issues\n\t */\n\tasync getChildIssues(parentIdentifier: string, _repo?: string): Promise<Array<{ id: string; title: string; url: string; state: string }>> {\n\t\tconst parentKey = this.normalizeIdentifier(parentIdentifier)\n\t\tconst jiraKeyPattern = /^[A-Z][A-Z0-9]+-\\d+$/\n\t\tif (!jiraKeyPattern.test(parentKey)) {\n\t\t\tgetLogger().warn(`Invalid Jira issue key format: ${parentKey}`)\n\t\t\treturn []\n\t\t}\n\t\tconst issues = await this.client.searchIssues(`parent = ${parentKey}`)\n\t\treturn issues.map(issue => ({\n\t\t\tid: issue.key,\n\t\t\ttitle: issue.fields.summary,\n\t\t\turl: `${this.config.host}/browse/${issue.key}`,\n\t\t\tstate: issue.fields.status.name.toLowerCase(),\n\t\t}))\n\t}\n\n\t/**\n\t * Get issue details (alias for fetchIssue for MCP compatibility)\n\t */\n\tasync getIssue(identifier: string | number): Promise<Issue> {\n\t\treturn this.fetchIssue(identifier)\n\t}\n\n\t/**\n\t * Get all comments for an issue\n\t */\n\tasync getComments(identifier: string | number): Promise<Array<{\n\t\tid: string\n\t\tbody: string\n\t\tauthor: { displayName: string; emailAddress: string; accountId: string }\n\t\tcreatedAt: string\n\t\tupdatedAt: string\n\t}>> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Fetching Jira comments', { issueKey })\n\n\t\tconst comments = await this.client.getComments(issueKey)\n\t\t\n\t\t// Map to expected format\n\t\treturn comments.map(comment => ({\n\t\t\tid: comment.id,\n\t\t\tbody: adfToMarkdown(comment.body),\n\t\t\tauthor: comment.author,\n\t\t\tcreatedAt: comment.created,\n\t\t\tupdatedAt: comment.updated,\n\t\t}))\n\t}\n\n\t/**\n\t * Add a comment to an issue\n\t */\n\tasync addComment(identifier: string | number, body: string): Promise<{ id: string }> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Adding Jira comment', { issueKey })\n\n\t\tconst comment = await this.client.addComment(issueKey, body)\n\t\treturn { id: comment.id }\n\t}\n\n\t/**\n\t * Update an existing comment\n\t */\n\tasync updateComment(identifier: string | number, commentId: string, body: string): Promise<void> {\n\t\tconst issueKey = this.normalizeIdentifier(identifier)\n\t\tgetLogger().debug('Updating Jira comment', { issueKey, commentId })\n\n\t\tawait this.client.updateComment(issueKey, commentId, body)\n\t}\n\n\t/**\n\t * Get the underlying API client (for direct API access by MCP provider)\n\t */\n\tgetApiClient(): JiraApiClient {\n\t\treturn this.client\n\t}\n\n\t/**\n\t * Get configuration (for MCP provider)\n\t */\n\tgetConfig(): JiraTrackerConfig {\n\t\treturn this.config\n\t}\n\n\t/**\n\t * Map Jira API issue to generic Issue type\n\t */\n\tprivate mapJiraIssueToIssue(jiraIssue: JiraIssue): Issue & {\n\t\tid?: string\n\t\tkey?: string\n\t\tauthor?: {\n\t\t\tdisplayName: string\n\t\t\temailAddress: string\n\t\t\taccountId: string\n\t\t}\n\t\tassignee?: {\n\t\t\tdisplayName: string\n\t\t\temailAddress: string\n\t\t\taccountId: string\n\t\t} | null\n\t\tissueType?: string\n\t\tstatus?: string\n\t} {\n\t\t// Extract description - handle ADF format or plain string\n\t\tconst description = adfToMarkdown(jiraIssue.fields.description)\n\n\t\treturn {\n\t\t\tid: jiraIssue.id,\n\t\t\tkey: jiraIssue.key,\n\t\t\tnumber: jiraIssue.key,\n\t\t\ttitle: jiraIssue.fields.summary,\n\t\t\tbody: description,\n\t\t\tstate: this.mapJiraStatusToState(jiraIssue.fields.status.name),\n\t\t\tlabels: jiraIssue.fields.labels,\n\t\t\tassignees: jiraIssue.fields.assignee \n\t\t\t\t? [jiraIssue.fields.assignee.displayName]\n\t\t\t\t: [],\n\t\t\tassignee: jiraIssue.fields.assignee,\n\t\t\tauthor: jiraIssue.fields.reporter,\n\t\t\turl: `${this.config.host}/browse/${jiraIssue.key}`,\n\t\t\tissueType: jiraIssue.fields.issuetype.name,\n\t\t\tstatus: jiraIssue.fields.status.name,\n\t\t}\n\t}\n\n\tprivate mapJiraStatusToState(statusName: string): 'open' | 'closed' {\n\t\tconst normalized = statusName.toLowerCase()\n\t\tconst closedStatuses = ['done', 'closed', 'resolved', 'cancelled', 'canceled']\n\t\treturn closedStatuses.includes(normalized) ? 'closed' : 'open'\n\t}\n\n\t/**\n\t * Find a transition by name, trying multiple possible names\n\t */\n\tprivate findTransitionByName(transitions: JiraTransition[], names: string[]): string | null {\n\t\tfor (const name of names) {\n\t\t\tconst transition = transitions.find(t => \n\t\t\t\tt.name.toLowerCase() === name.toLowerCase()\n\t\t\t)\n\t\t\tif (transition) {\n\t\t\t\treturn transition.name\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n"],"mappings":"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","names":["issue","_a"]}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isPRBranch,
|
|
12
12
|
isValidGitRepo,
|
|
13
13
|
parseWorktreeList
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-4FGEGQW4.js";
|
|
15
15
|
import {
|
|
16
16
|
getLogger
|
|
17
17
|
} from "./chunk-6MLEBAYZ.js";
|
|
@@ -388,4 +388,4 @@ var GitWorktreeManager = class {
|
|
|
388
388
|
export {
|
|
389
389
|
GitWorktreeManager
|
|
390
390
|
};
|
|
391
|
-
//# sourceMappingURL=chunk-
|
|
391
|
+
//# sourceMappingURL=chunk-I5T677EA.js.map
|
|
@@ -22,6 +22,7 @@ var MetadataManager = class {
|
|
|
22
22
|
branchName: data.branchName ?? null,
|
|
23
23
|
worktreePath: data.worktreePath ?? null,
|
|
24
24
|
issueType: data.issueType ?? null,
|
|
25
|
+
issueKey: data.issueKey ?? null,
|
|
25
26
|
issue_numbers: data.issue_numbers ?? [],
|
|
26
27
|
pr_numbers: data.pr_numbers ?? [],
|
|
27
28
|
issueTracker: data.issueTracker ?? null,
|
|
@@ -33,7 +34,12 @@ var MetadataManager = class {
|
|
|
33
34
|
draftPrNumber: data.draftPrNumber ?? null,
|
|
34
35
|
oneShot: data.oneShot ?? null,
|
|
35
36
|
capabilities: data.capabilities ?? [],
|
|
36
|
-
|
|
37
|
+
state: data.state ?? null,
|
|
38
|
+
childIssueNumbers: data.childIssueNumbers ?? [],
|
|
39
|
+
parentLoom: data.parentLoom ?? null,
|
|
40
|
+
childIssues: data.childIssues ?? [],
|
|
41
|
+
dependencyMap: data.dependencyMap ?? {},
|
|
42
|
+
mcpConfigPath: data.mcpConfigPath ?? null
|
|
37
43
|
};
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
@@ -86,6 +92,7 @@ var MetadataManager = class {
|
|
|
86
92
|
branchName: input.branchName,
|
|
87
93
|
worktreePath: input.worktreePath,
|
|
88
94
|
issueType: input.issueType,
|
|
95
|
+
...input.issueKey && { issueKey: input.issueKey },
|
|
89
96
|
issue_numbers: input.issue_numbers,
|
|
90
97
|
pr_numbers: input.pr_numbers,
|
|
91
98
|
issueTracker: input.issueTracker,
|
|
@@ -97,7 +104,12 @@ var MetadataManager = class {
|
|
|
97
104
|
capabilities: input.capabilities,
|
|
98
105
|
...input.draftPrNumber && { draftPrNumber: input.draftPrNumber },
|
|
99
106
|
...input.oneShot && { oneShot: input.oneShot },
|
|
100
|
-
...input.
|
|
107
|
+
...input.state && { state: input.state },
|
|
108
|
+
...input.childIssueNumbers && input.childIssueNumbers.length > 0 && { childIssueNumbers: input.childIssueNumbers },
|
|
109
|
+
...input.parentLoom && { parentLoom: input.parentLoom },
|
|
110
|
+
...input.childIssues && input.childIssues.length > 0 && { childIssues: input.childIssues },
|
|
111
|
+
...input.dependencyMap && Object.keys(input.dependencyMap).length > 0 && { dependencyMap: input.dependencyMap },
|
|
112
|
+
...input.mcpConfigPath && { mcpConfigPath: input.mcpConfigPath }
|
|
101
113
|
};
|
|
102
114
|
const filePath = this.getFilePath(worktreePath);
|
|
103
115
|
await fs.writeFile(filePath, JSON.stringify(content, null, 2), { mode: 420 });
|
|
@@ -173,6 +185,34 @@ var MetadataManager = class {
|
|
|
173
185
|
}
|
|
174
186
|
return results;
|
|
175
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Update existing metadata for a worktree by merging new fields
|
|
190
|
+
*
|
|
191
|
+
* Reads the existing metadata file, merges the provided updates,
|
|
192
|
+
* and writes back. Only provided fields are overwritten.
|
|
193
|
+
*
|
|
194
|
+
* @param worktreePath - Absolute path to the worktree
|
|
195
|
+
* @param updates - Partial metadata fields to merge
|
|
196
|
+
*/
|
|
197
|
+
async updateMetadata(worktreePath, updates) {
|
|
198
|
+
try {
|
|
199
|
+
const filePath = this.getFilePath(worktreePath);
|
|
200
|
+
if (!await fs.pathExists(filePath)) {
|
|
201
|
+
getLogger().warn(`No metadata file to update for worktree: ${worktreePath}`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
205
|
+
const data = JSON.parse(content);
|
|
206
|
+
const merged = { ...data, ...updates };
|
|
207
|
+
await fs.writeFile(filePath, JSON.stringify(merged, null, 2), { mode: 420 });
|
|
208
|
+
getLogger().debug(`Metadata updated for worktree: ${worktreePath}`);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
getLogger().warn(
|
|
211
|
+
`Failed to update metadata for worktree: ${error instanceof Error ? error.message : String(error)}`
|
|
212
|
+
);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
176
216
|
/**
|
|
177
217
|
* Delete metadata for a worktree (spec section 3.3)
|
|
178
218
|
*
|
|
@@ -287,4 +327,4 @@ var MetadataManager = class {
|
|
|
287
327
|
export {
|
|
288
328
|
MetadataManager
|
|
289
329
|
};
|
|
290
|
-
//# sourceMappingURL=chunk-
|
|
330
|
+
//# sourceMappingURL=chunk-KB64WNBZ.js.map
|
|
@@ -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\nexport type SwarmState = 'pending' | 'in_progress' | 'code_review' | 'done' | 'failed'\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' | 'epic'\n issueKey?: string // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\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 state?: SwarmState // Swarm mode lifecycle state\n childIssueNumbers?: string[] // Child issue numbers for epic looms\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n // Epic/swarm child issue data (populated during spin setup)\n childIssues?: Array<{\n number: string // Prefixed: \"#123\" for GitHub, \"ENG-123\" for Linear\n title: string\n body: string\n url: string\n }>\n dependencyMap?: Record<string, string[]> // issueNumber -> array of blocking issueNumbers\n mcpConfigPath?: string // Path to per-loom MCP config file (for swarm claude -p commands)\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' | 'epic'\n issueKey?: string // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\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 state?: SwarmState // Swarm mode lifecycle state\n childIssueNumbers?: string[] // Child issue numbers for epic looms\n parentLoom?: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n }\n // Epic/swarm child issue data (populated during spin setup)\n childIssues?: Array<{\n number: string // Prefixed: \"#123\" for GitHub, \"ENG-123\" for Linear\n title: string\n body: string\n url: string\n }>\n dependencyMap?: Record<string, string[]> // issueNumber -> array of blocking issueNumbers\n mcpConfigPath?: string // Path to per-loom MCP config file (for swarm claude -p commands)\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' | 'epic' | null\n issueKey: string | null // Canonical, properly-cased issue key (e.g., \"PROJ-123\")\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 state: SwarmState | null // Swarm mode lifecycle state (null for non-swarm looms)\n childIssueNumbers: string[] // Child issue numbers for epic looms (empty for non-epic looms)\n parentLoom: {\n type: 'issue' | 'pr' | 'branch' | 'epic'\n identifier: string | number\n branchName: string\n worktreePath: string\n databaseBranch?: string\n } | null\n // Epic/swarm child issue data (empty arrays/objects for non-epic looms)\n childIssues: Array<{\n number: string\n title: string\n body: string\n url: string\n }>\n dependencyMap: Record<string, string[]>\n mcpConfigPath: string | null // Path to per-loom MCP config file (null for non-swarm looms)\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 issueKey: data.issueKey ?? 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 state: data.state ?? null,\n childIssueNumbers: data.childIssueNumbers ?? [],\n parentLoom: data.parentLoom ?? null,\n childIssues: data.childIssues ?? [],\n dependencyMap: data.dependencyMap ?? {},\n mcpConfigPath: data.mcpConfigPath ?? 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 ...(input.issueKey && { issueKey: input.issueKey }),\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.state && { state: input.state }),\n ...(input.childIssueNumbers && input.childIssueNumbers.length > 0 && { childIssueNumbers: input.childIssueNumbers }),\n ...(input.parentLoom && { parentLoom: input.parentLoom }),\n ...(input.childIssues && input.childIssues.length > 0 && { childIssues: input.childIssues }),\n ...(input.dependencyMap && Object.keys(input.dependencyMap).length > 0 && { dependencyMap: input.dependencyMap }),\n ...(input.mcpConfigPath && { mcpConfigPath: input.mcpConfigPath }),\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 * Update existing metadata for a worktree by merging new fields\n *\n * Reads the existing metadata file, merges the provided updates,\n * and writes back. Only provided fields are overwritten.\n *\n * @param worktreePath - Absolute path to the worktree\n * @param updates - Partial metadata fields to merge\n */\n async updateMetadata(worktreePath: string, updates: Partial<MetadataFile>): Promise<void> {\n try {\n const filePath = this.getFilePath(worktreePath)\n\n // Check if file exists\n if (!(await fs.pathExists(filePath))) {\n getLogger().warn(`No metadata file to update for worktree: ${worktreePath}`)\n return\n }\n\n // Read existing data\n const content = await fs.readFile(filePath, 'utf8')\n const data: MetadataFile = JSON.parse(content)\n\n // Merge updates\n const merged = { ...data, ...updates }\n\n // Write back\n await fs.writeFile(filePath, JSON.stringify(merged, null, 2), { mode: 0o644 })\n\n getLogger().debug(`Metadata updated for worktree: ${worktreePath}`)\n } catch (error) {\n getLogger().warn(\n `Failed to update metadata for worktree: ${error instanceof Error ? error.message : String(error)}`\n )\n throw error\n }\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":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
GitWorktreeManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-I5T677EA.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-VT4PDUYT.js";
|
|
@@ -299,4 +299,4 @@ Please consult your shell's documentation for setting up custom completions.
|
|
|
299
299
|
export {
|
|
300
300
|
ShellCompletion
|
|
301
301
|
};
|
|
302
|
-
//# sourceMappingURL=chunk-
|
|
302
|
+
//# sourceMappingURL=chunk-KIK2ZFAL.js.map
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateIssueManagementMcpConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SKSYYBCU.js";
|
|
5
5
|
import {
|
|
6
6
|
openBrowser
|
|
7
7
|
} from "./chunk-YETJNRQM.js";
|
|
8
|
-
import {
|
|
9
|
-
waitForKeypress
|
|
10
|
-
} from "./chunk-7JDMYTFZ.js";
|
|
11
8
|
import {
|
|
12
9
|
launchClaude
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UR5DGNUO.js";
|
|
14
11
|
import {
|
|
15
12
|
getLogger
|
|
16
13
|
} from "./chunk-6MLEBAYZ.js";
|
|
14
|
+
import {
|
|
15
|
+
waitForKeypress
|
|
16
|
+
} from "./chunk-7JDMYTFZ.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/IssueEnhancementService.ts
|
|
19
19
|
var IssueEnhancementService = class {
|
|
@@ -53,24 +53,34 @@ var IssueEnhancementService = class {
|
|
|
53
53
|
try {
|
|
54
54
|
getLogger().info("Enhancing description with Claude Code. This may take a moment...");
|
|
55
55
|
const settings = await this.settingsManager.loadSettings();
|
|
56
|
-
const
|
|
56
|
+
const templateVariables = {
|
|
57
|
+
STANDARD_ISSUE_MODE: true,
|
|
58
|
+
DIRECT_PROMPT_MODE: true
|
|
59
|
+
};
|
|
60
|
+
const loadedAgents = await this.agentManager.loadAgents(
|
|
61
|
+
settings,
|
|
62
|
+
templateVariables,
|
|
63
|
+
["iloom-issue-enhancer.md"]
|
|
64
|
+
);
|
|
57
65
|
const agents = this.agentManager.formatForCli(loadedAgents);
|
|
58
66
|
const prompt = `@agent-iloom-issue-enhancer
|
|
59
67
|
|
|
60
|
-
TASK: Enhance the following issue description for
|
|
68
|
+
TASK: Enhance the following issue description for the issue tracker.
|
|
61
69
|
|
|
62
70
|
INPUT:
|
|
63
71
|
${description}
|
|
64
72
|
|
|
65
73
|
OUTPUT REQUIREMENTS:
|
|
66
74
|
- Return ONLY the enhanced description markdown text
|
|
75
|
+
- Use GitHub-Flavored Markdown syntax ONLY
|
|
76
|
+
- NEVER use Jira Wiki format (e.g., {code}, h1., *bold*, {quote}, [link|url])
|
|
67
77
|
- NO meta-commentary (no "Here is...", "The enhanced...", "I have...", etc)
|
|
68
78
|
- NO code block markers (\`\`\`)
|
|
69
79
|
- NO conversational framing or acknowledgments
|
|
70
80
|
- NO explanations of your work
|
|
71
81
|
- Start your response immediately with the enhanced content
|
|
72
82
|
|
|
73
|
-
Your response should be the raw markdown that will become the
|
|
83
|
+
Your response should be the raw markdown that will become the issue body.`;
|
|
74
84
|
const enhanced = await launchClaude(prompt, {
|
|
75
85
|
headless: true,
|
|
76
86
|
model: "sonnet",
|
|
@@ -98,7 +108,7 @@ Your response should be the raw markdown that will become the GitHub issue body.
|
|
|
98
108
|
* @returns Issue number and URL
|
|
99
109
|
*/
|
|
100
110
|
async createEnhancedIssue(originalDescription, enhancedDescription, repository, labels) {
|
|
101
|
-
getLogger().info("Creating
|
|
111
|
+
getLogger().info("Creating issue from description...");
|
|
102
112
|
const result = await this.issueTrackerService.createIssue(
|
|
103
113
|
originalDescription,
|
|
104
114
|
// Use original description as title
|
|
@@ -144,7 +154,15 @@ Press any key to open issue for editing...`;
|
|
|
144
154
|
async enhanceExistingIssue(issueNumber, options) {
|
|
145
155
|
const { author, repo } = options ?? {};
|
|
146
156
|
const settings = await this.settingsManager.loadSettings();
|
|
147
|
-
const
|
|
157
|
+
const templateVariables = {
|
|
158
|
+
ISSUE_NUMBER: issueNumber,
|
|
159
|
+
STANDARD_ISSUE_MODE: true
|
|
160
|
+
};
|
|
161
|
+
const loadedAgents = await this.agentManager.loadAgents(
|
|
162
|
+
settings,
|
|
163
|
+
templateVariables,
|
|
164
|
+
["iloom-issue-enhancer.md"]
|
|
165
|
+
);
|
|
148
166
|
const agents = this.agentManager.formatForCli(loadedAgents);
|
|
149
167
|
let mcpConfig;
|
|
150
168
|
let allowedTools;
|
|
@@ -222,26 +240,7 @@ IMPORTANT: When you create your analysis comment, tag @${author} in the "Questio
|
|
|
222
240
|
}
|
|
223
241
|
};
|
|
224
242
|
|
|
225
|
-
// src/utils/text.ts
|
|
226
|
-
function capitalizeFirstLetter(str) {
|
|
227
|
-
if (!str || str.length === 0) {
|
|
228
|
-
return str;
|
|
229
|
-
}
|
|
230
|
-
if (str.startsWith(" ")) {
|
|
231
|
-
return str.slice(1);
|
|
232
|
-
}
|
|
233
|
-
const firstChar = str.charAt(0);
|
|
234
|
-
const upperChar = firstChar.toUpperCase();
|
|
235
|
-
if (upperChar !== firstChar.toLowerCase() || new RegExp("\\p{L}", "u").test(firstChar)) {
|
|
236
|
-
if (upperChar !== firstChar) {
|
|
237
|
-
return upperChar + str.slice(1);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return str;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
243
|
export {
|
|
244
|
-
IssueEnhancementService
|
|
245
|
-
capitalizeFirstLetter
|
|
244
|
+
IssueEnhancementService
|
|
246
245
|
};
|
|
247
|
-
//# sourceMappingURL=chunk-
|
|
246
|
+
//# sourceMappingURL=chunk-KKV5WH5M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/IssueEnhancementService.ts"],"sourcesContent":["import type { IssueTracker } from './IssueTracker.js'\nimport type { AgentManager } from './AgentManager.js'\nimport type { SettingsManager } from './SettingsManager.js'\nimport type { TemplateVariables } from './PromptTemplateManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport { generateIssueManagementMcpConfig } from '../utils/mcp.js'\n\n/**\n * Options for enhancing an existing issue\n */\nexport interface EnhanceExistingIssueOptions {\n\t/** GitHub username of issue author for tagging in questions */\n\tauthor?: string\n\t/** Repository in \"owner/repo\" format */\n\trepo?: string\n}\n\n/**\n * Result of enhancing an existing issue\n */\nexport interface EnhanceExistingIssueResult {\n\t/** Whether the issue was enhanced */\n\tenhanced: boolean\n\t/** URL of the comment if enhancement occurred */\n\turl?: string\n}\n\n/**\n * Service for enhancing and creating issues with AI assistance.\n * Extracts reusable issue enhancement logic from StartCommand.\n */\nexport class IssueEnhancementService {\n\tconstructor(\n\t\tprivate issueTrackerService: IssueTracker,\n\t\tprivate agentManager: AgentManager,\n\t\tprivate settingsManager: SettingsManager\n\t) {\n\t\t// No-op - logger now uses AsyncLocalStorage context\n\t}\n\n\t/**\n\t * Expose issue tracker for provider checks\n\t */\n\tpublic get issueTracker(): IssueTracker {\n\t\treturn this.issueTrackerService\n\t}\n\n\t/**\n\t * Validates that a description meets minimum requirements.\n\t *\n\t * When hasBody is false (default): Strict validation - >30 characters AND >2 spaces\n\t * When hasBody is true: Relaxed validation - only requires non-empty description\n\t *\n\t * @param description - The description text to validate\n\t * @param hasBody - If true, skip strict validation (only require non-empty)\n\t */\n\tpublic validateDescription(description: string, hasBody = false): boolean {\n\t\tconst trimmedDescription = description.trim()\n\n\t\t// When --body is provided, only require non-empty description\n\t\tif (hasBody) {\n\t\t\treturn trimmedDescription.length > 0\n\t\t}\n\n\t\t// Standard validation: >30 chars AND >2 spaces\n\t\tconst spaceCount = (trimmedDescription.match(/ /g) ?? []).length\n\t\treturn trimmedDescription.length > 30 && spaceCount > 2\n\t}\n\n\t/**\n\t * Enhances a description using Claude Code in headless mode.\n\t * Falls back to original description if enhancement fails.\n\t */\n\tpublic async enhanceDescription(description: string): Promise<string> {\n\t\ttry {\n\t\t\tgetLogger().info('Enhancing description with Claude Code. This may take a moment...')\n\n\t\t\t// Load only the enhancer agent with template variables so Handlebars expressions resolve\n\t\t\tconst settings = await this.settingsManager.loadSettings()\n\t\t\tconst templateVariables: TemplateVariables = {\n\t\t\t\tSTANDARD_ISSUE_MODE: true,\n\t\t\t\tDIRECT_PROMPT_MODE: true,\n\t\t\t}\n\t\t\tconst loadedAgents = await this.agentManager.loadAgents(\n\t\t\t\tsettings,\n\t\t\t\ttemplateVariables,\n\t\t\t\t['iloom-issue-enhancer.md']\n\t\t\t)\n\t\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t\t// Call Claude in headless mode with issue enhancer agent\n\t\t\tconst prompt = `@agent-iloom-issue-enhancer\n\nTASK: Enhance the following issue description for the issue tracker.\n\nINPUT:\n${description}\n\nOUTPUT REQUIREMENTS:\n- Return ONLY the enhanced description markdown text\n- Use GitHub-Flavored Markdown syntax ONLY\n- NEVER use Jira Wiki format (e.g., {code}, h1., *bold*, {quote}, [link|url])\n- NO meta-commentary (no \"Here is...\", \"The enhanced...\", \"I have...\", etc)\n- NO code block markers (\\`\\`\\`)\n- NO conversational framing or acknowledgments\n- NO explanations of your work\n- Start your response immediately with the enhanced content\n\nYour response should be the raw markdown that will become the issue body.`\n\n\t\t\tconst enhanced = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: 'sonnet',\n\t\t\t\tagents,\n\t\t\t\tnoSessionPersistence: true, // Utility operation - don't persist session\n\t\t\t})\n\n\t\t\tif (enhanced && typeof enhanced === 'string') {\n\t\t\t\tgetLogger().success('Description enhanced successfully')\n\t\t\t\treturn enhanced\n\t\t\t}\n\n\t\t\t// Fallback to original description\n\t\t\tgetLogger().warn('Claude enhancement returned empty result, using original description')\n\t\t\treturn description\n\t\t} catch (error) {\n\t\t\tgetLogger().warn(`Failed to enhance description: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\treturn description\n\t\t}\n\t}\n\n\t/**\n\t * Creates a GitHub issue with title and enhanced body.\n\t * @param originalDescription - Used as the issue title\n\t * @param enhancedDescription - Used as the issue body\n\t * @param repository - Optional repository override (format: \"owner/repo\")\n\t * @param labels - Optional array of label names to add to the issue\n\t * @returns Issue number and URL\n\t */\n\tpublic async createEnhancedIssue(\n\t\toriginalDescription: string,\n\t\tenhancedDescription: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\tgetLogger().info('Creating issue from description...')\n\n\t\tconst result = await this.issueTrackerService.createIssue(\n\t\t\toriginalDescription, // Use original description as title\n\t\t\tenhancedDescription, // Use enhanced description as body\n\t\t\trepository,\n\t\t\tlabels\n\t\t)\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Waits for user keypress and opens issue in browser for review.\n\t * @param issueNumber - Issue number to open for review\n\t * @param confirm - If true, wait for additional keypress after opening browser before returning\n\t * @param repository - Optional repository to fetch issue from (format: \"owner/repo\")\n\t */\n\tpublic async waitForReviewAndOpen(issueNumber: string | number, confirm = false, repository?: string): Promise<void> {\n\t\t// Check if running in non-interactive environment (CI or no TTY)\n\t\tconst isCI = process.env.CI === 'true'\n\t\tconst isNonInteractive = isCI || !process.stdin.isTTY\n\n\t\tif (isNonInteractive) {\n\t\t\t// In non-interactive environment: Skip all interactive operations\n\t\t\tgetLogger().info(`Running in non-interactive environment - skipping interactive prompts for issue #${issueNumber}`)\n\t\t\treturn\n\t\t}\n\n\t\t// Get issue URL\n\t\tconst issueUrl = await this.issueTrackerService.getIssueUrl(issueNumber, repository)\n\n\t\t// Display message and wait for first keypress\n\t\tconst message = `Created issue #${issueNumber}.\nReview and edit the issue in your browser if needed.\nPress any key to open issue for editing...`\n\t\tawait waitForKeypress(message)\n\n\t\t// Open issue in browser\n\t\tawait openBrowser(issueUrl)\n\n\t\t// If confirmation required, wait for second keypress\n\t\tif (confirm) {\n\t\t\tawait waitForKeypress('Press any key to continue with loom creation...')\n\t\t}\n\t}\n\n\t/**\n\t * Enhances an existing issue using the issue enhancer agent.\n\t * This method encapsulates the Claude invocation, MCP config generation,\n\t * and response parsing for enhancing existing issues.\n\t *\n\t * @param issueNumber - The issue number to enhance\n\t * @param options - Optional enhancement options (author, repo)\n\t * @returns Result indicating whether enhancement occurred and the comment URL if so\n\t */\n\tpublic async enhanceExistingIssue(\n\t\tissueNumber: string | number,\n\t\toptions?: EnhanceExistingIssueOptions\n\t): Promise<EnhanceExistingIssueResult> {\n\t\tconst { author, repo } = options ?? {}\n\n\t\t// Load only the enhancer agent with template variables so Handlebars expressions resolve\n\t\tconst settings = await this.settingsManager.loadSettings()\n\t\tconst templateVariables: TemplateVariables = {\n\t\t\tISSUE_NUMBER: issueNumber,\n\t\t\tSTANDARD_ISSUE_MODE: true,\n\t\t}\n\t\tconst loadedAgents = await this.agentManager.loadAgents(\n\t\t\tsettings,\n\t\t\ttemplateVariables,\n\t\t\t['iloom-issue-enhancer.md']\n\t\t)\n\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t// Generate MCP config and tool filtering for issue management\n\t\tlet mcpConfig: Record<string, unknown>[] | undefined\n\t\tlet allowedTools: string[] | undefined\n\t\tlet disallowedTools: string[] | undefined\n\n\t\ttry {\n\t\t\tconst provider = this.issueTrackerService.providerName as 'github' | 'linear'\n\t\t\tmcpConfig = await generateIssueManagementMcpConfig('issue', repo, provider, settings)\n\t\t\tgetLogger().debug('Generated MCP configuration for issue management:', { mcpConfig })\n\n\t\t\t// Configure tool filtering for issue workflows\n\t\t\tallowedTools = [\n\t\t\t\t'mcp__issue_management__get_issue',\n\t\t\t\t'mcp__issue_management__get_comment',\n\t\t\t\t'mcp__issue_management__create_comment',\n\t\t\t\t'mcp__issue_management__update_comment',\n\t\t\t\t'mcp__issue_management__create_issue',\n\t\t\t]\n\t\t\tdisallowedTools = ['Bash(gh api:*)']\n\n\t\t\tgetLogger().debug('Configured tool filtering for issue workflow', { allowedTools, disallowedTools })\n\t\t} catch (error) {\n\t\t\t// Log warning but continue without MCP\n\t\t\tgetLogger().warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t}\n\n\t\t// Construct prompt for the orchestrating Claude instance\n\t\tconst prompt = this.constructEnhancerPrompt(issueNumber, author)\n\n\t\t// Invoke Claude CLI with enhancer agent\n\t\tconst response = await launchClaude(prompt, {\n\t\t\theadless: true,\n\t\t\tmodel: 'sonnet',\n\t\t\tagents,\n\t\t\tnoSessionPersistence: true, // Headless operation - no session persistence needed\n\t\t\t...(mcpConfig && { mcpConfig }),\n\t\t\t...(allowedTools && { allowedTools }),\n\t\t\t...(disallowedTools && { disallowedTools }),\n\t\t})\n\n\t\t// Parse response to determine outcome\n\t\treturn this.parseEnhancerResponse(response)\n\t}\n\n\t/**\n\t * Construct the prompt for the orchestrating Claude instance.\n\t * This prompt is very clear about expected output format to ensure reliable parsing.\n\t */\n\tprivate constructEnhancerPrompt(issueNumber: string | number, author?: string): string {\n\t\tconst authorInstruction = author\n\t\t\t? `\\nIMPORTANT: When you create your analysis comment, tag @${author} in the \"Questions for Reporter\" section if you have questions.\\n`\n\t\t\t: ''\n\n\t\treturn `Execute @agent-iloom-issue-enhancer ${issueNumber}${authorInstruction}\n\n## OUTPUT REQUIREMENTS\n* If the issue was not enhanced, return ONLY: \"No enhancement needed\"\n* If the issue WAS enhanced, return ONLY: <FULL URL OF THE COMMENT INCLUDING COMMENT ID>\n* If you encounter permission/authentication/access errors, return ONLY: \"Permission denied: <specific error description>\"\n* IMPORTANT: Return ONLY one of the above - DO NOT include commentary such as \"I created a comment at <URL>\" or \"I examined the issue and found no enhancement was necessary\"\n* CONTEXT: Your output is going to be parsed programmatically, so adherence to the output requirements is CRITICAL.`\n\t}\n\n\t/**\n\t * Parse the response from the enhancer agent.\n\t * Returns either { enhanced: false } or { enhanced: true, url: \"...\" }\n\t * Throws specific errors for permission issues.\n\t */\n\tprivate parseEnhancerResponse(response: string | void): EnhanceExistingIssueResult {\n\t\t// Handle empty or void response\n\t\tif (!response || typeof response !== 'string') {\n\t\t\tthrow new Error('No response from enhancer agent')\n\t\t}\n\n\t\tconst trimmed = response.trim()\n\n\t\tgetLogger().debug(`RESPONSE FROM ENHANCER AGENT: '${trimmed}'`)\n\n\t\t// Check for permission denied errors (case-insensitive)\n\t\tif (trimmed.toLowerCase().startsWith('permission denied:')) {\n\t\t\tconst errorMessage = trimmed.substring('permission denied:'.length).trim()\n\t\t\tthrow new Error(`Permission denied: ${errorMessage}`)\n\t\t}\n\n\t\t// Check for \"No enhancement needed\" (case-insensitive)\n\t\tif (trimmed.toLowerCase().includes('no enhancement needed')) {\n\t\t\treturn { enhanced: false }\n\t\t}\n\n\t\t// Check if response looks like a GitHub comment URL\n\t\tconst urlPattern = /https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/\\d+#issuecomment-\\d+/\n\t\tconst match = trimmed.match(urlPattern)\n\n\t\tif (match) {\n\t\t\treturn { enhanced: true, url: match[0] }\n\t\t}\n\n\t\t// Unexpected response format\n\t\tthrow new Error(`Unexpected response from enhancer agent: ${trimmed}`)\n\t}\n}\n"],"mappings":"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","names":[]}
|