@iloom/cli 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +37 -6
  2. package/dist/{ClaudeContextManager-LD3VB6EM.js → ClaudeContextManager-JKR4WGNU.js} +4 -4
  3. package/dist/{ClaudeService-CFFI7DD5.js → ClaudeService-55DQGB7T.js} +3 -3
  4. package/dist/{GitHubService-SH4H6VS5.js → GitHubService-LWP4GKGH.js} +3 -3
  5. package/dist/{LoomLauncher-FB2MV2ZI.js → LoomLauncher-UMMLPIZO.js} +4 -4
  6. package/dist/{PromptTemplateManager-WM5GIPEF.js → PromptTemplateManager-WII75TKH.js} +2 -2
  7. package/dist/README.md +755 -0
  8. package/dist/{add-issue-L5HX6LEV.js → add-issue-X56V3XPB.js} +3 -3
  9. package/dist/{chunk-SLIMABFA.js → chunk-DEPYQRRB.js} +2 -2
  10. package/dist/{chunk-HURVAQRK.js → chunk-ELFT36PV.js} +2 -2
  11. package/dist/{chunk-DGEKUT7Q.js → chunk-JXQXSC45.js} +33 -5
  12. package/dist/chunk-JXQXSC45.js.map +1 -0
  13. package/dist/{chunk-L4QGC27H.js → chunk-PVAVNJKS.js} +13 -1
  14. package/dist/chunk-PVAVNJKS.js.map +1 -0
  15. package/dist/{chunk-6OTVPRXH.js → chunk-VCMMAFXQ.js} +2 -2
  16. package/dist/{chunk-IIPTBZQW.js → chunk-ZWXJBSUW.js} +2 -2
  17. package/dist/cli.js +24 -14
  18. package/dist/cli.js.map +1 -1
  19. package/dist/contribute-3MQJ3XAQ.js +256 -0
  20. package/dist/contribute-3MQJ3XAQ.js.map +1 -0
  21. package/dist/{enhance-VVMAKMVZ.js → enhance-VGWUX474.js} +4 -4
  22. package/dist/{feedback-AKHD7QIM.js → feedback-ZOUCCHN4.js} +3 -3
  23. package/dist/{finish-WGPISUEH.js → finish-QJSK6Z7J.js} +3 -3
  24. package/dist/{ignite-JEN3K3OT.js → ignite-HICLZEYU.js} +121 -6
  25. package/dist/ignite-HICLZEYU.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +43 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/{init-EVUT4ZQJ.js → init-UMKNHNV5.js} +2 -2
  30. package/dist/mcp/github-comment-server.js.map +1 -1
  31. package/dist/prompts/init-prompt.txt +3 -1
  32. package/dist/prompts/issue-prompt.txt +141 -9
  33. package/dist/{start-2NEZU7SE.js → start-CT2ZEFP2.js} +10 -10
  34. package/package.json +3 -2
  35. package/dist/chunk-DGEKUT7Q.js.map +0 -1
  36. package/dist/chunk-L4QGC27H.js.map +0 -1
  37. package/dist/ignite-JEN3K3OT.js.map +0 -1
  38. /package/dist/{ClaudeContextManager-LD3VB6EM.js.map → ClaudeContextManager-JKR4WGNU.js.map} +0 -0
  39. /package/dist/{ClaudeService-CFFI7DD5.js.map → ClaudeService-55DQGB7T.js.map} +0 -0
  40. /package/dist/{GitHubService-SH4H6VS5.js.map → GitHubService-LWP4GKGH.js.map} +0 -0
  41. /package/dist/{LoomLauncher-FB2MV2ZI.js.map → LoomLauncher-UMMLPIZO.js.map} +0 -0
  42. /package/dist/{PromptTemplateManager-WM5GIPEF.js.map → PromptTemplateManager-WII75TKH.js.map} +0 -0
  43. /package/dist/{add-issue-L5HX6LEV.js.map → add-issue-X56V3XPB.js.map} +0 -0
  44. /package/dist/{chunk-SLIMABFA.js.map → chunk-DEPYQRRB.js.map} +0 -0
  45. /package/dist/{chunk-HURVAQRK.js.map → chunk-ELFT36PV.js.map} +0 -0
  46. /package/dist/{chunk-6OTVPRXH.js.map → chunk-VCMMAFXQ.js.map} +0 -0
  47. /package/dist/{chunk-IIPTBZQW.js.map → chunk-ZWXJBSUW.js.map} +0 -0
  48. /package/dist/{enhance-VVMAKMVZ.js.map → enhance-VGWUX474.js.map} +0 -0
  49. /package/dist/{feedback-AKHD7QIM.js.map → feedback-ZOUCCHN4.js.map} +0 -0
  50. /package/dist/{finish-WGPISUEH.js.map → finish-QJSK6Z7J.js.map} +0 -0
  51. /package/dist/{init-EVUT4ZQJ.js.map → init-UMKNHNV5.js.map} +0 -0
  52. /package/dist/{start-2NEZU7SE.js.map → start-CT2ZEFP2.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-PXZBAC2M.js";
6
6
  import {
7
7
  PromptTemplateManager
8
- } from "./chunk-L4QGC27H.js";
8
+ } from "./chunk-PVAVNJKS.js";
9
9
  import {
10
10
  ShellCompletion
11
11
  } from "./chunk-PV3GAXQO.js";
@@ -336,4 +336,4 @@ var InitCommand = class {
336
336
  export {
337
337
  InitCommand
338
338
  };
339
- //# sourceMappingURL=init-EVUT4ZQJ.js.map
339
+ //# sourceMappingURL=init-UMKNHNV5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/github-comment-server.ts","../../src/utils/github.ts"],"sourcesContent":["/**\n * GitHub Comment MCP Server\n *\n * A Model Context Protocol server that enables Claude to create and update\n * GitHub comments during issue/PR workflows. Uses gh CLI for all GitHub operations.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport {\n\tcreateIssueComment,\n\tupdateIssueComment,\n\tcreatePRComment,\n} from '../utils/github.js'\nimport type {\n\tCreateCommentInput,\n\tUpdateCommentInput,\n} from './types.js'\n\n// Validate required environment variables\nfunction validateEnvironment(): void {\n\tconst required = ['REPO_OWNER', 'REPO_NAME']\n\tconst missing = required.filter((key) => !process.env[key])\n\n\tif (missing.length > 0) {\n\t\tconsole.error(\n\t\t\t`Missing required environment variables: ${missing.join(', ')}`\n\t\t)\n\t\tprocess.exit(1)\n\t}\n}\n\n// Initialize the MCP server\nconst server = new McpServer({\n\tname: 'github-comment-broker',\n\tversion: '0.1.0',\n})\n\n// Register create_comment tool\nserver.registerTool(\n\t'create_comment',\n\t{\n\t\ttitle: 'Create GitHub Comment',\n\t\tdescription:\n\t\t\t'Create a new comment on a GitHub issue or pull request. Use this to start tracking a workflow phase.',\n\t\tinputSchema: {\n\t\t\tnumber: z.number().describe('The issue or PR number'),\n\t\t\tbody: z.string().describe('The comment body (markdown supported)'),\n\t\t\ttype: z\n\t\t\t\t.enum(['issue', 'pr'])\n\t\t\t\t.describe('Type of entity to comment on (issue or pr)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tcreated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ number, body, type }: CreateCommentInput) => {\n\t\tconsole.error(`Creating ${type} comment on #${number}`)\n\n\t\ttry {\n\t\t\tconst result =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? await createIssueComment(number, body)\n\t\t\t\t\t: await createPRComment(number, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment created successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to create comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to create ${type} comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Register update_comment tool\nserver.registerTool(\n\t'update_comment',\n\t{\n\t\ttitle: 'Update GitHub Comment',\n\t\tdescription:\n\t\t\t'Update an existing GitHub comment. Use this to update progress during a workflow phase.',\n\t\tinputSchema: {\n\t\t\tcommentId: z.number().describe('The GitHub comment ID to update'),\n\t\t\tbody: z.string().describe('The updated comment body (markdown supported)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tupdated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ commentId, body }: UpdateCommentInput) => {\n\t\tconsole.error(`Updating comment ${commentId}`)\n\n\t\ttry {\n\t\t\tconst result = await updateIssueComment(commentId, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment updated successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to update comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to update comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('Starting GitHub Comment MCP Server...')\n\n\t// Validate environment\n\tvalidateEnvironment()\n\tconsole.error('Environment validated')\n\tconsole.error(`Repository: ${process.env.REPO_OWNER}/${process.env.REPO_NAME}`)\n\tconsole.error(`Event type: ${process.env.GITHUB_EVENT_NAME ?? 'not specified'}`)\n\n\t// Connect stdio transport\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\n\tconsole.error('GitHub Comment MCP Server running on stdio transport')\n}\n\n// Run the server\nmain().catch((error) => {\n\tconsole.error('Fatal error starting MCP server:', error)\n\tprocess.exit(1)\n})\n","import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output\n\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\tconst userMatch = output.match(/Logged in to github\\.com as ([^\\s]+)/)\n\n\t\tconst username = userMatch?.[1]\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes: scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? [],\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/github-comment-server.ts","../../src/utils/github.ts"],"sourcesContent":["/**\n * GitHub Comment MCP Server\n *\n * A Model Context Protocol server that enables Claude to create and update\n * GitHub comments during issue/PR workflows. Uses gh CLI for all GitHub operations.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport {\n\tcreateIssueComment,\n\tupdateIssueComment,\n\tcreatePRComment,\n} from '../utils/github.js'\nimport type {\n\tCreateCommentInput,\n\tUpdateCommentInput,\n} from './types.js'\n\n// Validate required environment variables\nfunction validateEnvironment(): void {\n\tconst required = ['REPO_OWNER', 'REPO_NAME']\n\tconst missing = required.filter((key) => !process.env[key])\n\n\tif (missing.length > 0) {\n\t\tconsole.error(\n\t\t\t`Missing required environment variables: ${missing.join(', ')}`\n\t\t)\n\t\tprocess.exit(1)\n\t}\n}\n\n// Initialize the MCP server\nconst server = new McpServer({\n\tname: 'github-comment-broker',\n\tversion: '0.1.0',\n})\n\n// Register create_comment tool\nserver.registerTool(\n\t'create_comment',\n\t{\n\t\ttitle: 'Create GitHub Comment',\n\t\tdescription:\n\t\t\t'Create a new comment on a GitHub issue or pull request. Use this to start tracking a workflow phase.',\n\t\tinputSchema: {\n\t\t\tnumber: z.number().describe('The issue or PR number'),\n\t\t\tbody: z.string().describe('The comment body (markdown supported)'),\n\t\t\ttype: z\n\t\t\t\t.enum(['issue', 'pr'])\n\t\t\t\t.describe('Type of entity to comment on (issue or pr)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tcreated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ number, body, type }: CreateCommentInput) => {\n\t\tconsole.error(`Creating ${type} comment on #${number}`)\n\n\t\ttry {\n\t\t\tconst result =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? await createIssueComment(number, body)\n\t\t\t\t\t: await createPRComment(number, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment created successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to create comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to create ${type} comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Register update_comment tool\nserver.registerTool(\n\t'update_comment',\n\t{\n\t\ttitle: 'Update GitHub Comment',\n\t\tdescription:\n\t\t\t'Update an existing GitHub comment. Use this to update progress during a workflow phase.',\n\t\tinputSchema: {\n\t\t\tcommentId: z.number().describe('The GitHub comment ID to update'),\n\t\t\tbody: z.string().describe('The updated comment body (markdown supported)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tupdated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ commentId, body }: UpdateCommentInput) => {\n\t\tconsole.error(`Updating comment ${commentId}`)\n\n\t\ttry {\n\t\t\tconst result = await updateIssueComment(commentId, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment updated successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to update comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to update comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('Starting GitHub Comment MCP Server...')\n\n\t// Validate environment\n\tvalidateEnvironment()\n\tconsole.error('Environment validated')\n\tconsole.error(`Repository: ${process.env.REPO_OWNER}/${process.env.REPO_NAME}`)\n\tconsole.error(`Event type: ${process.env.GITHUB_EVENT_NAME ?? 'not specified'}`)\n\n\t// Connect stdio transport\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\n\tconsole.error('GitHub Comment MCP Server running on stdio transport')\n}\n\n// Run the server\nmain().catch((error) => {\n\tconsole.error('Fatal error starting MCP server:', error)\n\tprocess.exit(1)\n})\n","import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":"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","names":[]}
@@ -502,11 +502,13 @@ Then ask: "Does this configuration look correct?"
502
502
 
503
503
  ### Phase 3: Choose Configuration File
504
504
 
505
+ **Fork/Contributor Check:** If "GitHub Remote Configuration" shows both "origin" and "upstream" remotes (classic fork pattern), display: "It looks like you're working on a fork (origin + upstream remotes detected). For contributors, we recommend saving to settings.local.json to avoid including personal settings in PRs to upstream, but you can still choose shared if needed."
506
+
505
507
  **CRITICAL: You MUST ask the user where to save the configuration. DO NOT make assumptions or decide for them.**
506
508
 
507
509
  Ask the user this exact question: "Should this configuration be committed to git?
508
510
  - yes: Save to .iloom/settings.json (shared with team)
509
- - no: Save to .iloom/settings.local.json (local only, gitignored)"
511
+ - no: Save to .iloom/settings.local.json (local only, gitignored)" <-- recommend this for fork setups
510
512
 
511
513
  **DO NOT:**
512
514
  - Assume what the user wants based on the type of setting
@@ -20,6 +20,94 @@ Before sending any response, verify it doesn't contain:
20
20
 
21
21
  ---
22
22
 
23
+ {{#IF FIRST_TIME_USER}}
24
+ ## First-Time User Context
25
+
26
+ This is the user's first time running through the `iloom` workflow. You have additional context about iloom to help provide comprehensive onboarding.
27
+
28
+ ### iloom System Overview
29
+
30
+ iloom is a CLI tool for scaling a human's understanding of the AI's work, as the AI's output scales. It does this by managing isolated Git worktrees in a guided workflow, with significant AI integration. It orchestrates multi-phase workflows:
31
+
32
+ 1. **Enhancement** - Expands brief issues into detailed requirements
33
+ 2. **Complexity Evaluation** - Categorizes as SIMPLE or COMPLEX based on scope
34
+ 3. **Analysis** - Investigates root causes and technical constraints
35
+ 4. **Planning** - Creates implementation roadmap with file specifications
36
+ 5. **Implementation** - Executes the plan with validation
37
+
38
+ Each phase creates GitHub comments for team visibility and traceability.
39
+
40
+ ### Key iloom Commands
41
+ - `iloom init` / `iloom config` - Interactive configuration wizard
42
+ - `iloom start <issue>` - Create isolated loom for issue
43
+ - `iloom spin` - Resume work in current loom with full context
44
+ - `iloom finish` - Validate, commit, merge and cleanup
45
+ - `iloom list` - Show active looms
46
+ - `iloom feedback` - Submit a bug report or a feedback request
47
+
48
+ The `il` command can also be used as a shorter alias.
49
+
50
+ ### Loom Isolation Features
51
+ Each loom provides:
52
+ - Dedicated Git worktree (no branch conflicts)
53
+ - Unique database branch via Neon integration
54
+ - Color-coded terminal/VS Code for visual context switching
55
+ - Deterministic port assignment (3000 + issue number)
56
+
57
+ ### Configuration System
58
+ **IMPORTANT: NEVER run `iloom init` or `iloom config` as Bash commands from within this session.**
59
+
60
+ These are interactive configuration commands that launch Claude themselves. Instead, tell users to:
61
+ 1. Exit this Claude session (type `/exit`)
62
+ 2. From their main terminal/worktree, run: `iloom init` or `iloom config`
63
+ 3. Complete the configuration with the interactive assistant
64
+ 4. Return to their work
65
+
66
+ Settings are stored in `.iloom/settings.json` and .iloom/settings.local.json:
67
+ - `mainBranch` - Primary branch name (auto-detected)
68
+ - `workflows` - Permission modes per workflow type
69
+ - `agents` - Model selection per phase
70
+ - `capabilities.web.basePort` - Development server base port
71
+
72
+ ### Complete Documentation Reference - use this to provide answers to questions about functionality or limitations.
73
+
74
+ README_CONTENT
75
+
76
+ ### Settings Schema Documentation - use this to suggest solutions to any questions that might be asked
77
+
78
+ SETTINGS_SCHEMA_CONTENT
79
+
80
+ ### User Onboarding Instructions
81
+
82
+ **CRITICAL FOR FIRST-TIME USERS: You MUST use AskUserQuestion tool for ALL interactions.**
83
+
84
+ Since this is a first-time user:
85
+ 1. **IMMEDIATELY** after your initial greeting, use the AskUserQuestion tool to ask:
86
+ - If they want an explanation of how iloom works
87
+ - If they have questions about the workflow phases
88
+ - If they want to proceed directly to working on their issue
89
+
90
+ 2. **IF they ask for explanation**: Use progressive disclosure with AskUserQuestion tool:
91
+ - Give brief overview (2-3 sentences)
92
+ - Use AskUserQuestion to ask what specific aspect they want to learn more about:
93
+ * How looms work (isolation, ports, databases)
94
+ * Workflow phases (enhancement, analysis, planning, implementation)
95
+ * Key commands (start, spin, finish, list)
96
+ * Configuration options (but NEVER run `iloom config` or `iloom init` commands - tell them to exit and run externally)
97
+ - After each explanation, use AskUserQuestion to ask what they want to do next
98
+
99
+ 3. **ALWAYS** use AskUserQuestion tool for decision points - NEVER leave users with just text questions
100
+
101
+ 4. Only start the workflow after they explicitly choose to proceed via AskUserQuestion
102
+
103
+ 5. Be more educational in your explanations throughout the process
104
+
105
+ 6. Reference the documentation above when answering questions
106
+
107
+ ---
108
+
109
+ {{/IF FIRST_TIME_USER}}
110
+
23
111
  You are orchestrating a set of agents through a development process, with human review at each step. This is referred to as the "iloom workflow".
24
112
 
25
113
  **IMPORTANT: Unless otherwise instructed, each step requires explicit human approval. Do not proceed to any step until explicitly told to do so.**
@@ -117,7 +205,16 @@ Only execute if workflow plan determined NEEDS_ENHANCEMENT:
117
205
  1. Execute: @agent-iloom-issue-enhancer ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
118
206
  2. Upon completion: Extract issue+comment link (including comment ID) from agent output and provide link to GitHub issue comment
119
207
  3. Mark todo #2 and #3 as completed
120
- 4. **STOP HERE** - Request explicit approval before proceeding to analysis
208
+ 4. Use AskUserQuestion tool with a single question:
209
+ - Question: "Enhancement complete. See results at: [GitHub comment URL]. How would you like to proceed?"
210
+ - Options:
211
+ - "Continue to complexity evaluation" (default)
212
+ - "Provide feedback on enhancement"
213
+ - "Exit workflow"
214
+ - multiSelect: false
215
+ - If user chooses "Provide feedback on enhancement": Process their input and re-run enhancement if needed, telling the agent to edit their comment and provide the comment id
216
+ - If user chooses "Exit workflow": End workflow gracefully
217
+ - If user chooses "Continue to complexity evaluation": Proceed to next step
121
218
 
122
219
  If workflow plan determined SKIP_ENHANCEMENT:
123
220
  1. Mark todos #2 and #3 as completed
@@ -145,10 +242,20 @@ Only execute if workflow plan determined NEEDS_COMPLEXITY_EVAL:
145
242
 
146
243
  Reasoning: [reasoning text from evaluator]
147
244
  ```
148
- 5. Prompt user for confirmation:
149
- Ask: "The evaluator assessed this as a [SIMPLE/COMPLEX] task. Do you agree? If not, tell me how you'd like me to classify it."
245
+ 5. Use AskUserQuestion tool with a single question:
246
+ - Question: "Complexity evaluated as [SIMPLE/COMPLEX]. See assessment at: [GitHub comment URL]. Do you agree with this classification?"
247
+ - Options:
248
+ - "Yes, proceed with [SIMPLE/COMPLEX] workflow" (default)
249
+ - "No, reclassify as SIMPLE"
250
+ - "No, reclassify as COMPLEX"
251
+ - "Provide feedback before deciding"
252
+ - "Exit workflow"
253
+ - multiSelect: false
254
+ - If user reclassifies: Update confirmed complexity accordingly
255
+ - If user chooses "Provide feedback before deciding": Process and re-evaluate if needed, telling the agent to edit their comment and provide the comment id
256
+ - If user chooses "Exit workflow": End workflow gracefully
150
257
  6. Mark todos #5, #6, and #7 as completed
151
- 7. **STOP HERE** - Inform user of confirmed complexity and request explicit approval before proceeding to next phase
258
+ 7. Proceed to ROUTING DECISION POINT with confirmed complexity
152
259
 
153
260
  If workflow plan determined SKIP_COMPLEXITY_EVAL:
154
261
  1. Mark todos #5, #6, and #7 as completed
@@ -167,7 +274,16 @@ Only execute if workflow plan determined NEEDS_ANALYSIS AND complexity is COMPLE
167
274
  1. Execute: @agent-iloom-issue-analyzer ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
168
275
  2. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
169
276
  3. Mark todos #10, #11, and #12 as completed (todo #9 was already marked at routing decision point)
170
- 4. **STOP HERE** - Request explicit approval before proceeding to planning phase
277
+ 4. Use AskUserQuestion tool with a single question:
278
+ - Question: "Analysis complete. See findings at: [GitHub comment URL]. How would you like to proceed?"
279
+ - Options:
280
+ - "Continue to planning phase" (default)
281
+ - "Provide feedback on analysis"
282
+ - "Exit workflow"
283
+ - multiSelect: false
284
+ - If user chooses "Provide feedback on analysis": Process their input and re-run analysis if needed, telling the agent to edit their comment and provide the comment id
285
+ - If user chooses "Exit workflow": End workflow gracefully
286
+ - If user chooses "Continue to planning phase": Proceed to planning phase
171
287
 
172
288
  If workflow plan determined SKIP_ANALYSIS:
173
289
  1. Mark todos #10, #11, and #12 as completed (todo #9 was already marked at routing decision point)
@@ -206,9 +322,16 @@ Execute combined analyze-and-plan agent:
206
322
  2. Execute: @agent-iloom-issue-analyze-and-plan ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
207
323
  3. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
208
324
  4. Mark todo #9 as completed (COMPLEX todos #10-15 were already marked at routing decision point)
209
- 5. Provide summary: "Combined analysis and planning complete. See results at: [GitHub comment URL]"
210
- 6. **STOP HERE** - Request explicit approval before proceeding to implementation
211
- 7. After approval, proceed to STEP 4 (Implementation Phase)
325
+ 5. Use AskUserQuestion tool with a single question:
326
+ - Question: "Combined analysis and planning complete. See results at: [GitHub comment URL]. How would you like to proceed?"
327
+ - Options:
328
+ - "Continue to implementation" (default)
329
+ - "Provide feedback on plan"
330
+ - "Exit workflow"
331
+ - multiSelect: false
332
+ - If user chooses "Provide feedback on plan": Process their input and re-run planning if needed, telling the agent to edit their comment and provide the comment id
333
+ - If user chooses "Exit workflow": End workflow gracefully
334
+ - If user chooses "Continue to implementation": Proceed to STEP 4 (Implementation Phase)
212
335
 
213
336
  ---
214
337
 
@@ -220,7 +343,16 @@ Only execute if workflow plan determined NEEDS_PLANNING AND complexity is COMPLE
220
343
  1. Execute: @agent-iloom-issue-planner ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
221
344
  2. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
222
345
  3. Mark todos #13, #14, and #15 as completed (todos #9-12 were already marked at routing decision point and analysis phase)
223
- 4. **STOP HERE** - Request explicit approval before proceeding to implementation
346
+ 4. Use AskUserQuestion tool with a single question:
347
+ - Question: "Planning complete. See implementation plan at: [GitHub comment URL]. How would you like to proceed?"
348
+ - Options:
349
+ - "Continue to implementation" (default)
350
+ - "Provide feedback on plan"
351
+ - "Exit workflow"
352
+ - multiSelect: false
353
+ - If user chooses "Provide feedback on plan": Process their input and re-run planning if needed, telling the agent to edit their comment and provide the comment id
354
+ - If user chooses "Exit workflow": End workflow gracefully
355
+ - If user chooses "Continue to implementation": Proceed to STEP 4 (Implementation Phase)
224
356
 
225
357
  If workflow plan determined SKIP_PLANNING AND complexity is COMPLEX:
226
358
  1. Mark todos #13, #14, and #15 as completed (todos #9-12 were already marked at routing decision point and analysis phase)
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-BLCTGFZN.js";
13
13
  import {
14
14
  ClaudeContextManager
15
- } from "./chunk-SLIMABFA.js";
15
+ } from "./chunk-DEPYQRRB.js";
16
16
  import {
17
17
  extractSettingsOverrides
18
18
  } from "./chunk-GYCR2LOU.js";
@@ -43,11 +43,11 @@ import {
43
43
  } from "./chunk-ZZZWQGTS.js";
44
44
  import {
45
45
  GitHubService
46
- } from "./chunk-IIPTBZQW.js";
47
- import "./chunk-DGEKUT7Q.js";
48
- import "./chunk-HURVAQRK.js";
46
+ } from "./chunk-ZWXJBSUW.js";
47
+ import "./chunk-JXQXSC45.js";
48
+ import "./chunk-ELFT36PV.js";
49
49
  import "./chunk-PXZBAC2M.js";
50
- import "./chunk-L4QGC27H.js";
50
+ import "./chunk-PVAVNJKS.js";
51
51
  import {
52
52
  GitWorktreeManager
53
53
  } from "./chunk-IO4WFTL2.js";
@@ -311,8 +311,8 @@ var LoomManager = class {
311
311
  const setArguments = (_j = input.options) == null ? void 0 : _j.setArguments;
312
312
  const executablePath = (_k = input.options) == null ? void 0 : _k.executablePath;
313
313
  if (enableClaude || enableCode || enableDevServer || enableTerminal) {
314
- const { LoomLauncher } = await import("./LoomLauncher-FB2MV2ZI.js");
315
- const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-LD3VB6EM.js");
314
+ const { LoomLauncher } = await import("./LoomLauncher-UMMLPIZO.js");
315
+ const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-JKR4WGNU.js");
316
316
  const claudeContext = new ClaudeContextManager2(void 0, void 0, this.settings);
317
317
  const launcher = new LoomLauncher(claudeContext);
318
318
  await launcher.launchLoom({
@@ -654,8 +654,8 @@ var LoomManager = class {
654
654
  const executablePath = (_i = input.options) == null ? void 0 : _i.executablePath;
655
655
  if (enableClaude || enableCode || enableDevServer || enableTerminal) {
656
656
  logger.info("Launching workspace components...");
657
- const { LoomLauncher } = await import("./LoomLauncher-FB2MV2ZI.js");
658
- const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-LD3VB6EM.js");
657
+ const { LoomLauncher } = await import("./LoomLauncher-UMMLPIZO.js");
658
+ const { ClaudeContextManager: ClaudeContextManager2 } = await import("./ClaudeContextManager-JKR4WGNU.js");
659
659
  const claudeContext = new ClaudeContextManager2(void 0, void 0, this.settings);
660
660
  const launcher = new LoomLauncher(claudeContext);
661
661
  await launcher.launchLoom({
@@ -980,4 +980,4 @@ var StartCommand = class {
980
980
  export {
981
981
  StartCommand
982
982
  };
983
- //# sourceMappingURL=start-2NEZU7SE.js.map
983
+ //# sourceMappingURL=start-CT2ZEFP2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iloom/cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Control plane for maintaining alignment between you and Claude AI as you work across multiple issues using isolated environments, visible context, and multi-agent workflows to scale understanding, not just output",
5
5
  "keywords": [
6
6
  "ai",
@@ -46,7 +46,8 @@
46
46
  },
47
47
  "packageManager": "pnpm@10.16.1+sha512.0e155aa2629db8672b49e8475da6226aa4bdea85fdcdfdc15350874946d4f3c91faaf64cbdc4a5d1ab8002f473d5c3fcedcd197989cf0390f9badd3c04678706",
48
48
  "scripts": {
49
- "build": "tsup && pnpm run export-schema",
49
+ "build": "tsup && pnpm run export-schema && pnpm run copy-docs",
50
+ "copy-docs": "tsx scripts/copy-docs.ts",
50
51
  "export-schema": "tsx scripts/export-schema.ts",
51
52
  "dev": "tsup --watch",
52
53
  "test": "vitest run",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output\n\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\tconst userMatch = output.match(/Logged in to github\\.com as ([^\\s]+)/)\n\n\t\tconst username = userMatch?.[1]\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes: scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? [],\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number,\n\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/PromptTemplateManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { accessSync } from 'fs'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\nimport { logger } from '../utils/logger.js'\n\nexport interface TemplateVariables {\n\tISSUE_NUMBER?: number\n\tPR_NUMBER?: number\n\tISSUE_TITLE?: string\n\tPR_TITLE?: string\n\tWORKSPACE_PATH?: string\n\tPORT?: number\n\tONE_SHOT_MODE?: boolean\n\tSETTINGS_SCHEMA?: string\n\tSETTINGS_JSON?: string\n\tSETTINGS_LOCAL_JSON?: string\n\tSHELL_TYPE?: string\n\tSHELL_CONFIG_PATH?: string\n\tSHELL_CONFIG_CONTENT?: string\n\tREMOTES_INFO?: string\n\tMULTIPLE_REMOTES?: string\n\tSINGLE_REMOTE?: string\n\tSINGLE_REMOTE_NAME?: string\n\tSINGLE_REMOTE_URL?: string\n\tNO_REMOTES?: string\n}\n\nexport class PromptTemplateManager {\n\tprivate templateDir: string\n\n\tconstructor(templateDir?: string) {\n\t\tif (templateDir) {\n\t\t\tthis.templateDir = templateDir\n\t\t} else {\n\t\t\t// Find templates relative to the package installation\n\t\t\t// When running from dist/, templates are copied to dist/prompts/\n\t\t\tconst currentFileUrl = import.meta.url\n\t\t\tconst currentFilePath = fileURLToPath(currentFileUrl)\n\t\t\tconst distDir = path.dirname(currentFilePath) // dist directory (may be chunked file location)\n\n\t\t\t// Walk up to find the dist directory (in case of chunked files)\n\t\t\tlet templateDir = path.join(distDir, 'prompts')\n\t\t\tlet currentDir = distDir\n\n\t\t\t// Try to find the prompts directory by walking up\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst candidatePath = path.join(currentDir, 'prompts')\n\t\t\t\ttry {\n\t\t\t\t\t// Check if this directory exists (sync check for constructor)\n\t\t\t\t\taccessSync(candidatePath)\n\t\t\t\t\ttemplateDir = candidatePath\n\t\t\t\t\tbreak\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.templateDir = templateDir\n\t\t\tlogger.debug('PromptTemplateManager initialized', {\n\t\t\t\tcurrentFilePath,\n\t\t\t\tdistDir,\n\t\t\t\ttemplateDir: this.templateDir\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Load a template file by name\n\t */\n\tasync loadTemplate(templateName: 'issue' | 'pr' | 'regular' | 'init'): Promise<string> {\n\t\tconst templatePath = path.join(this.templateDir, `${templateName}-prompt.txt`)\n\n\t\tlogger.debug('Loading template', {\n\t\t\ttemplateName,\n\t\t\ttemplateDir: this.templateDir,\n\t\t\ttemplatePath\n\t\t})\n\n\t\ttry {\n\t\t\treturn await readFile(templatePath, 'utf-8')\n\t\t} catch (error) {\n\t\t\tlogger.error('Failed to load template', { templateName, templatePath, error })\n\t\t\tthrow new Error(`Template not found: ${templatePath}`)\n\t\t}\n\t}\n\n\t/**\n\t * Substitute variables in a template string\n\t */\n\tsubstituteVariables(template: string, variables: TemplateVariables): string {\n\t\tlet result = template\n\n\t\t// Process conditional sections first\n\t\tresult = this.processConditionalSections(result, variables)\n\n\t\t// Replace each variable if it exists\n\t\tif (variables.ISSUE_NUMBER !== undefined) {\n\t\t\tresult = result.replace(/ISSUE_NUMBER/g, String(variables.ISSUE_NUMBER))\n\t\t}\n\n\t\tif (variables.PR_NUMBER !== undefined) {\n\t\t\tresult = result.replace(/PR_NUMBER/g, String(variables.PR_NUMBER))\n\t\t}\n\n\t\tif (variables.ISSUE_TITLE !== undefined) {\n\t\t\tresult = result.replace(/ISSUE_TITLE/g, variables.ISSUE_TITLE)\n\t\t}\n\n\t\tif (variables.PR_TITLE !== undefined) {\n\t\t\tresult = result.replace(/PR_TITLE/g, variables.PR_TITLE)\n\t\t}\n\n\t\tif (variables.WORKSPACE_PATH !== undefined) {\n\t\t\tresult = result.replace(/WORKSPACE_PATH/g, variables.WORKSPACE_PATH)\n\t\t}\n\n\t\tif (variables.PORT !== undefined) {\n\t\t\tresult = result.replace(/PORT/g, String(variables.PORT))\n\t\t}\n\n\t\tif (variables.SETTINGS_SCHEMA !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_SCHEMA/g, variables.SETTINGS_SCHEMA)\n\t\t}\n\n\t\tif (variables.SETTINGS_JSON !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_JSON/g, variables.SETTINGS_JSON)\n\t\t}\n\n\t\tif (variables.SETTINGS_LOCAL_JSON !== undefined) {\n\t\t\tresult = result.replace(/SETTINGS_LOCAL_JSON/g, variables.SETTINGS_LOCAL_JSON)\n\t\t}\n\n\t\tif (variables.SHELL_TYPE !== undefined) {\n\t\t\tresult = result.replace(/SHELL_TYPE/g, variables.SHELL_TYPE)\n\t\t}\n\n\t\tif (variables.SHELL_CONFIG_PATH !== undefined) {\n\t\t\tresult = result.replace(/SHELL_CONFIG_PATH/g, variables.SHELL_CONFIG_PATH)\n\t\t}\n\n\t\tif (variables.SHELL_CONFIG_CONTENT !== undefined) {\n\t\t\tresult = result.replace(/SHELL_CONFIG_CONTENT/g, variables.SHELL_CONFIG_CONTENT)\n\t\t}\n\n\t\tif (variables.REMOTES_INFO !== undefined) {\n\t\t\tresult = result.replace(/REMOTES_INFO/g, variables.REMOTES_INFO)\n\t\t}\n\n\t\tif (variables.MULTIPLE_REMOTES !== undefined) {\n\t\t\tresult = result.replace(/MULTIPLE_REMOTES/g, variables.MULTIPLE_REMOTES)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE/g, variables.SINGLE_REMOTE)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE_NAME !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE_NAME/g, variables.SINGLE_REMOTE_NAME)\n\t\t}\n\n\t\tif (variables.SINGLE_REMOTE_URL !== undefined) {\n\t\t\tresult = result.replace(/SINGLE_REMOTE_URL/g, variables.SINGLE_REMOTE_URL)\n\t\t}\n\n\t\tif (variables.NO_REMOTES !== undefined) {\n\t\t\tresult = result.replace(/NO_REMOTES/g, variables.NO_REMOTES)\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Process conditional sections in template\n\t * Format: {{#IF ONE_SHOT_MODE}}content{{/IF ONE_SHOT_MODE}}\n\t *\n\t * Note: /s flag allows . to match newlines\n\t */\n\tprivate processConditionalSections(template: string, variables: TemplateVariables): string {\n\t\tlet result = template\n\n\t\t// Process ONE_SHOT_MODE conditionals\n\t\tconst oneShotRegex = /\\{\\{#IF ONE_SHOT_MODE\\}\\}(.*?)\\{\\{\\/IF ONE_SHOT_MODE\\}\\}/gs\n\n\t\tif (variables.ONE_SHOT_MODE === true) {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(oneShotRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(oneShotRegex, '')\n\t\t}\n\n\t\t// Process SETTINGS_JSON conditionals\n\t\tconst settingsJsonRegex = /\\{\\{#IF SETTINGS_JSON\\}\\}(.*?)\\{\\{\\/IF SETTINGS_JSON\\}\\}/gs\n\n\t\tif (variables.SETTINGS_JSON !== undefined && variables.SETTINGS_JSON !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(settingsJsonRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(settingsJsonRegex, '')\n\t\t}\n\n\t\t// Process SETTINGS_LOCAL_JSON conditionals\n\t\tconst settingsLocalJsonRegex = /\\{\\{#IF SETTINGS_LOCAL_JSON\\}\\}(.*?)\\{\\{\\/IF SETTINGS_LOCAL_JSON\\}\\}/gs\n\n\t\tif (variables.SETTINGS_LOCAL_JSON !== undefined && variables.SETTINGS_LOCAL_JSON !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(settingsLocalJsonRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(settingsLocalJsonRegex, '')\n\t\t}\n\n\t\t// Process MULTIPLE_REMOTES conditionals\n\t\tconst multipleRemotesRegex = /\\{\\{#IF MULTIPLE_REMOTES\\}\\}(.*?)\\{\\{\\/IF MULTIPLE_REMOTES\\}\\}/gs\n\n\t\tif (variables.MULTIPLE_REMOTES !== undefined && variables.MULTIPLE_REMOTES !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(multipleRemotesRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(multipleRemotesRegex, '')\n\t\t}\n\n\t\t// Process SINGLE_REMOTE conditionals\n\t\tconst singleRemoteRegex = /\\{\\{#IF SINGLE_REMOTE\\}\\}(.*?)\\{\\{\\/IF SINGLE_REMOTE\\}\\}/gs\n\n\t\tif (variables.SINGLE_REMOTE !== undefined && variables.SINGLE_REMOTE !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(singleRemoteRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(singleRemoteRegex, '')\n\t\t}\n\n\t\t// Process NO_REMOTES conditionals\n\t\tconst noRemotesRegex = /\\{\\{#IF NO_REMOTES\\}\\}(.*?)\\{\\{\\/IF NO_REMOTES\\}\\}/gs\n\n\t\tif (variables.NO_REMOTES !== undefined && variables.NO_REMOTES !== '') {\n\t\t\t// Include the content, remove the conditional markers\n\t\t\tresult = result.replace(noRemotesRegex, '$1')\n\t\t} else {\n\t\t\t// Remove the entire conditional block\n\t\t\tresult = result.replace(noRemotesRegex, '')\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a fully processed prompt for a workflow type\n\t */\n\tasync getPrompt(\n\t\ttype: 'issue' | 'pr' | 'regular' | 'init',\n\t\tvariables: TemplateVariables\n\t): Promise<string> {\n\t\tconst template = await this.loadTemplate(type)\n\t\treturn this.substituteVariables(template, variables)\n\t}\n}\n"],"mappings":"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","names":["templateDir"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/ignite.ts"],"sourcesContent":["import path from 'path'\nimport { logger } from '../utils/logger.js'\nimport { ClaudeWorkflowOptions } from '../lib/ClaudeService.js'\nimport { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { launchClaude, ClaudeCliOptions } from '../utils/claude.js'\nimport { PromptTemplateManager, TemplateVariables } from '../lib/PromptTemplateManager.js'\nimport { generateGitHubCommentMcpConfig } from '../utils/mcp.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { extractSettingsOverrides } from '../utils/cli-overrides.js'\n\n/**\n * IgniteCommand: Auto-detect workspace context and launch Claude\n *\n * This command:\n * 1. Auto-detects context from current directory and git branch\n * 2. Loads appropriate prompt template with variable substitution\n * 3. Launches Claude with existing agent system (NO changes to agent loading)\n * 4. Executes in current terminal (not opening a new window)\n *\n * CRITICAL: This command works with agents exactly as they currently function.\n * NO modifications to agent loading mechanisms.\n */\nexport class IgniteCommand {\n\tprivate templateManager: PromptTemplateManager\n\tprivate gitWorktreeManager: GitWorktreeManager\n\tprivate agentManager: AgentManager\n\tprivate settingsManager: SettingsManager\n\tprivate settings?: import('../lib/SettingsManager.js').IloomSettings\n\n\tconstructor(\n\t\ttemplateManager?: PromptTemplateManager,\n\t\tgitWorktreeManager?: GitWorktreeManager,\n\t\tagentManager?: AgentManager,\n\t\tsettingsManager?: SettingsManager\n\t) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.gitWorktreeManager = gitWorktreeManager ?? new GitWorktreeManager()\n\t\tthis.agentManager = agentManager ?? new AgentManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t}\n\n\t/**\n\t * Main entry point for spin command\n\t */\n\tasync execute(oneShot: import('../types/index.js').OneShotMode = 'default'): Promise<void> {\n\t\ttry {\n\t\t\tlogger.info('🚀 Your loom is spinning up, please wait...')\n\n\t\t\t// Step 1: Auto-detect workspace context\n\t\t\tconst context = await this.detectWorkspaceContext()\n\n\t\t\tlogger.debug('Auto-detected workspace context', { context })\n\n\t\t\t// Inform user what context was detected\n\t\t\tthis.logDetectedContext(context)\n\n\t\t\tlogger.info('📝 Loading prompt template and preparing Claude...')\n\n\t\t\t// Step 2: Get prompt template with variable substitution\n\t\t\tconst variables = this.buildTemplateVariables(context, oneShot)\n\t\t\tconst systemInstructions = await this.templateManager.getPrompt(context.type, variables)\n\n\t\t\t// User prompt to trigger the workflow (includes one-shot bypass instructions if needed)\n\t\t\tconst userPrompt = this.buildUserPrompt(oneShot)\n\n\t\t\t// Step 2.5: Load settings if not cached with CLI overrides\n\t\t\t// Settings are pre-validated at CLI startup, so no error handling needed here\n\t\t\tif (!this.settings) {\n\t\t\t\tconst cliOverrides = extractSettingsOverrides()\n\t\t\t\tthis.settings = await this.settingsManager.loadSettings(undefined, cliOverrides)\n\t\t\t}\n\n\t\t\t// Step 3: Determine model and permission mode based on workflow type\n\t\t\tconst model = this.getModelForWorkflow(context.type)\n\t\t\tlet permissionMode = this.getPermissionModeForWorkflow(context.type)\n\n\t\t\t// Override permission mode if bypassPermissions oneShot mode\n\t\t\tif (oneShot === 'bypassPermissions') {\n\t\t\t\tpermissionMode = 'bypassPermissions'\n\t\t\t}\n\n\t\t\t// Display warning if bypassPermissions is used\n\t\t\tif (permissionMode === 'bypassPermissions') {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'⚠️ WARNING: Using bypassPermissions mode - Claude will execute all tool calls without confirmation. ' +\n\t\t\t\t\t\t'This can be dangerous. Use with caution.'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Step 4: Build Claude CLI options\n\t\t\tconst claudeOptions: ClaudeCliOptions = {\n\t\t\t\theadless: false, // Enable stdio: 'inherit' for current terminal\n\t\t\t\taddDir: context.workspacePath,\n\t\t\t}\n\n\t\t\t// Add optional model if present\n\t\t\tif (model !== undefined) {\n\t\t\t\tclaudeOptions.model = model\n\t\t\t}\n\n\t\t\t// Add permission mode if not default\n\t\t\tif (permissionMode !== undefined && permissionMode !== 'default') {\n\t\t\t\tclaudeOptions.permissionMode = permissionMode\n\t\t\t}\n\n\t\t\t// Add optional branch name for context\n\t\t\tif (context.branchName !== undefined) {\n\t\t\t\tclaudeOptions.branchName = context.branchName\n\t\t\t}\n\n\t\t\t// Step 4.5: Generate MCP config and tool filtering for issue/PR workflows\n\t\t\tlet mcpConfig: Record<string, unknown>[] | undefined\n\t\t\tlet allowedTools: string[] | undefined\n\t\t\tlet disallowedTools: string[] | undefined\n\n\t\t\tif (context.type === 'issue' || context.type === 'pr') {\n\t\t\t\ttry {\n\t\t\t\t\tmcpConfig = await generateGitHubCommentMcpConfig(context.type)\n\t\t\t\t\tlogger.debug('Generated MCP configuration for GitHub comment broker')\n\n\t\t\t\t\t// Configure tool filtering for issue/PR workflows\n\t\t\t\t\tallowedTools = [\n\t\t\t\t\t\t'mcp__github_comment__create_comment',\n\t\t\t\t\t\t'mcp__github_comment__update_comment',\n\t\t\t\t\t]\n\t\t\t\t\tdisallowedTools = ['Bash(gh api:*)']\n\n\t\t\t\t\tlogger.debug('Configured tool filtering for issue/PR workflow', { allowedTools, disallowedTools })\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Log warning but continue without MCP\n\t\t\t\t\tlogger.warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Step 4.6: Load agent configurations using cached settings\n\t\t\tlet agents: Record<string, unknown> | undefined\n\t\t\ttry {\n\t\t\t\t// Use cached settings from Step 2.5\n\t\t\t\tif (this.settings?.agents && Object.keys(this.settings.agents).length > 0) {\n\t\t\t\t\tlogger.debug('Loaded project settings', {\n\t\t\t\t\t\tagentOverrides: Object.keys(this.settings.agents),\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// Load agents with settings overrides\n\t\t\t\tconst loadedAgents = await this.agentManager.loadAgents(this.settings)\n\t\t\t\tagents = this.agentManager.formatForCli(loadedAgents)\n\t\t\t\tlogger.debug('Loaded agent configurations', {\n\t\t\t\t\tagentCount: Object.keys(agents).length,\n\t\t\t\t\tagentNames: Object.keys(agents),\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\t// Log warning but continue without agents\n\t\t\t\tlogger.warn(`Failed to load agents: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\t}\n\n\t\t\tlogger.debug('Launching Claude in current terminal', {\n\t\t\t\ttype: context.type,\n\t\t\t\tmodel,\n\t\t\t\tpermissionMode,\n\t\t\t\tworkspacePath: context.workspacePath,\n\t\t\t\thasMcpConfig: !!mcpConfig,\n\t\t\t})\n\n\t\t\tlogger.info('✨ Launching Claude in current terminal...')\n\n\t\t\t// Step 5: Launch Claude with system instructions appended and user prompt\n\t\t\tawait launchClaude(userPrompt, {\n\t\t\t\t...claudeOptions,\n\t\t\t\tappendSystemPrompt: systemInstructions,\n\t\t\t\t...(mcpConfig && { mcpConfig }),\n\t\t\t\t...(allowedTools && { allowedTools }),\n\t\t\t\t...(disallowedTools && { disallowedTools }),\n\t\t\t\t...(agents && { agents }),\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tlogger.error(`Failed to launch Claude: ${errorMessage}`)\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Log user-friendly information about detected context\n\t */\n\tprivate logDetectedContext(context: ClaudeWorkflowOptions): void {\n\t\tif (context.type === 'issue') {\n\t\t\tlogger.info(`🎯 Detected issue workflow: Issue #${context.issueNumber}`)\n\t\t} else if (context.type === 'pr') {\n\t\t\tlogger.info(`🔄 Detected PR workflow: PR #${context.prNumber}`)\n\t\t} else {\n\t\t\tlogger.info('🌟 Detected regular workflow')\n\t\t}\n\n\t\tif (context.branchName) {\n\t\t\tlogger.info(`🌿 Working on branch: ${context.branchName}`)\n\t\t}\n\n\t\tif (context.port) {\n\t\t\tlogger.info(`🌐 Development server port: ${context.port}`)\n\t\t}\n\t}\n\n\t/**\n\t * Build template variables from context\n\t */\n\tprivate buildTemplateVariables(context: ClaudeWorkflowOptions, oneShot: import('../types/index.js').OneShotMode): TemplateVariables {\n\t\tconst variables: TemplateVariables = {\n\t\t\tWORKSPACE_PATH: context.workspacePath,\n\t\t}\n\n\t\tif (context.issueNumber !== undefined) {\n\t\t\tvariables.ISSUE_NUMBER = context.issueNumber\n\t\t}\n\n\t\tif (context.prNumber !== undefined) {\n\t\t\tvariables.PR_NUMBER = context.prNumber\n\t\t}\n\n\t\tif (context.title !== undefined) {\n\t\t\tif (context.type === 'issue') {\n\t\t\t\tvariables.ISSUE_TITLE = context.title\n\t\t\t} else if (context.type === 'pr') {\n\t\t\t\tvariables.PR_TITLE = context.title\n\t\t\t}\n\t\t}\n\n\t\tif (context.port !== undefined) {\n\t\t\tvariables.PORT = context.port\n\t\t}\n\n\t\t// Set ONE_SHOT_MODE flag for template conditional sections\n\t\tif (oneShot === 'noReview' || oneShot === 'bypassPermissions') {\n\t\t\tvariables.ONE_SHOT_MODE = true\n\t\t}\n\n\t\treturn variables\n\t}\n\n\t/**\n\t * Get the appropriate model for a workflow type\n\t * Same logic as ClaudeService.getModelForWorkflow()\n\t */\n\tprivate getModelForWorkflow(type: 'issue' | 'pr' | 'regular'): string | undefined {\n\t\t// Issue workflows use claude-sonnet-4-20250514\n\t\tif (type === 'issue') {\n\t\t\treturn 'claude-sonnet-4-20250514'\n\t\t}\n\t\t// For PR and regular workflows, use Claude's default model\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Get the appropriate permission mode for a workflow type\n\t * Same logic as ClaudeService.getPermissionModeForWorkflow()\n\t */\n\tprivate getPermissionModeForWorkflow(\n\t\ttype: 'issue' | 'pr' | 'regular'\n\t): ClaudeCliOptions['permissionMode'] {\n\t\t// Check settings for configured permission mode\n\t\tif (this.settings?.workflows) {\n\t\t\tconst workflowConfig =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? this.settings.workflows.issue\n\t\t\t\t\t: type === 'pr'\n\t\t\t\t\t\t? this.settings.workflows.pr\n\t\t\t\t\t\t: this.settings.workflows.regular\n\n\t\t\tif (workflowConfig?.permissionMode) {\n\t\t\t\treturn workflowConfig.permissionMode\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to current defaults\n\t\tif (type === 'issue') {\n\t\t\treturn 'acceptEdits'\n\t\t}\n\t\t// For PR and regular workflows, use default permissions\n\t\treturn 'default'\n\t}\n\n\t/**\n\t * Auto-detect workspace context from current directory and git branch\n\t *\n\t * Detection priority:\n\t * 1. Directory name patterns (_pr_N, issue-N)\n\t * 2. Git branch name patterns\n\t * 3. Fallback to 'regular' workflow\n\t *\n\t * This leverages the same logic as FinishCommand.autoDetectFromCurrentDirectory()\n\t */\n\tprivate async detectWorkspaceContext(): Promise<ClaudeWorkflowOptions> {\n\t\tconst workspacePath = process.cwd()\n\t\tconst currentDir = path.basename(workspacePath)\n\n\t\t// Check for PR worktree pattern: _pr_N suffix\n\t\t// Pattern: /.*_pr_(\\d+)$/\n\t\tconst prPattern = /_pr_(\\d+)$/\n\t\tconst prMatch = currentDir.match(prPattern)\n\n\t\tif (prMatch?.[1]) {\n\t\t\tconst prNumber = parseInt(prMatch[1], 10)\n\t\t\tlogger.debug(`Auto-detected PR #${prNumber} from directory: ${currentDir}`)\n\n\t\t\treturn this.buildContextForPR(prNumber, workspacePath)\n\t\t}\n\n\t\t// Check for issue pattern in directory name\n\t\t// Pattern: /issue-(\\d+)/\n\t\tconst issuePattern = /issue-(\\d+)/\n\t\tconst issueMatch = currentDir.match(issuePattern)\n\n\t\tif (issueMatch?.[1]) {\n\t\t\tconst issueNumber = parseInt(issueMatch[1], 10)\n\t\t\tlogger.debug(`Auto-detected issue #${issueNumber} from directory: ${currentDir}`)\n\n\t\t\treturn this.buildContextForIssue(issueNumber, workspacePath)\n\t\t}\n\n\t\t// Fallback: Try to extract from git branch name\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tconst currentBranch = repoInfo.currentBranch\n\n\t\t\tif (currentBranch) {\n\t\t\t\t// Try to extract issue from branch name\n\t\t\t\tconst branchIssueMatch = currentBranch.match(issuePattern)\n\t\t\t\tif (branchIssueMatch?.[1]) {\n\t\t\t\t\tconst issueNumber = parseInt(branchIssueMatch[1], 10)\n\t\t\t\t\tlogger.debug(`Auto-detected issue #${issueNumber} from branch: ${currentBranch}`)\n\n\t\t\t\t\treturn this.buildContextForIssue(issueNumber, workspacePath, currentBranch)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Git command failed - not a git repo or other git error\n\t\t\tlogger.debug('Could not detect from git branch', { error })\n\t\t}\n\n\t\t// Last resort: use regular workflow\n\t\tlogger.debug('No specific context detected, using regular workflow')\n\t\treturn this.buildContextForRegular(workspacePath)\n\t}\n\n\t/**\n\t * Build context for issue workflow\n\t */\n\tprivate async buildContextForIssue(\n\t\tissueNumber: number,\n\t\tworkspacePath: string,\n\t\tbranchName?: string\n\t): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name if not provided\n\t\tif (!branchName) {\n\t\t\ttry {\n\t\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t\t} catch {\n\t\t\t\t// Ignore git errors\n\t\t\t}\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'issue',\n\t\t\tissueNumber,\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Build context for PR workflow\n\t */\n\tprivate async buildContextForPR(\n\t\tprNumber: number,\n\t\tworkspacePath: string\n\t): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name\n\t\tlet branchName: string | undefined\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t} catch {\n\t\t\t// Ignore git errors\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'pr',\n\t\t\tprNumber,\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Build context for regular workflow\n\t */\n\tprivate async buildContextForRegular(workspacePath: string): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name\n\t\tlet branchName: string | undefined\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t} catch {\n\t\t\t// Ignore git errors\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'regular',\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Get PORT from environment variables\n\t * Returns undefined if PORT is not set or invalid\n\t */\n\tprivate getPortFromEnv(): number | undefined {\n\t\tconst portStr = process.env.PORT\n\t\tif (!portStr) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst port = parseInt(portStr, 10)\n\t\tif (isNaN(port)) {\n\t\t\tlogger.warn(`Invalid PORT environment variable: ${portStr}`)\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn port\n\t}\n\n\n\t/**\n\t * Build user prompt based on one-shot mode\n\t */\n\tprivate buildUserPrompt(oneShot: import('../types/index.js').OneShotMode = 'default'): string {\n\t\t// For one-shot modes, add bypass instructions to override template approval requirements\n\t\tif (oneShot === 'noReview' || oneShot === 'bypassPermissions') {\n\t\t\treturn 'Guide the user through the iloom workflow! The user has requested you move through the workflow without awaiting confirmation. This supersedes any other guidance.'\n\t\t}\n\n\t\t// Default mode: simple \"Go!\" prompt\n\t\treturn 'Guide the user through the iloom workflow!'\n\t}\n}\n"],"mappings":"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","names":[]}