@iloom/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +24 -0
  2. package/dist/{BranchNamingService-TOM2KAUT.js → BranchNamingService-GCCWB3LK.js} +2 -2
  3. package/dist/ClaudeContextManager-DQFKIMEP.js +16 -0
  4. package/dist/ClaudeService-CJS32WG2.js +15 -0
  5. package/dist/{LoomLauncher-SJBZFZXE.js → LoomLauncher-4UG2E4CD.js} +40 -32
  6. package/dist/LoomLauncher-4UG2E4CD.js.map +1 -0
  7. package/dist/MetadataManager-WXUVXKUS.js +10 -0
  8. package/dist/PRManager-7DSIMCAD.js +16 -0
  9. package/dist/{PromptTemplateManager-2TDZAUC6.js → PromptTemplateManager-72FEOGT6.js} +2 -2
  10. package/dist/README.md +24 -0
  11. package/dist/{SettingsManager-FJFU6JJD.js → SettingsManager-XPR4TEQL.js} +2 -2
  12. package/dist/agents/iloom-issue-analyze-and-plan.md +41 -7
  13. package/dist/agents/iloom-issue-analyzer.md +38 -8
  14. package/dist/agents/iloom-issue-complexity-evaluator.md +45 -15
  15. package/dist/agents/iloom-issue-enhancer.md +60 -18
  16. package/dist/agents/iloom-issue-implementer.md +29 -7
  17. package/dist/agents/iloom-issue-planner.md +36 -7
  18. package/dist/agents/iloom-issue-reviewer.md +30 -7
  19. package/dist/{chunk-M5XUCTTJ.js → chunk-3CMGCRB5.js} +2 -2
  20. package/dist/{chunk-KM3W7YQX.js → chunk-4YTILIIH.js} +8 -9
  21. package/dist/chunk-4YTILIIH.js.map +1 -0
  22. package/dist/{chunk-HVGQP44L.js → chunk-AS2IRKLU.js} +2 -2
  23. package/dist/{chunk-ADDNFQJ4.js → chunk-CDQEK2WD.js} +6 -6
  24. package/dist/{chunk-74VMN2KC.js → chunk-DKQ4SUII.js} +16 -1
  25. package/dist/chunk-DKQ4SUII.js.map +1 -0
  26. package/dist/{chunk-LTNDJMTH.js → chunk-GVRO4PWE.js} +13 -9
  27. package/dist/chunk-GVRO4PWE.js.map +1 -0
  28. package/dist/{chunk-HHDSIE72.js → chunk-HABINPX2.js} +72 -16
  29. package/dist/{chunk-HHDSIE72.js.map → chunk-HABINPX2.js.map} +1 -1
  30. package/dist/{chunk-TR5MC2U6.js → chunk-LN4H3A6A.js} +66 -7
  31. package/dist/chunk-LN4H3A6A.js.map +1 -0
  32. package/dist/{chunk-VWNS6DH5.js → chunk-OOU3DKNT.js} +13 -7
  33. package/dist/chunk-OOU3DKNT.js.map +1 -0
  34. package/dist/chunk-P2ZQ5LKB.js +347 -0
  35. package/dist/chunk-P2ZQ5LKB.js.map +1 -0
  36. package/dist/{chunk-P2WZIDF3.js → chunk-QIUJPPJQ.js} +2 -2
  37. package/dist/{chunk-HD5SUKI2.js → chunk-RFUOIUQF.js} +49 -6
  38. package/dist/{chunk-HD5SUKI2.js.map → chunk-RFUOIUQF.js.map} +1 -1
  39. package/dist/{chunk-OF7BNW4D.js → chunk-RJKMF6BC.js} +30 -4
  40. package/dist/chunk-RJKMF6BC.js.map +1 -0
  41. package/dist/{chunk-O7WHXLCB.js → chunk-RNZMHJK7.js} +18 -4
  42. package/dist/chunk-RNZMHJK7.js.map +1 -0
  43. package/dist/{chunk-75B2HZZ5.js → chunk-RUC7OULH.js} +2 -2
  44. package/dist/{chunk-F4J6KEL6.js → chunk-S65T4O6I.js} +2 -2
  45. package/dist/{chunk-NFVFVYAP.js → chunk-T5IIUG4Z.js} +109 -20
  46. package/dist/chunk-T5IIUG4Z.js.map +1 -0
  47. package/dist/{chunk-JJUPY5MM.js → chunk-VAYGNQTE.js} +2 -2
  48. package/dist/{chunk-S44CHE3G.js → chunk-VTXCGKV5.js} +2 -2
  49. package/dist/{chunk-MLS5FAV7.js → chunk-YZTDGPFB.js} +21 -1
  50. package/dist/chunk-YZTDGPFB.js.map +1 -0
  51. package/dist/{chunk-3NFBZRPR.js → chunk-Z5NXYJIG.js} +19 -1
  52. package/dist/chunk-Z5NXYJIG.js.map +1 -0
  53. package/dist/{claude-X7EBJRB2.js → claude-ACVXNB6N.js} +4 -4
  54. package/dist/{cleanup-7QVPYBJJ.js → cleanup-MIDJVSIU.js} +18 -18
  55. package/dist/cli.js +293 -372
  56. package/dist/cli.js.map +1 -1
  57. package/dist/{contribute-RZYCYUDX.js → contribute-RS3DO3WP.js} +4 -4
  58. package/dist/{dev-server-LOY7YWCP.js → dev-server-ASH7HJVI.js} +28 -14
  59. package/dist/dev-server-ASH7HJVI.js.map +1 -0
  60. package/dist/{feedback-562KPG5U.js → feedback-RVIGHBJG.js} +9 -8
  61. package/dist/{feedback-562KPG5U.js.map → feedback-RVIGHBJG.js.map} +1 -1
  62. package/dist/{git-OXJACVAU.js → git-OQAPUPLP.js} +16 -6
  63. package/dist/git-OQAPUPLP.js.map +1 -0
  64. package/dist/{ignite-VSIPGKKG.js → ignite-XJALWFAT.js} +60 -25
  65. package/dist/ignite-XJALWFAT.js.map +1 -0
  66. package/dist/index.d.ts +60 -7
  67. package/dist/index.js +107 -7
  68. package/dist/index.js.map +1 -1
  69. package/dist/init-F6PFMSU5.js +21 -0
  70. package/dist/init-F6PFMSU5.js.map +1 -0
  71. package/dist/mcp/recap-server.js +264 -0
  72. package/dist/mcp/recap-server.js.map +1 -0
  73. package/dist/{open-CX7HUE26.js → open-KW4NTLXH.js} +15 -16
  74. package/dist/{open-CX7HUE26.js.map → open-KW4NTLXH.js.map} +1 -1
  75. package/dist/{projects-6DTNDVLH.js → projects-QEAEBAT2.js} +2 -2
  76. package/dist/prompts/init-prompt.txt +31 -72
  77. package/dist/prompts/issue-prompt.txt +115 -15
  78. package/dist/prompts/pr-prompt.txt +49 -1
  79. package/dist/prompts/regular-prompt.txt +80 -20
  80. package/dist/{rebase-55URTXZC.js → rebase-WZHHE5LU.js} +9 -9
  81. package/dist/recap-33NPZ3ZO.js +117 -0
  82. package/dist/recap-33NPZ3ZO.js.map +1 -0
  83. package/dist/{run-DP2U2CA2.js → run-HRYQ7TR7.js} +15 -16
  84. package/dist/{run-DP2U2CA2.js.map → run-HRYQ7TR7.js.map} +1 -1
  85. package/dist/schema/settings.schema.json +13 -2
  86. package/dist/shell-JMU5XTHW.js +240 -0
  87. package/dist/shell-JMU5XTHW.js.map +1 -0
  88. package/dist/{summary-J3CJSM7L.js → summary-4SSGGH7N.js} +20 -12
  89. package/dist/summary-4SSGGH7N.js.map +1 -0
  90. package/dist/{test-git-QLAIBJLX.js → test-git-6SAIRBUD.js} +4 -4
  91. package/dist/{test-prefix-6YM2ZOON.js → test-prefix-RLVRK5ZD.js} +4 -4
  92. package/dist/{test-tabs-JGO3VOXJ.js → test-tabs-3SCJWRKT.js} +3 -3
  93. package/package.json +1 -1
  94. package/dist/ClaudeContextManager-VEGJTS5E.js +0 -16
  95. package/dist/ClaudeService-ICSHJMQ5.js +0 -15
  96. package/dist/LoomLauncher-SJBZFZXE.js.map +0 -1
  97. package/dist/chunk-3NFBZRPR.js.map +0 -1
  98. package/dist/chunk-74VMN2KC.js.map +0 -1
  99. package/dist/chunk-KM3W7YQX.js.map +0 -1
  100. package/dist/chunk-LTNDJMTH.js.map +0 -1
  101. package/dist/chunk-MLS5FAV7.js.map +0 -1
  102. package/dist/chunk-NFVFVYAP.js.map +0 -1
  103. package/dist/chunk-O7WHXLCB.js.map +0 -1
  104. package/dist/chunk-OF7BNW4D.js.map +0 -1
  105. package/dist/chunk-QRBOPFAA.js +0 -48
  106. package/dist/chunk-QRBOPFAA.js.map +0 -1
  107. package/dist/chunk-TR5MC2U6.js.map +0 -1
  108. package/dist/chunk-VWNS6DH5.js.map +0 -1
  109. package/dist/dev-server-LOY7YWCP.js.map +0 -1
  110. package/dist/ignite-VSIPGKKG.js.map +0 -1
  111. package/dist/init-SCR2LQ4A.js +0 -21
  112. package/dist/summary-J3CJSM7L.js.map +0 -1
  113. /package/dist/{BranchNamingService-TOM2KAUT.js.map → BranchNamingService-GCCWB3LK.js.map} +0 -0
  114. /package/dist/{ClaudeContextManager-VEGJTS5E.js.map → ClaudeContextManager-DQFKIMEP.js.map} +0 -0
  115. /package/dist/{ClaudeService-ICSHJMQ5.js.map → ClaudeService-CJS32WG2.js.map} +0 -0
  116. /package/dist/{PromptTemplateManager-2TDZAUC6.js.map → MetadataManager-WXUVXKUS.js.map} +0 -0
  117. /package/dist/{SettingsManager-FJFU6JJD.js.map → PRManager-7DSIMCAD.js.map} +0 -0
  118. /package/dist/{claude-X7EBJRB2.js.map → PromptTemplateManager-72FEOGT6.js.map} +0 -0
  119. /package/dist/{git-OXJACVAU.js.map → SettingsManager-XPR4TEQL.js.map} +0 -0
  120. /package/dist/{chunk-M5XUCTTJ.js.map → chunk-3CMGCRB5.js.map} +0 -0
  121. /package/dist/{chunk-HVGQP44L.js.map → chunk-AS2IRKLU.js.map} +0 -0
  122. /package/dist/{chunk-ADDNFQJ4.js.map → chunk-CDQEK2WD.js.map} +0 -0
  123. /package/dist/{chunk-P2WZIDF3.js.map → chunk-QIUJPPJQ.js.map} +0 -0
  124. /package/dist/{chunk-75B2HZZ5.js.map → chunk-RUC7OULH.js.map} +0 -0
  125. /package/dist/{chunk-F4J6KEL6.js.map → chunk-S65T4O6I.js.map} +0 -0
  126. /package/dist/{chunk-JJUPY5MM.js.map → chunk-VAYGNQTE.js.map} +0 -0
  127. /package/dist/{chunk-S44CHE3G.js.map → chunk-VTXCGKV5.js.map} +0 -0
  128. /package/dist/{init-SCR2LQ4A.js.map → claude-ACVXNB6N.js.map} +0 -0
  129. /package/dist/{cleanup-7QVPYBJJ.js.map → cleanup-MIDJVSIU.js.map} +0 -0
  130. /package/dist/{contribute-RZYCYUDX.js.map → contribute-RS3DO3WP.js.map} +0 -0
  131. /package/dist/{projects-6DTNDVLH.js.map → projects-QEAEBAT2.js.map} +0 -0
  132. /package/dist/{rebase-55URTXZC.js.map → rebase-WZHHE5LU.js.map} +0 -0
  133. /package/dist/{test-git-QLAIBJLX.js.map → test-git-6SAIRBUD.js.map} +0 -0
  134. /package/dist/{test-prefix-6YM2ZOON.js.map → test-prefix-RLVRK5ZD.js.map} +0 -0
  135. /package/dist/{test-tabs-JGO3VOXJ.js.map → test-tabs-3SCJWRKT.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/SettingsManager.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport path from 'path'\nimport os from 'os'\nimport { z } from 'zod'\nimport deepmerge from 'deepmerge'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Zod schema for agent settings\n */\nexport const AgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.optional()\n\t\t.describe('Claude model shorthand: sonnet, opus, or haiku'),\n\t// Future: could add other per-agent overrides\n})\n\n/**\n * Zod schema for spin agent settings with default model\n * Used for the spin orchestrator configuration\n */\nexport const SpinAgentSettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('opus')\n\t\t.describe('Claude model shorthand for spin orchestrator'),\n})\n\n/**\n * Zod schema for summary settings with default model\n * Used for session summary generation configuration\n */\nexport const SummarySettingsSchema = z.object({\n\tmodel: z\n\t\t.enum(['sonnet', 'opus', 'haiku'])\n\t\t.default('sonnet')\n\t\t.describe('Claude model shorthand for session summary generation'),\n})\n\n/**\n * Zod schema for workflow permission configuration\n */\nexport const WorkflowPermissionSchema = z.object({\n\tpermissionMode: z\n\t\t.enum(['plan', 'acceptEdits', 'bypassPermissions', 'default'])\n\t\t.optional()\n\t\t.describe('Permission mode for Claude CLI in this workflow type'),\n\tnoVerify: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Skip pre-commit hooks (--no-verify) when committing during finish workflow'),\n\tstartIde: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Launch Claude Code agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe('Launch terminal window without dev server when starting this workflow type'),\n\tgenerateSummary: z\n\t\t.boolean()\n\t\t.default(true)\n\t\t.describe('Generate and post Claude session summary when finishing this workflow type'),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const WorkflowPermissionSchemaNoDefaults = z.object({\n\tpermissionMode: z\n\t\t.enum(['plan', 'acceptEdits', 'bypassPermissions', 'default'])\n\t\t.optional()\n\t\t.describe('Permission mode for Claude CLI in this workflow type'),\n\tnoVerify: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Skip pre-commit hooks (--no-verify) when committing during finish workflow'),\n\tstartIde: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch IDE (code) when starting this workflow type'),\n\tstartDevServer: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch development server when starting this workflow type'),\n\tstartAiAgent: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch Claude Code agent when starting this workflow type'),\n\tstartTerminal: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Launch terminal window without dev server when starting this workflow type'),\n\tgenerateSummary: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe('Generate and post Claude session summary when finishing this workflow type'),\n})\n\n/**\n * Zod schema for workflows settings\n */\nexport const WorkflowsSettingsSchema = z\n\t.object({\n\t\tissue: WorkflowPermissionSchema.optional(),\n\t\tpr: WorkflowPermissionSchema.optional(),\n\t\tregular: WorkflowPermissionSchema.optional(),\n\t})\n\t.optional()\n\n/**\n * Non-defaulting variant for pre-merge validation\n */\nexport const WorkflowsSettingsSchemaNoDefaults = z\n\t.object({\n\t\tissue: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tpr: WorkflowPermissionSchemaNoDefaults.optional(),\n\t\tregular: WorkflowPermissionSchemaNoDefaults.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for capabilities settings\n */\nexport const CapabilitiesSettingsSchema = z\n\t.object({\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t})\n\t\t\t.optional(),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.default('DATABASE_URL')\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Non-defaulting variant for pre-merge validation\n */\nexport const CapabilitiesSettingsSchemaNoDefaults = z\n\t.object({\n\t\tweb: z\n\t\t\t.object({\n\t\t\t\tbasePort: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.min(1, 'Base port must be >= 1')\n\t\t\t\t\t.max(65535, 'Base port must be <= 65535')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Base port for web workspace port calculations (default: 3000)'),\n\t\t\t})\n\t\t\t.optional(),\n\t\tdatabase: z\n\t\t\t.object({\n\t\t\t\tdatabaseUrlEnvVarName: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.min(1, 'Database URL variable name cannot be empty')\n\t\t\t\t\t.regex(/^[A-Z_][A-Z0-9_]*$/, 'Must be valid env var name (uppercase, underscores)')\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Name of environment variable for database connection URL'),\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.optional()\n\n/**\n * Zod schema for Neon database provider settings\n */\nexport const NeonSettingsSchema = z.object({\n\tprojectId: z\n\t\t.string()\n\t\t.min(1)\n\t\t.regex(/^[a-zA-Z0-9-]+$/, 'Neon project ID must contain only letters, numbers, and hyphens')\n\t\t.describe('Neon project ID found in your project URL (e.g., \"fantastic-fox-3566354\")'),\n\tparentBranch: z\n\t\t.string()\n\t\t.min(1)\n\t\t.describe('Branch from which new database branches are created'),\n})\n\n/**\n * Zod schema for database provider settings\n */\nexport const DatabaseProvidersSettingsSchema = z\n\t.object({\n\t\tneon: NeonSettingsSchema.optional().describe(\n\t\t\t'Neon database configuration. Requires Neon CLI installed and authenticated for database branching.',\n\t\t),\n\t})\n\t.optional()\n\n/**\n * Zod schema for iloom settings\n */\nexport const IloomSettingsSchema = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tworkflows: WorkflowsSettingsSchema.describe('Per-workflow-type permission configurations'),\n\tagents: z\n\t\t.record(z.string(), AgentSettingsSchema)\n\t\t.optional()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Per-agent configuration overrides. Available agents: ' +\n\t\t\t\t'iloom-issue-analyzer (analyzes issues), ' +\n\t\t\t\t'iloom-issue-planner (creates implementation plans), ' +\n\t\t\t\t'iloom-issue-analyze-and-plan (combined analysis and planning), ' +\n\t\t\t\t'iloom-issue-complexity-evaluator (evaluates complexity), ' +\n\t\t\t\t'iloom-issue-enhancer (enhances issue descriptions), ' +\n\t\t\t\t'iloom-issue-implementer (implements code changes), ' +\n\t\t\t\t'iloom-issue-reviewer (reviews code changes against requirements)',\n\t\t),\n\tspin: SpinAgentSettingsSchema.optional().describe(\n\t\t'Spin orchestrator configuration. Model defaults to opus when not configured.',\n\t),\n\tsummary: SummarySettingsSchema.optional().describe(\n\t\t'Session summary generation configuration. Model defaults to sonnet when not configured.',\n\t),\n\tcapabilities: CapabilitiesSettingsSchema.describe('Project capability configurations'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'linear']).optional().default('github').describe('Issue tracker provider (github, linear)'),\n\t\t\tgithub: z\n\t\t\t\t.object({\n\t\t\t\t\tremote: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Remote name cannot be empty')\n\t\t\t\t\t\t.describe('Git remote name to use for GitHub operations'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Issue management configuration'),\n\tmergeBehavior: z\n\t\t.object({\n\t\t\tmode: z.enum(['local', 'github-pr', 'github-draft-pr']).default('local'),\n\t\t\tremote: z.string().optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\n\t\t\t\t.default('vscode')\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor), antigravity (Antigravity IDE).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(false)\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control. ' +\n\t\t\t\t\t\t'Default is false for safety; enable via init or explicitly if .vscode is gitignored.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.default('upstreamOnly')\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\n\t\t),\n})\n\n/**\n * Non-defaulting variant for pre-merge validation\n * This prevents Zod from polluting partial settings with default values before merge\n */\nexport const IloomSettingsSchemaNoDefaults = z.object({\n\tmainBranch: z\n\t\t.string()\n\t\t.min(1, \"Settings 'mainBranch' cannot be empty\")\n\t\t.optional()\n\t\t.describe('Name of the main/primary branch for the repository'),\n\tsourceEnvOnStart: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). ' +\n\t\t\t\t'Files are sourced in precedence order so later files override earlier ones. ' +\n\t\t\t\t'NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. ' +\n\t\t\t\t'WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. ' +\n\t\t\t\t'Before enabling, verify ALL your .env.* files do not contain unquoted special characters ' +\n\t\t\t\t'(e.g., database URLs with ?, &, or other shell metacharacters). ' +\n\t\t\t\t'Shell compatibility issues may cause processes to fail or behave unexpectedly.',\n\t\t),\n\tworktreePrefix: z\n\t\t.string()\n\t\t.optional()\n\t\t.refine(\n\t\t\t(val) => {\n\t\t\t\tif (val === undefined) return true // undefined = use default calculation\n\t\t\t\tif (val === '') return true // empty string = no prefix mode\n\n\t\t\t\t// Allowlist: only alphanumeric, hyphens, underscores, and forward slashes\n\t\t\t\tconst allowedChars = /^[a-zA-Z0-9\\-_/]+$/\n\t\t\t\tif (!allowedChars.test(val)) return false\n\n\t\t\t\t// Reject if only special characters (no alphanumeric content)\n\t\t\t\tif (/^[-_/]+$/.test(val)) return false\n\n\t\t\t\t// Check each segment (split by /) contains at least one alphanumeric character\n\t\t\t\tconst segments = val.split('/')\n\t\t\t\tfor (const segment of segments) {\n\t\t\t\t\tif (segment && /^[-_]+$/.test(segment)) {\n\t\t\t\t\t\t// Segment exists but contains only hyphens/underscores\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage:\n\t\t\t\t\t\"worktreePrefix contains invalid characters. Only alphanumeric characters, hyphens (-), underscores (_), and forward slashes (/) are allowed. Use forward slashes for nested directories.\",\n\t\t\t},\n\t\t)\n\t\t.describe(\n\t\t\t'Prefix for worktree directories. Empty string disables prefix. Defaults to <repo-name>-looms if not set.',\n\t\t),\n\tprotectedBranches: z\n\t\t.array(z.string().min(1, 'Protected branch name cannot be empty'))\n\t\t.optional()\n\t\t.describe('List of branches that cannot be deleted (defaults to [mainBranch, \"main\", \"master\", \"develop\"])'),\n\tworkflows: WorkflowsSettingsSchemaNoDefaults.describe('Per-workflow-type permission configurations'),\n\tagents: z\n\t\t.record(z.string(), AgentSettingsSchema)\n\t\t.optional()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Per-agent configuration overrides. Available agents: ' +\n\t\t\t\t'iloom-issue-analyzer (analyzes issues), ' +\n\t\t\t\t'iloom-issue-planner (creates implementation plans), ' +\n\t\t\t\t'iloom-issue-analyze-and-plan (combined analysis and planning), ' +\n\t\t\t\t'iloom-issue-complexity-evaluator (evaluates complexity), ' +\n\t\t\t\t'iloom-issue-enhancer (enhances issue descriptions), ' +\n\t\t\t\t'iloom-issue-implementer (implements code changes), ' +\n\t\t\t\t'iloom-issue-reviewer (reviews code changes against requirements)',\n\t\t),\n\tspin: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Spin orchestrator configuration'),\n\tsummary: z\n\t\t.object({\n\t\t\tmodel: z.enum(['sonnet', 'opus', 'haiku']).optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Session summary generation configuration'),\n\tcapabilities: CapabilitiesSettingsSchemaNoDefaults.describe('Project capability configurations'),\n\tdatabaseProviders: DatabaseProvidersSettingsSchema.describe('Database provider configurations'),\n\tissueManagement: z\n\t\t.object({\n\t\t\tprovider: z.enum(['github', 'linear']).optional().describe('Issue tracker provider (github, linear)'),\n\t\t\tgithub: z\n\t\t\t\t.object({\n\t\t\t\t\tremote: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Remote name cannot be empty')\n\t\t\t\t\t\t.describe('Git remote name to use for GitHub operations'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tlinear: z\n\t\t\t\t.object({\n\t\t\t\t\tteamId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.min(1, 'Team ID cannot be empty')\n\t\t\t\t\t\t.describe('Linear team identifier (e.g., \"ENG\", \"PLAT\")'),\n\t\t\t\t\tbranchFormat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Branch naming template for Linear issues'),\n\t\t\t\t\tapiToken: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.optional()\n\t\t\t\t\t\t.describe('Linear API token (lin_api_...). SECURITY: Store in settings.local.json only, never commit to source control.'),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Issue management configuration'),\n\tmergeBehavior: z\n\t\t.object({\n\t\t\tmode: z.enum(['local', 'github-pr', 'github-draft-pr']).optional(),\n\t\t\tremote: z.string().optional(),\n\t\t})\n\t\t.optional()\n\t\t.describe('Merge behavior configuration: local (merge locally), github-pr (create PR), or github-draft-pr (create draft PR at start, mark ready on finish)'),\n\tide: z\n\t\t.object({\n\t\t\ttype: z\n\t\t\t\t.enum(['vscode', 'cursor', 'webstorm', 'sublime', 'intellij', 'windsurf', 'antigravity'])\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), ' +\n\t\t\t\t\t\t'webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), ' +\n\t\t\t\t\t\t'windsurf (Windsurf editor), antigravity (Antigravity IDE).'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe(\n\t\t\t'IDE configuration for workspace launches. Controls which editor opens when you start a loom. ' +\n\t\t\t\t'Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. ' +\n\t\t\t\t'Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity).'\n\t\t),\n\tcolors: z\n\t\t.object({\n\t\t\tterminal: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe('Apply terminal background colors based on branch name (macOS only)'),\n\t\t\tvscode: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t'Apply VSCode/Cursor title bar colors based on branch name. ' +\n\t\t\t\t\t\t'Note: This modifies .vscode/settings.json which may be in source control.'\n\t\t\t\t),\n\t\t})\n\t\t.optional()\n\t\t.describe('Color synchronization settings for workspace identification'),\n\tattribution: z\n\t\t.enum(['off', 'upstreamOnly', 'on'])\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Controls when iloom attribution appears in session summaries. ' +\n\t\t\t\t'\"off\" - never show attribution. ' +\n\t\t\t\t'\"upstreamOnly\" - only show for contributions to external repositories (e.g., open source). ' +\n\t\t\t\t'\"on\" - always show attribution.'\n\t\t),\n})\n\n/**\n * TypeScript type for Neon settings derived from Zod schema\n */\nexport type NeonSettings = z.infer<typeof NeonSettingsSchema>\n\n/**\n * TypeScript type for database providers settings derived from Zod schema\n */\nexport type DatabaseProvidersSettings = z.infer<typeof DatabaseProvidersSettingsSchema>\n\n/**\n * TypeScript type for agent settings derived from Zod schema\n */\nexport type AgentSettings = z.infer<typeof AgentSettingsSchema>\n\n/**\n * TypeScript type for spin agent settings derived from Zod schema\n */\nexport type SpinAgentSettings = z.infer<typeof SpinAgentSettingsSchema>\n\n/**\n * TypeScript type for summary settings derived from Zod schema\n */\nexport type SummarySettings = z.infer<typeof SummarySettingsSchema>\n\n/**\n * TypeScript type for workflow permission configuration derived from Zod schema\n */\nexport type WorkflowPermission = z.infer<typeof WorkflowPermissionSchema>\n\n/**\n * TypeScript type for workflows settings derived from Zod schema\n */\nexport type WorkflowsSettings = z.infer<typeof WorkflowsSettingsSchema>\n\n/**\n * TypeScript type for capabilities settings derived from Zod schema\n */\nexport type CapabilitiesSettings = z.infer<typeof CapabilitiesSettingsSchema>\n\n/**\n * TypeScript type for IDE settings derived from Zod schema\n */\nexport type IdeSettings = z.infer<typeof IloomSettingsSchema>['ide']\n\n/**\n * TypeScript type for iloom settings derived from Zod schema\n */\nexport type IloomSettings = z.infer<typeof IloomSettingsSchema>\n\n/**\n * Manages project-level settings from .iloom/settings.json\n */\nexport class SettingsManager {\n\t/**\n\t * Load settings from global, project, and local sources with proper precedence\n\t * Merge hierarchy (lowest to highest priority):\n\t * 1. Global settings (~/.config/iloom-ai/settings.json)\n\t * 2. Project settings (<PROJECT_ROOT>/.iloom/settings.json)\n\t * 3. Local settings (<PROJECT_ROOT>/.iloom/settings.local.json)\n\t * 4. CLI overrides (--set flags)\n\t * Returns empty object if all files don't exist (not an error)\n\t */\n\tasync loadSettings(\n\t\tprojectRoot?: string,\n\t\tcliOverrides?: Partial<IloomSettings>,\n\t): Promise<IloomSettings> {\n\t\tconst root = this.getProjectRoot(projectRoot)\n\n\t\t// Load global settings (lowest priority)\n\t\tconst globalSettings = await this.loadGlobalSettingsFile()\n\t\tconst globalSettingsPath = this.getGlobalSettingsPath()\n\t\tlogger.debug(`🌍 Global settings from ${globalSettingsPath}:`, JSON.stringify(globalSettings, null, 2))\n\n\t\t// Load base settings from settings.json\n\t\tconst baseSettings = await this.loadSettingsFile(root, 'settings.json')\n\t\tconst baseSettingsPath = path.join(root, '.iloom', 'settings.json')\n\t\tlogger.debug(`📄 Base settings from ${baseSettingsPath}:`, JSON.stringify(baseSettings, null, 2))\n\n\t\t// Load local overrides from settings.local.json\n\t\tconst localSettings = await this.loadSettingsFile(root, 'settings.local.json')\n\t\tconst localSettingsPath = path.join(root, '.iloom', 'settings.local.json')\n\t\tlogger.debug(`📄 Local settings from ${localSettingsPath}:`, JSON.stringify(localSettings, null, 2))\n\n\t\t// Deep merge with priority: cliOverrides > localSettings > baseSettings > globalSettings\n\t\tlet merged = this.mergeSettings(this.mergeSettings(globalSettings, baseSettings), localSettings)\n\t\tlogger.debug('🔄 After merging global + base + local settings:', JSON.stringify(merged, null, 2))\n\n\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\tlogger.debug('⚙️ CLI overrides to apply:', JSON.stringify(cliOverrides, null, 2))\n\t\t\tmerged = this.mergeSettings(merged, cliOverrides)\n\t\t\tlogger.debug('🔄 After applying CLI overrides:', JSON.stringify(merged, null, 2))\n\t\t}\n\n\t\t// Validate merged result\n\t\ttry {\n\t\t\tconst finalSettings = IloomSettingsSchema.parse(merged)\n\n\t\t\t// Debug: Log final merged configuration\n\t\t\tthis.logFinalConfiguration(finalSettings)\n\n\t\t\treturn finalSettings\n\t\t} catch (error) {\n\t\t\t// Show all Zod validation errors\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, '<merged settings>')\n\t\t\t\t// Enhance error message if CLI overrides were applied\n\t\t\t\tif (cliOverrides && Object.keys(cliOverrides).length > 0) {\n\t\t\t\t\tthrow new Error(`${errorMsg.message}\\n\\nNote: CLI overrides were applied. Check your --set arguments.`)\n\t\t\t\t}\n\t\t\t\tthrow errorMsg\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Log the final merged configuration for debugging\n\t */\n\tprivate logFinalConfiguration(settings: IloomSettings): void {\n\t\tlogger.debug('📋 Final merged configuration:', JSON.stringify(settings, null, 2))\n\t}\n\n\t/**\n\t * Load and parse a single settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t * Uses non-defaulting schema to prevent polluting partial settings with defaults before merge\n\t */\n\tprivate async loadSettingsFile(\n\t\tprojectRoot: string,\n\t\tfilename: string,\n\t): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\n\t\tconst settingsPath = path.join(projectRoot, '.iloom', filename)\n\n\t\ttry {\n\t\t\tconst content = await readFile(settingsPath, 'utf-8')\n\t\t\tlet parsed: unknown\n\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(content)\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to parse settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}`,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Basic type checking - ensure it's an object, but don't validate schema completeness\n\t\t\t// Individual files may be incomplete (e.g., Linear config split between files)\n\t\t\t// Final validation will happen on the merged result in loadSettings()\n\t\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Settings validation failed at ${filename}:\\n - root: Expected object, received ${typeof parsed}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn parsed as z.infer<typeof IloomSettingsSchemaNoDefaults>\n\t\t} catch (error) {\n\t\t\t// File not found is not an error - return empty settings\n\t\t\tif ((error as { code?: string }).code === 'ENOENT') {\n\t\t\t\tlogger.debug(`No settings file found at ${settingsPath}, using defaults`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Re-throw parsing errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Deep merge two settings objects with priority to override\n\t * Uses deepmerge library with array replacement strategy\n\t */\n\tprivate mergeSettings(\n\t\tbase: Partial<IloomSettings> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t\toverride: Partial<IloomSettings> | z.infer<typeof IloomSettingsSchemaNoDefaults>,\n\t): IloomSettings {\n\t\t// Use deepmerge with array replacement (not concatenation)\n\t\t// Type assertion is safe because the merged result will be validated with IloomSettingsSchema\n\t\t// which applies all the defaults after merging\n\t\treturn deepmerge(base as Record<string, unknown>, override as Record<string, unknown>, {\n\t\t\t// Replace arrays instead of concatenating them\n\t\t\tarrayMerge: (_destinationArray, sourceArray) => sourceArray,\n\t\t}) as IloomSettings\n\t}\n\n\t/**\n\t * Format all Zod validation errors into a single error message\n\t */\n\tprivate formatAllZodErrors(error: z.ZodError, settingsPath: string): Error {\n\t\tconst errorMessages = error.issues.map(issue => {\n\t\t\tconst path = issue.path.length > 0 ? issue.path.join('.') : 'root'\n\t\t\treturn ` - ${path}: ${issue.message}`\n\t\t})\n\n\t\treturn new Error(\n\t\t\t`Settings validation failed at ${settingsPath}:\\n${errorMessages.join('\\n')}`,\n\t\t)\n\t}\n\n\t/**\n\t * Validate settings structure and model names using Zod schema\n\t * This method is kept for testing purposes but uses Zod internally\n\t * @internal - Only used in tests via bracket notation\n\t */\n\t// @ts-expect-error - Used in tests via bracket notation, TypeScript can't detect this usage\n\tprivate validateSettings(settings: IloomSettings): void {\n\t\ttry {\n\t\t\tIloomSettingsSchema.parse(settings)\n\t\t} catch (error) {\n\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\tthrow this.formatAllZodErrors(error, '<validation>')\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Get project root (defaults to process.cwd())\n\t */\n\tprivate getProjectRoot(projectRoot?: string): string {\n\t\treturn projectRoot ?? process.cwd()\n\t}\n\n\t/**\n\t * Get global config directory path (~/.config/iloom-ai)\n\t */\n\tprivate getGlobalConfigDir(): string {\n\t\treturn path.join(os.homedir(), '.config', 'iloom-ai')\n\t}\n\n\t/**\n\t * Get global settings file path (~/.config/iloom-ai/settings.json)\n\t */\n\tprivate getGlobalSettingsPath(): string {\n\t\treturn path.join(this.getGlobalConfigDir(), 'settings.json')\n\t}\n\n\t/**\n\t * Load and parse global settings file\n\t * Returns empty object if file doesn't exist (not an error)\n\t * Warns but returns empty object on validation/parse errors (graceful degradation)\n\t */\n\tprivate async loadGlobalSettingsFile(): Promise<z.infer<typeof IloomSettingsSchemaNoDefaults>> {\n\t\tconst settingsPath = this.getGlobalSettingsPath()\n\n\t\ttry {\n\t\t\tconst content = await readFile(settingsPath, 'utf-8')\n\t\t\tlet parsed: unknown\n\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(content)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to parse global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Invalid JSON'}. Ignoring global settings.`,\n\t\t\t\t)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Validate with non-defaulting schema\n\t\t\ttry {\n\t\t\t\tconst validated = IloomSettingsSchemaNoDefaults.strict().parse(parsed)\n\t\t\t\treturn validated\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof z.ZodError) {\n\t\t\t\t\tconst errorMsg = this.formatAllZodErrors(error, 'global settings')\n\t\t\t\t\tlogger.warn(`${errorMsg.message}. Ignoring global settings.`)\n\t\t\t\t} else {\n\t\t\t\t\tlogger.warn(`Validation error in global settings: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\t\t}\n\t\t\t\treturn {}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// File not found is not an error - return empty settings\n\t\t\tif ((error as { code?: string }).code === 'ENOENT') {\n\t\t\t\tlogger.debug(`No global settings file found at ${settingsPath}`)\n\t\t\t\treturn {}\n\t\t\t}\n\n\t\t\t// Other file system errors - warn and continue\n\t\t\tlogger.warn(`Error reading global settings file at ${settingsPath}: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)\n\t\t\treturn {}\n\t\t}\n\t}\n\n\t/**\n\t * Get effective protected branches list with mainBranch always included\n\t *\n\t * This method provides a single source of truth for protected branches logic:\n\t * 1. Use configured protectedBranches if provided\n\t * 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t * 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t *\n\t * @param projectRoot - Optional project root directory (defaults to process.cwd())\n\t * @returns Array of protected branch names with mainBranch guaranteed to be included\n\t */\n\tasync getProtectedBranches(projectRoot?: string): Promise<string[]> {\n\t\tconst settings = await this.loadSettings(projectRoot)\n\t\tconst mainBranch = settings.mainBranch ?? 'main'\n\n\t\t// Build protected branches list:\n\t\t// 1. Use configured protectedBranches if provided\n\t\t// 2. Otherwise use defaults: [mainBranch, 'main', 'master', 'develop']\n\t\t// 3. ALWAYS ensure mainBranch is included even if user configured custom list\n\t\tlet protectedBranches: string[]\n\t\tif (settings.protectedBranches) {\n\t\t\t// Use configured list but ensure mainBranch is always included\n\t\t\tprotectedBranches = settings.protectedBranches.includes(mainBranch)\n\t\t\t\t? settings.protectedBranches\n\t\t\t\t: [mainBranch, ...settings.protectedBranches]\n\t\t} else {\n\t\t\t// Use defaults with current mainBranch\n\t\t\tprotectedBranches = [mainBranch, 'main', 'master', 'develop']\n\t\t}\n\n\t\treturn protectedBranches\n\t}\n\n\t/**\n\t * Get the spin orchestrator model with default applied\n\t * Default is defined in SpinAgentSettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSpinModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.spin?.model ?? SpinAgentSettingsSchema.parse({}).model\n\t}\n\n\t/**\n\t * Get the session summary model with default applied\n\t * Default is defined in SummarySettingsSchema\n\t *\n\t * @param settings - Pre-loaded settings object\n\t * @returns Model shorthand ('opus', 'sonnet', or 'haiku')\n\t */\n\tgetSummaryModel(settings?: IloomSettings): 'sonnet' | 'opus' | 'haiku' {\n\t\treturn settings?.summary?.model ?? SummarySettingsSchema.parse({}).model\n\t}\n}\n"],"mappings":"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","names":["path"]}
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openBrowser
4
+ } from "./chunk-YETJNRQM.js";
5
+ import {
6
+ getConfiguredRepoFromSettings,
7
+ getEffectivePRTargetRemote,
8
+ parseGitRemotes
9
+ } from "./chunk-PSFVTBM7.js";
10
+ import {
11
+ executeGhCommand
12
+ } from "./chunk-KO2FOMHL.js";
13
+ import {
14
+ detectClaudeCli,
15
+ launchClaude
16
+ } from "./chunk-RUC7OULH.js";
17
+ import {
18
+ getLogger
19
+ } from "./chunk-6UIGZD2N.js";
20
+
21
+ // src/lib/PRManager.ts
22
+ var PRManager = class {
23
+ constructor(settings) {
24
+ this.settings = settings;
25
+ }
26
+ /**
27
+ * Check if a PR already exists for the given branch
28
+ * @param branchName - Branch to check
29
+ * @param cwd - Working directory
30
+ * @returns Existing PR info or null if none found
31
+ */
32
+ async checkForExistingPR(branchName, cwd) {
33
+ try {
34
+ const prList = await executeGhCommand(
35
+ ["pr", "list", "--head", branchName, "--state", "open", "--json", "number,url"],
36
+ cwd ? { cwd } : void 0
37
+ );
38
+ if (prList.length > 0) {
39
+ return prList[0] ?? null;
40
+ }
41
+ return null;
42
+ } catch (error) {
43
+ getLogger().debug("Error checking for existing PR", { error });
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Generate PR body using Claude if available, otherwise use simple template
49
+ * @param issueNumber - Issue number to include in body
50
+ * @param worktreePath - Path to worktree for context
51
+ * @returns PR body markdown
52
+ */
53
+ async generatePRBody(issueNumber, worktreePath) {
54
+ const hasClaudeCli = await detectClaudeCli();
55
+ if (hasClaudeCli) {
56
+ try {
57
+ const prompt = this.buildPRBodyPrompt(issueNumber);
58
+ const body2 = await launchClaude(prompt, {
59
+ headless: true,
60
+ addDir: worktreePath,
61
+ timeout: 3e4
62
+ });
63
+ if (body2 && typeof body2 === "string" && body2.trim()) {
64
+ const sanitized = this.sanitizeClaudeOutput(body2);
65
+ if (sanitized) {
66
+ return sanitized;
67
+ }
68
+ }
69
+ } catch (error) {
70
+ getLogger().debug("Claude PR body generation failed, using template", { error });
71
+ }
72
+ }
73
+ let body = "This PR contains changes from the iloom workflow.\n\n";
74
+ if (issueNumber) {
75
+ body += `Fixes #${issueNumber}`;
76
+ }
77
+ return body;
78
+ }
79
+ /**
80
+ * Build structured XML prompt for PR body generation
81
+ * Uses XML format for clear task definition and output expectations
82
+ */
83
+ buildPRBodyPrompt(issueNumber) {
84
+ const issueContext = issueNumber ? `
85
+ <IssueContext>
86
+ This PR is associated with GitHub issue #${issueNumber}.
87
+ Include "Fixes #${issueNumber}" at the end of the body on its own line.
88
+ </IssueContext>` : "";
89
+ return `<Task>
90
+ You are a software engineer writing a pull request body for this repository.
91
+ Examine the changes in the git repository and generate a concise, professional PR description.
92
+ </Task>
93
+
94
+ <Requirements>
95
+ <Format>Write 2-3 sentences summarizing what was changed and why.${issueNumber ? `
96
+
97
+ End with "Fixes #${issueNumber}" on its own line.` : ""}</Format>
98
+ <Tone>Professional and concise</Tone>
99
+ <Focus>Summarize the changes and their purpose</Focus>
100
+ <NoMeta>CRITICAL: Do NOT include ANY explanatory text, analysis, or meta-commentary. Output ONLY the raw PR body text.</NoMeta>
101
+ <Examples>
102
+ Good: "Add user authentication with JWT tokens to secure the API endpoints. This includes login and registration endpoints with proper password hashing.
103
+
104
+ Fixes #42"
105
+ Good: "Fix navigation bug in sidebar menu that caused incorrect highlighting on nested routes."
106
+ Bad: "Here's the PR body:
107
+
108
+ ---
109
+
110
+ Add user authentication..."
111
+ Bad: "Based on the changes, I'll write: Fix navigation bug..."
112
+ </Examples>
113
+ ${issueContext}
114
+ </Requirements>
115
+
116
+ <Output>
117
+ IMPORTANT: Your entire response will be used directly as the GitHub pull request body.
118
+ Do not include any explanatory text, headers, or separators before or after the body.
119
+ Start your response immediately with the PR body text.
120
+ </Output>`;
121
+ }
122
+ /**
123
+ * Sanitize Claude output to remove meta-commentary and clean formatting
124
+ * Handles cases where Claude includes explanatory text despite instructions
125
+ */
126
+ sanitizeClaudeOutput(rawOutput) {
127
+ let cleaned = rawOutput.trim();
128
+ const metaPatterns = [
129
+ /^.*?based on.*?changes.*?:/i,
130
+ /^.*?looking at.*?files.*?:/i,
131
+ /^.*?examining.*?:/i,
132
+ /^.*?analyzing.*?:/i,
133
+ /^.*?i'll.*?generate.*?:/i,
134
+ /^.*?let me.*?:/i,
135
+ /^.*?here.*?is.*?(?:the\s+)?(?:pr|pull request).*?body.*?:/i,
136
+ /^.*?here's.*?(?:the\s+)?(?:pr|pull request).*?body.*?:/i
137
+ ];
138
+ for (const pattern of metaPatterns) {
139
+ cleaned = cleaned.replace(pattern, "").trim();
140
+ }
141
+ cleaned = cleaned.replace(/^[-=]{3,}\s*/m, "").trim();
142
+ if (cleaned.includes(":")) {
143
+ const colonIndex = cleaned.indexOf(":");
144
+ const beforeColon = cleaned.substring(0, colonIndex).trim().toLowerCase();
145
+ const metaIndicators = [
146
+ "here is the pr body",
147
+ "here is the pull request body",
148
+ "pr body",
149
+ "pull request body",
150
+ "here is",
151
+ "here's",
152
+ "the body should be",
153
+ "i suggest",
154
+ "my suggestion"
155
+ ];
156
+ const isMetaCommentary = metaIndicators.some((indicator) => beforeColon.includes(indicator));
157
+ if (isMetaCommentary) {
158
+ const afterColon = cleaned.substring(colonIndex + 1).trim();
159
+ const afterSeparator = afterColon.replace(/^[-=]{3,}\s*/m, "").trim();
160
+ if (afterSeparator && afterSeparator.length > 10) {
161
+ cleaned = afterSeparator;
162
+ }
163
+ }
164
+ }
165
+ if (cleaned.startsWith('"') && cleaned.endsWith('"') || cleaned.startsWith("'") && cleaned.endsWith("'")) {
166
+ cleaned = cleaned.slice(1, -1).trim();
167
+ }
168
+ return cleaned;
169
+ }
170
+ /**
171
+ * Create a GitHub PR for the branch
172
+ * @param branchName - Branch to create PR from (used as --head)
173
+ * @param title - PR title
174
+ * @param body - PR body
175
+ * @param baseBranch - Base branch to target (usually main/master)
176
+ * @param cwd - Working directory
177
+ * @returns PR URL
178
+ */
179
+ async createPR(branchName, title, body, baseBranch, cwd) {
180
+ try {
181
+ const targetRemote = await getEffectivePRTargetRemote(this.settings, cwd);
182
+ let headValue = branchName;
183
+ if (targetRemote !== "origin") {
184
+ const remotes = await parseGitRemotes(cwd);
185
+ const originRemote = remotes.find((r) => r.name === "origin");
186
+ if (originRemote) {
187
+ headValue = `${originRemote.owner}:${branchName}`;
188
+ getLogger().debug(`Fork workflow detected, using head: ${headValue}`);
189
+ }
190
+ }
191
+ const args = ["pr", "create", "--head", headValue, "--title", title, "--body", body, "--base", baseBranch];
192
+ if (targetRemote !== "origin") {
193
+ const repo = await getConfiguredRepoFromSettings(this.settings, cwd);
194
+ args.push("--repo", repo);
195
+ }
196
+ const result = await executeGhCommand(args, cwd ? { cwd } : void 0);
197
+ const url = typeof result === "string" ? result.trim() : String(result).trim();
198
+ if (!url.includes("github.com") || !url.includes("/pull/")) {
199
+ throw new Error(`Unexpected response from gh pr create: ${url}`);
200
+ }
201
+ return url;
202
+ } catch (error) {
203
+ const errorMessage = error instanceof Error ? error.message : String(error);
204
+ if (errorMessage.includes("Head sha can't be blank") || errorMessage.includes("No commits between")) {
205
+ throw new Error(
206
+ `Failed to create pull request: ${errorMessage}
207
+
208
+ This error typically occurs when:
209
+ - The branch was not fully pushed to the remote
210
+ - There's a race condition between push and PR creation
211
+ - The branch has no commits ahead of the base branch
212
+
213
+ Try running: git push -u origin ${branchName}
214
+ Then retry: il finish`
215
+ );
216
+ }
217
+ throw new Error(`Failed to create pull request: ${errorMessage}`);
218
+ }
219
+ }
220
+ /**
221
+ * Open PR URL in browser
222
+ * @param url - PR URL to open
223
+ */
224
+ async openPRInBrowser(url) {
225
+ try {
226
+ await openBrowser(url);
227
+ getLogger().debug("Opened PR in browser", { url });
228
+ } catch (error) {
229
+ getLogger().warn("Failed to open PR in browser", { error });
230
+ }
231
+ }
232
+ /**
233
+ * Complete PR workflow: check for existing, create if needed, optionally open in browser
234
+ * @param branchName - Branch to create PR from
235
+ * @param title - PR title
236
+ * @param issueNumber - Optional issue number for body generation
237
+ * @param baseBranch - Base branch to target
238
+ * @param worktreePath - Path to worktree
239
+ * @param openInBrowser - Whether to open PR in browser
240
+ * @returns PR creation result
241
+ */
242
+ async createOrOpenPR(branchName, title, issueNumber, baseBranch, worktreePath, openInBrowser) {
243
+ const existingPR = await this.checkForExistingPR(branchName, worktreePath);
244
+ if (existingPR) {
245
+ getLogger().info(`Pull request already exists: ${existingPR.url}`);
246
+ if (openInBrowser) {
247
+ await this.openPRInBrowser(existingPR.url);
248
+ }
249
+ return {
250
+ url: existingPR.url,
251
+ number: existingPR.number,
252
+ wasExisting: true
253
+ };
254
+ }
255
+ const body = await this.generatePRBody(issueNumber, worktreePath);
256
+ getLogger().info("Creating pull request...");
257
+ const url = await this.createPR(branchName, title, body, baseBranch, worktreePath);
258
+ const prNumber = this.extractPRNumberFromUrl(url);
259
+ if (openInBrowser) {
260
+ await this.openPRInBrowser(url);
261
+ }
262
+ return {
263
+ url,
264
+ number: prNumber,
265
+ wasExisting: false
266
+ };
267
+ }
268
+ /**
269
+ * Extract PR number from GitHub PR URL
270
+ * @param url - PR URL (e.g., https://github.com/owner/repo/pull/123)
271
+ * @returns PR number
272
+ */
273
+ extractPRNumberFromUrl(url) {
274
+ const match = url.match(/\/pull\/(\d+)/);
275
+ if (match == null ? void 0 : match[1]) {
276
+ return parseInt(match[1], 10);
277
+ }
278
+ throw new Error(`Could not extract PR number from URL: ${url}`);
279
+ }
280
+ /**
281
+ * Create a draft PR for the branch
282
+ * Used by github-draft-pr mode during il start
283
+ * @param branchName - Branch to create PR from (used as --head)
284
+ * @param title - PR title
285
+ * @param body - PR body
286
+ * @param baseBranch - Base branch to target (usually main/master)
287
+ * @param cwd - Working directory
288
+ * @returns PR URL and number
289
+ */
290
+ async createDraftPR(branchName, title, body, baseBranch, cwd) {
291
+ try {
292
+ const targetRemote = await getEffectivePRTargetRemote(this.settings, cwd);
293
+ let headValue = branchName;
294
+ if (targetRemote !== "origin") {
295
+ const remotes = await parseGitRemotes(cwd);
296
+ const originRemote = remotes.find((r) => r.name === "origin");
297
+ if (originRemote) {
298
+ headValue = `${originRemote.owner}:${branchName}`;
299
+ getLogger().debug(`Fork workflow detected, using head: ${headValue}`);
300
+ }
301
+ }
302
+ const args = ["pr", "create", "--head", headValue, "--title", title, "--body", body, "--base", baseBranch, "--draft"];
303
+ if (targetRemote !== "origin") {
304
+ const repo = await getConfiguredRepoFromSettings(this.settings, cwd);
305
+ args.push("--repo", repo);
306
+ }
307
+ const result = await executeGhCommand(args, cwd ? { cwd } : void 0);
308
+ const url = typeof result === "string" ? result.trim() : String(result).trim();
309
+ if (!url.includes("github.com") || !url.includes("/pull/")) {
310
+ throw new Error(`Unexpected response from gh pr create --draft: ${url}`);
311
+ }
312
+ const number = this.extractPRNumberFromUrl(url);
313
+ return { url, number };
314
+ } catch (error) {
315
+ const errorMessage = error instanceof Error ? error.message : String(error);
316
+ if (errorMessage.includes("Head sha can't be blank") || errorMessage.includes("No commits between")) {
317
+ throw new Error(
318
+ `Failed to create draft pull request: ${errorMessage}
319
+
320
+ This error typically occurs when:
321
+ - The branch was not fully pushed to the remote
322
+ - The branch has no commits ahead of the base branch
323
+
324
+ Try running: git push -u origin ${branchName}
325
+ Then retry: il start`
326
+ );
327
+ }
328
+ throw new Error(`Failed to create draft pull request: ${errorMessage}`);
329
+ }
330
+ }
331
+ /**
332
+ * Mark a draft PR as ready for review
333
+ * Used by github-draft-pr mode during il finish
334
+ * @param prNumber - PR number to mark ready
335
+ * @param cwd - Working directory
336
+ */
337
+ async markPRReady(prNumber, cwd) {
338
+ const args = ["pr", "ready", String(prNumber)];
339
+ await executeGhCommand(args, cwd ? { cwd } : void 0);
340
+ getLogger().info(`Marked PR #${prNumber} as ready for review`);
341
+ }
342
+ };
343
+
344
+ export {
345
+ PRManager
346
+ };
347
+ //# sourceMappingURL=chunk-P2ZQ5LKB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/PRManager.ts"],"sourcesContent":["import { executeGhCommand } from '../utils/github.js'\nimport { launchClaude, detectClaudeCli } from '../utils/claude.js'\nimport { getEffectivePRTargetRemote, getConfiguredRepoFromSettings, parseGitRemotes } from '../utils/remote.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { getLogger } from '../utils/logger-context.js'\nimport type { IloomSettings } from './SettingsManager.js'\n\ninterface ExistingPR {\n\tnumber: number\n\turl: string\n}\n\ninterface PRCreationResult {\n\turl: string\n\tnumber: number\n\twasExisting: boolean\n}\n\nexport class PRManager {\n\tconstructor(private settings: IloomSettings) {\n\t\t// Uses getLogger() for all logging operations\n\t}\n\n\t/**\n\t * Check if a PR already exists for the given branch\n\t * @param branchName - Branch to check\n\t * @param cwd - Working directory\n\t * @returns Existing PR info or null if none found\n\t */\n\tasync checkForExistingPR(branchName: string, cwd?: string): Promise<ExistingPR | null> {\n\t\ttry {\n\t\t\tconst prList = await executeGhCommand<Array<{ number: number; url: string }>>(\n\t\t\t\t['pr', 'list', '--head', branchName, '--state', 'open', '--json', 'number,url'],\n\t\t\t\tcwd ? { cwd } : undefined\n\t\t\t)\n\n\t\t\tif (prList.length > 0) {\n\t\t\t\treturn prList[0] ?? null // Return first match\n\t\t\t}\n\n\t\t\treturn null\n\t\t} catch (error) {\n\t\tgetLogger().debug('Error checking for existing PR', { error })\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Generate PR body using Claude if available, otherwise use simple template\n\t * @param issueNumber - Issue number to include in body\n\t * @param worktreePath - Path to worktree for context\n\t * @returns PR body markdown\n\t */\n\tasync generatePRBody(issueNumber: string | number | undefined, worktreePath: string): Promise<string> {\n\t\t// Try Claude first for rich body generation\n\t\tconst hasClaudeCli = await detectClaudeCli()\n\n\t\tif (hasClaudeCli) {\n\t\t\ttry {\n\t\t\t\tconst prompt = this.buildPRBodyPrompt(issueNumber)\n\n\t\t\t\tconst body = await launchClaude(prompt, {\n\t\t\t\t\theadless: true,\n\t\t\t\t\taddDir: worktreePath,\n\t\t\t\t\ttimeout: 30000,\n\t\t\t\t})\n\n\t\t\t\tif (body && typeof body === 'string' && body.trim()) {\n\t\t\t\t\tconst sanitized = this.sanitizeClaudeOutput(body)\n\t\t\t\t\tif (sanitized) {\n\t\t\t\t\t\treturn sanitized\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\tgetLogger().debug('Claude PR body generation failed, using template', { error })\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to simple template\n\t\tlet body = 'This PR contains changes from the iloom workflow.\\n\\n'\n\n\t\tif (issueNumber) {\n\t\t\tbody += `Fixes #${issueNumber}`\n\t\t}\n\n\t\treturn body\n\t}\n\n\t/**\n\t * Build structured XML prompt for PR body generation\n\t * Uses XML format for clear task definition and output expectations\n\t */\n\tprivate buildPRBodyPrompt(issueNumber?: string | number): string {\n\t\tconst issueContext = issueNumber\n\t\t\t? `\\n<IssueContext>\nThis PR is associated with GitHub issue #${issueNumber}.\nInclude \"Fixes #${issueNumber}\" at the end of the body on its own line.\n</IssueContext>`\n\t\t\t: ''\n\n\t\treturn `<Task>\nYou are a software engineer writing a pull request body for this repository.\nExamine the changes in the git repository and generate a concise, professional PR description.\n</Task>\n\n<Requirements>\n<Format>Write 2-3 sentences summarizing what was changed and why.${issueNumber ? `\\n\\nEnd with \"Fixes #${issueNumber}\" on its own line.` : ''}</Format>\n<Tone>Professional and concise</Tone>\n<Focus>Summarize the changes and their purpose</Focus>\n<NoMeta>CRITICAL: Do NOT include ANY explanatory text, analysis, or meta-commentary. Output ONLY the raw PR body text.</NoMeta>\n<Examples>\nGood: \"Add user authentication with JWT tokens to secure the API endpoints. This includes login and registration endpoints with proper password hashing.\n\nFixes #42\"\nGood: \"Fix navigation bug in sidebar menu that caused incorrect highlighting on nested routes.\"\nBad: \"Here's the PR body:\\n\\n---\\n\\nAdd user authentication...\"\nBad: \"Based on the changes, I'll write: Fix navigation bug...\"\n</Examples>\n${issueContext}\n</Requirements>\n\n<Output>\nIMPORTANT: Your entire response will be used directly as the GitHub pull request body.\nDo not include any explanatory text, headers, or separators before or after the body.\nStart your response immediately with the PR body text.\n</Output>`\n\t}\n\n\t/**\n\t * Sanitize Claude output to remove meta-commentary and clean formatting\n\t * Handles cases where Claude includes explanatory text despite instructions\n\t */\n\tprivate sanitizeClaudeOutput(rawOutput: string): string {\n\t\tlet cleaned = rawOutput.trim()\n\n\t\t// Remove common meta-commentary patterns (case-insensitive)\n\t\tconst metaPatterns = [\n\t\t\t/^.*?based on.*?changes.*?:/i,\n\t\t\t/^.*?looking at.*?files.*?:/i,\n\t\t\t/^.*?examining.*?:/i,\n\t\t\t/^.*?analyzing.*?:/i,\n\t\t\t/^.*?i'll.*?generate.*?:/i,\n\t\t\t/^.*?let me.*?:/i,\n\t\t\t/^.*?here.*?is.*?(?:the\\s+)?(?:pr|pull request).*?body.*?:/i,\n\t\t\t/^.*?here's.*?(?:the\\s+)?(?:pr|pull request).*?body.*?:/i,\n\t\t]\n\n\t\tfor (const pattern of metaPatterns) {\n\t\t\tcleaned = cleaned.replace(pattern, '').trim()\n\t\t}\n\n\t\t// Remove leading separator lines (---, ===, etc.)\n\t\tcleaned = cleaned.replace(/^[-=]{3,}\\s*/m, '').trim()\n\n\t\t// Extract content after separators only if it looks like meta-commentary\n\t\tif (cleaned.includes(':')) {\n\t\t\tconst colonIndex = cleaned.indexOf(':')\n\t\t\tconst beforeColon = cleaned.substring(0, colonIndex).trim().toLowerCase()\n\n\t\t\t// Only split if the text before colon looks like meta-commentary\n\t\t\tconst metaIndicators = [\n\t\t\t\t'here is the pr body',\n\t\t\t\t'here is the pull request body',\n\t\t\t\t'pr body',\n\t\t\t\t'pull request body',\n\t\t\t\t'here is',\n\t\t\t\t\"here's\",\n\t\t\t\t'the body should be',\n\t\t\t\t'i suggest',\n\t\t\t\t'my suggestion'\n\t\t\t]\n\n\t\t\tconst isMetaCommentary = metaIndicators.some(indicator => beforeColon.includes(indicator))\n\n\t\t\tif (isMetaCommentary) {\n\t\t\t\tconst afterColon = cleaned.substring(colonIndex + 1).trim()\n\t\t\t\t// Remove leading separator after colon\n\t\t\t\tconst afterSeparator = afterColon.replace(/^[-=]{3,}\\s*/m, '').trim()\n\t\t\t\tif (afterSeparator && afterSeparator.length > 10) {\n\t\t\t\t\tcleaned = afterSeparator\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove quotes if the entire message is wrapped in them\n\t\tif ((cleaned.startsWith('\"') && cleaned.endsWith('\"')) ||\n\t\t\t(cleaned.startsWith(\"'\") && cleaned.endsWith(\"'\"))) {\n\t\t\tcleaned = cleaned.slice(1, -1).trim()\n\t\t}\n\n\t\treturn cleaned\n\t}\n\n\t/**\n\t * Create a GitHub PR for the branch\n\t * @param branchName - Branch to create PR from (used as --head)\n\t * @param title - PR title\n\t * @param body - PR body\n\t * @param baseBranch - Base branch to target (usually main/master)\n\t * @param cwd - Working directory\n\t * @returns PR URL\n\t */\n\tasync createPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tbody: string,\n\t\tbaseBranch: string,\n\t\tcwd?: string\n\t): Promise<string> {\n\t\ttry {\n\t\t\t// Get the target remote for the PR\n\t\t\tconst targetRemote = await getEffectivePRTargetRemote(this.settings, cwd)\n\n\t\t\t// Determine the correct --head value\n\t\t\t// For fork workflows (target != origin), we need \"username:branch\" format\n\t\t\t// See: https://github.com/cli/cli/issues/2691\n\t\t\tlet headValue = branchName\n\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\t// Fork workflow: need to specify the head as \"owner:branch\"\n\t\t\t\t// Get the owner of the origin remote (where we pushed the branch)\n\t\t\t\tconst remotes = await parseGitRemotes(cwd)\n\t\t\t\tconst originRemote = remotes.find(r => r.name === 'origin')\n\n\t\t\t\tif (originRemote) {\n\t\t\t\t\theadValue = `${originRemote.owner}:${branchName}`\n\t\t\t\tgetLogger().debug(`Fork workflow detected, using head: ${headValue}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Build gh pr create command\n\t\t\t// Note: gh pr create returns a plain URL string, not JSON\n\t\t\tconst args = ['pr', 'create', '--head', headValue, '--title', title, '--body', body, '--base', baseBranch]\n\n\t\t\t// If target remote is not 'origin', we need to specify the repo\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\tconst repo = await getConfiguredRepoFromSettings(this.settings, cwd)\n\t\t\t\targs.push('--repo', repo)\n\t\t\t}\n\n\t\t\t// gh pr create returns the PR URL as plain text (not JSON)\n\t\t\tconst result = await executeGhCommand<string>(args, cwd ? { cwd } : undefined)\n\n\t\t\t// Result is a string URL like \"https://github.com/owner/repo/pull/123\"\n\t\t\tconst url = typeof result === 'string' ? result.trim() : String(result).trim()\n\n\t\t\tif (!url.includes('github.com') || !url.includes('/pull/')) {\n\t\t\t\tthrow new Error(`Unexpected response from gh pr create: ${url}`)\n\t\t\t}\n\n\t\t\treturn url\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\n\t\t\t// Provide helpful error message for common GraphQL errors\n\t\t\tif (errorMessage.includes(\"Head sha can't be blank\") || errorMessage.includes(\"No commits between\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to create pull request: ${errorMessage}\\n\\n` +\n\t\t\t\t\t`This error typically occurs when:\\n` +\n\t\t\t\t\t` - The branch was not fully pushed to the remote\\n` +\n\t\t\t\t\t` - There's a race condition between push and PR creation\\n` +\n\t\t\t\t\t` - The branch has no commits ahead of the base branch\\n\\n` +\n\t\t\t\t\t`Try running: git push -u origin ${branchName}\\n` +\n\t\t\t\t\t`Then retry: il finish`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to create pull request: ${errorMessage}`)\n\t\t}\n\t}\n\n\t/**\n\t * Open PR URL in browser\n\t * @param url - PR URL to open\n\t */\n\tasync openPRInBrowser(url: string): Promise<void> {\n\t\ttry {\n\t\t\tawait openBrowser(url)\n\t\tgetLogger().debug('Opened PR in browser', { url })\n\t\t} catch (error) {\n\t\t\t// Don't fail the whole operation if browser opening fails\n\t\tgetLogger().warn('Failed to open PR in browser', { error })\n\t\t}\n\t}\n\n\t/**\n\t * Complete PR workflow: check for existing, create if needed, optionally open in browser\n\t * @param branchName - Branch to create PR from\n\t * @param title - PR title\n\t * @param issueNumber - Optional issue number for body generation\n\t * @param baseBranch - Base branch to target\n\t * @param worktreePath - Path to worktree\n\t * @param openInBrowser - Whether to open PR in browser\n\t * @returns PR creation result\n\t */\n\tasync createOrOpenPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tissueNumber: string | number | undefined,\n\t\tbaseBranch: string,\n\t\tworktreePath: string,\n\t\topenInBrowser: boolean\n\t): Promise<PRCreationResult> {\n\t\t// Check for existing PR\n\t\tconst existingPR = await this.checkForExistingPR(branchName, worktreePath)\n\n\t\tif (existingPR) {\n\t\tgetLogger().info(`Pull request already exists: ${existingPR.url}`)\n\n\t\t\tif (openInBrowser) {\n\t\t\t\tawait this.openPRInBrowser(existingPR.url)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\turl: existingPR.url,\n\t\t\t\tnumber: existingPR.number,\n\t\t\t\twasExisting: true,\n\t\t\t}\n\t\t}\n\n\t\t// Generate PR body\n\t\tconst body = await this.generatePRBody(issueNumber, worktreePath)\n\n\t\t// Create new PR\n\tgetLogger().info('Creating pull request...')\n\t\tconst url = await this.createPR(branchName, title, body, baseBranch, worktreePath)\n\n\t\t// Extract PR number from URL\n\t\tconst prNumber = this.extractPRNumberFromUrl(url)\n\n\t\tif (openInBrowser) {\n\t\t\tawait this.openPRInBrowser(url)\n\t\t}\n\n\t\treturn {\n\t\t\turl,\n\t\t\tnumber: prNumber,\n\t\t\twasExisting: false,\n\t\t}\n\t}\n\n\t/**\n\t * Extract PR number from GitHub PR URL\n\t * @param url - PR URL (e.g., https://github.com/owner/repo/pull/123)\n\t * @returns PR number\n\t */\n\tprivate extractPRNumberFromUrl(url: string): number {\n\t\tconst match = url.match(/\\/pull\\/(\\d+)/)\n\t\tif (match?.[1]) {\n\t\t\treturn parseInt(match[1], 10)\n\t\t}\n\t\tthrow new Error(`Could not extract PR number from URL: ${url}`)\n\t}\n\n\t/**\n\t * Create a draft PR for the branch\n\t * Used by github-draft-pr mode during il start\n\t * @param branchName - Branch to create PR from (used as --head)\n\t * @param title - PR title\n\t * @param body - PR body\n\t * @param baseBranch - Base branch to target (usually main/master)\n\t * @param cwd - Working directory\n\t * @returns PR URL and number\n\t */\n\tasync createDraftPR(\n\t\tbranchName: string,\n\t\ttitle: string,\n\t\tbody: string,\n\t\tbaseBranch: string,\n\t\tcwd?: string\n\t): Promise<{ url: string; number: number }> {\n\t\ttry {\n\t\t\t// Get the target remote for the PR\n\t\t\tconst targetRemote = await getEffectivePRTargetRemote(this.settings, cwd)\n\n\t\t\t// Determine the correct --head value\n\t\t\t// For fork workflows (target != origin), we need \"username:branch\" format\n\t\t\tlet headValue = branchName\n\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\t// Fork workflow: need to specify the head as \"owner:branch\"\n\t\t\t\tconst remotes = await parseGitRemotes(cwd)\n\t\t\t\tconst originRemote = remotes.find(r => r.name === 'origin')\n\n\t\t\t\tif (originRemote) {\n\t\t\t\t\theadValue = `${originRemote.owner}:${branchName}`\n\t\t\t\t\tgetLogger().debug(`Fork workflow detected, using head: ${headValue}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Build gh pr create command with --draft flag\n\t\t\tconst args = ['pr', 'create', '--head', headValue, '--title', title, '--body', body, '--base', baseBranch, '--draft']\n\n\t\t\t// If target remote is not 'origin', we need to specify the repo\n\t\t\tif (targetRemote !== 'origin') {\n\t\t\t\tconst repo = await getConfiguredRepoFromSettings(this.settings, cwd)\n\t\t\t\targs.push('--repo', repo)\n\t\t\t}\n\n\t\t\t// gh pr create returns the PR URL as plain text (not JSON)\n\t\t\tconst result = await executeGhCommand<string>(args, cwd ? { cwd } : undefined)\n\n\t\t\t// Result is a string URL like \"https://github.com/owner/repo/pull/123\"\n\t\t\tconst url = typeof result === 'string' ? result.trim() : String(result).trim()\n\n\t\t\tif (!url.includes('github.com') || !url.includes('/pull/')) {\n\t\t\t\tthrow new Error(`Unexpected response from gh pr create --draft: ${url}`)\n\t\t\t}\n\n\t\t\tconst number = this.extractPRNumberFromUrl(url)\n\n\t\t\treturn { url, number }\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\n\t\t\t// Provide helpful error message for common errors\n\t\t\tif (errorMessage.includes(\"Head sha can't be blank\") || errorMessage.includes(\"No commits between\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to create draft pull request: ${errorMessage}\\n\\n` +\n\t\t\t\t\t`This error typically occurs when:\\n` +\n\t\t\t\t\t` - The branch was not fully pushed to the remote\\n` +\n\t\t\t\t\t` - The branch has no commits ahead of the base branch\\n\\n` +\n\t\t\t\t\t`Try running: git push -u origin ${branchName}\\n` +\n\t\t\t\t\t`Then retry: il start`\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to create draft pull request: ${errorMessage}`)\n\t\t}\n\t}\n\n\t/**\n\t * Mark a draft PR as ready for review\n\t * Used by github-draft-pr mode during il finish\n\t * @param prNumber - PR number to mark ready\n\t * @param cwd - Working directory\n\t */\n\tasync markPRReady(prNumber: number, cwd?: string): Promise<void> {\n\t\tconst args = ['pr', 'ready', String(prNumber)]\n\t\tawait executeGhCommand(args, cwd ? { cwd } : undefined)\n\t\tgetLogger().info(`Marked PR #${prNumber} as ready for review`)\n\t}\n}\n"],"mappings":"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","names":["body"]}
@@ -15,7 +15,7 @@ var ClaudeBranchNameStrategy = class {
15
15
  this.claudeModel = claudeModel;
16
16
  }
17
17
  async generate(issueNumber, title) {
18
- const { generateBranchName } = await import("./claude-X7EBJRB2.js");
18
+ const { generateBranchName } = await import("./claude-ACVXNB6N.js");
19
19
  return generateBranchName(title, issueNumber, this.claudeModel);
20
20
  }
21
21
  };
@@ -52,4 +52,4 @@ export {
52
52
  ClaudeBranchNameStrategy,
53
53
  DefaultBranchNamingService
54
54
  };
55
- //# sourceMappingURL=chunk-P2WZIDF3.js.map
55
+ //# sourceMappingURL=chunk-QIUJPPJQ.js.map
@@ -403,11 +403,16 @@ var FirstRunManager = class {
403
403
 
404
404
  // src/utils/mcp.ts
405
405
  import path2 from "path";
406
- async function generateIssueManagementMcpConfig(contextType, repo, provider = "github", settings) {
406
+ import os2 from "os";
407
+ async function generateIssueManagementMcpConfig(contextType, repo, provider = "github", settings, draftPrNumber) {
407
408
  var _a, _b;
409
+ const effectiveContextType = draftPrNumber ? "pr" : contextType;
408
410
  let envVars = {
409
411
  ISSUE_PROVIDER: provider
410
412
  };
413
+ if (draftPrNumber) {
414
+ envVars.DRAFT_PR_NUMBER = String(draftPrNumber);
415
+ }
411
416
  if (provider === "github") {
412
417
  let owner;
413
418
  let name;
@@ -423,7 +428,7 @@ async function generateIssueManagementMcpConfig(contextType, repo, provider = "g
423
428
  owner = repoInfo.owner;
424
429
  name = repoInfo.name;
425
430
  }
426
- const githubEventName = contextType === "issue" ? "issues" : contextType === "pr" ? "pull_request" : void 0;
431
+ const githubEventName = effectiveContextType === "issue" ? "issues" : effectiveContextType === "pr" ? "pull_request" : void 0;
427
432
  envVars = {
428
433
  ...envVars,
429
434
  REPO_OWNER: owner,
@@ -435,8 +440,9 @@ async function generateIssueManagementMcpConfig(contextType, repo, provider = "g
435
440
  provider,
436
441
  repoOwner: owner,
437
442
  repoName: name,
438
- contextType: contextType ?? "auto-detect",
439
- githubEventName: githubEventName ?? "auto-detect"
443
+ contextType: effectiveContextType ?? "auto-detect",
444
+ githubEventName: githubEventName ?? "auto-detect",
445
+ draftPrNumber: draftPrNumber ?? void 0
440
446
  });
441
447
  } else {
442
448
  const apiToken = ((_b = (_a = settings == null ? void 0 : settings.issueManagement) == null ? void 0 : _a.linear) == null ? void 0 : _b.apiToken) ?? process.env.LINEAR_API_TOKEN;
@@ -461,10 +467,47 @@ async function generateIssueManagementMcpConfig(contextType, repo, provider = "g
461
467
  };
462
468
  return [mcpServerConfig];
463
469
  }
470
+ function slugifyPath(loomPath) {
471
+ let slug = loomPath.replace(/[/\\]+$/, "");
472
+ slug = slug.replace(/[/\\]/g, "___");
473
+ slug = slug.replace(/[^a-zA-Z0-9_-]/g, "-");
474
+ return `${slug}.json`;
475
+ }
476
+ function generateRecapMcpConfig(loomPath, loomMetadata) {
477
+ const recapsDir = path2.join(os2.homedir(), ".config", "iloom-ai", "recaps");
478
+ const recapFilePath = path2.join(recapsDir, slugifyPath(loomPath));
479
+ const envVars = {
480
+ RECAP_FILE_PATH: recapFilePath,
481
+ LOOM_METADATA_JSON: JSON.stringify(loomMetadata)
482
+ };
483
+ logger.debug("Generated MCP config for recap server", {
484
+ loomPath,
485
+ recapFilePath,
486
+ loomMetadataDescription: loomMetadata.description
487
+ });
488
+ return [
489
+ {
490
+ mcpServers: {
491
+ recap: {
492
+ transport: "stdio",
493
+ command: "node",
494
+ args: [
495
+ path2.join(
496
+ path2.dirname(new globalThis.URL(import.meta.url).pathname),
497
+ "../dist/mcp/recap-server.js"
498
+ )
499
+ ],
500
+ env: envVars
501
+ }
502
+ }
503
+ }
504
+ ];
505
+ }
464
506
 
465
507
  export {
466
508
  IssueTrackerFactory,
467
509
  FirstRunManager,
468
- generateIssueManagementMcpConfig
510
+ generateIssueManagementMcpConfig,
511
+ generateRecapMcpConfig
469
512
  };
470
- //# sourceMappingURL=chunk-HD5SUKI2.js.map
513
+ //# sourceMappingURL=chunk-RFUOIUQF.js.map