infra-kit 0.1.98 → 0.1.100

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 (56) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-d367c3be-9c2a-48e7-bcea-b45861af568c.jsonl +2 -0
  3. package/.omc/state/agent-replay-f2846d8f-974c-486c-b16f-4bdaa28ca45f.jsonl +1 -0
  4. package/.omc/state/last-tool-error.json +7 -0
  5. package/.omc/state/subagent-tracking.json +7 -0
  6. package/.turbo/turbo-eslint-check.log +2 -4
  7. package/.turbo/turbo-eslint-fix.log +1 -0
  8. package/.turbo/turbo-prettier-check.log +2 -4
  9. package/.turbo/turbo-prettier-fix.log +1 -10
  10. package/.turbo/turbo-test.log +12 -191
  11. package/.turbo/turbo-ts-check.log +2 -9
  12. package/dist/cli.js +69 -44
  13. package/dist/cli.js.map +4 -4
  14. package/dist/mcp.js +45 -34
  15. package/dist/mcp.js.map +4 -4
  16. package/package.json +11 -11
  17. package/src/commands/config/config.ts +1 -1
  18. package/src/commands/doctor/doctor.ts +62 -12
  19. package/src/commands/env-clear/env-clear.ts +5 -10
  20. package/src/commands/env-list/env-list.ts +5 -10
  21. package/src/commands/env-load/env-load.ts +5 -10
  22. package/src/commands/env-status/env-status.ts +5 -10
  23. package/src/commands/gh-merge-dev/gh-merge-dev.ts +17 -18
  24. package/src/commands/gh-release-deliver/gh-release-deliver.ts +290 -89
  25. package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +15 -14
  26. package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +30 -23
  27. package/src/commands/gh-release-list/gh-release-list.ts +5 -10
  28. package/src/commands/init/init.ts +17 -6
  29. package/src/commands/release-create/release-create.ts +223 -139
  30. package/src/commands/release-desc-edit/index.ts +1 -0
  31. package/src/commands/release-desc-edit/release-desc-edit.ts +207 -0
  32. package/src/commands/version/version.ts +5 -10
  33. package/src/commands/worktrees-add/worktrees-add.ts +34 -26
  34. package/src/commands/worktrees-list/worktrees-list.ts +6 -11
  35. package/src/commands/worktrees-open/worktrees-open.ts +10 -6
  36. package/src/commands/worktrees-remove/worktrees-remove.ts +18 -14
  37. package/src/commands/worktrees-sync/worktrees-sync.ts +17 -12
  38. package/src/entry/cli.ts +24 -21
  39. package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +21 -0
  40. package/src/integrations/gh/gh-release-prs/index.ts +1 -1
  41. package/src/integrations/gh/index.ts +1 -1
  42. package/src/integrations/jira/api.ts +8 -17
  43. package/src/integrations/jira/index.ts +2 -0
  44. package/src/lib/__tests__/infra-kit-config.test.ts +50 -0
  45. package/src/lib/errors/__tests__/operation-error.test.ts +62 -0
  46. package/src/lib/errors/format-zx-error.ts +54 -0
  47. package/src/lib/errors/operation-error.ts +80 -0
  48. package/src/lib/infra-kit-config/infra-kit-config.ts +7 -0
  49. package/src/lib/version-utils/__tests__/next-version.test.ts +128 -23
  50. package/src/lib/version-utils/index.ts +4 -2
  51. package/src/lib/version-utils/next-version.ts +64 -25
  52. package/src/mcp/tools/index.ts +2 -2
  53. package/src/types.ts +56 -2
  54. package/tsconfig.tsbuildinfo +1 -1
  55. package/src/commands/release-create-batch/index.ts +0 -1
  56. package/src/commands/release-create-batch/release-create-batch.ts +0 -222
@@ -3,12 +3,12 @@ import { z } from 'zod/v4'
3
3
  import { getReleasePRsWithInfo } from 'src/integrations/gh'
4
4
  import { logger } from 'src/lib/logger'
5
5
  import { detectReleaseType, formatVersionLabel, getJiraDescriptions } from 'src/lib/release-utils'
6
- import type { ToolsExecutionResult } from 'src/types'
6
+ import { defineMcpTool, textContent } from 'src/types'
7
7
 
8
8
  /**
9
9
  * List all open release branches
10
10
  */
11
- export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
11
+ export const ghReleaseList = async () => {
12
12
  const releasePRs = await getReleasePRsWithInfo()
13
13
 
14
14
  const releases = releasePRs.map((pr) => {
@@ -52,18 +52,13 @@ export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
52
52
  }
53
53
 
54
54
  return {
55
- content: [
56
- {
57
- type: 'text',
58
- text: JSON.stringify(structuredContent, null, 2),
59
- },
60
- ],
55
+ content: textContent(JSON.stringify(structuredContent, null, 2)),
61
56
  structuredContent,
62
57
  }
63
58
  }
64
59
 
65
60
  // MCP Tool Registration
66
- export const ghReleaseListMcpTool = {
61
+ export const ghReleaseListMcpTool = defineMcpTool({
67
62
  name: 'gh-release-list',
68
63
  description:
69
64
  'List every open release PR with its version, type (regular / hotfix), and associated Jira fix-version description. Read-only; sourced from GitHub and Jira.',
@@ -81,4 +76,4 @@ export const ghReleaseListMcpTool = {
81
76
  count: z.number().describe('Number of release branches'),
82
77
  },
83
78
  handler: ghReleaseList,
84
- }
79
+ })
@@ -10,20 +10,31 @@ export const MARKER_END = '# -- infra-kit:end --'
10
10
  const LEGACY_PAIRED: [start: string, end: string][] = [['# region infra-kit', '# endregion infra-kit']]
11
11
  const LEGACY_SINGLE = '# infra-kit shell functions'
12
12
 
13
- const USER_GLOBAL_CONFIG_STUB = `# infra-kit user-global config
13
+ const USER_GLOBAL_CONFIG_STUB = `# infra-kit user-global config — ~/.infra-kit/config.yml
14
14
  #
15
- # Shared across every project on this machine. Loaded after each project's
16
- # infra-kit.yml and before ~/.infra-kit/projects/<repo-name>/infra-kit.yml.
15
+ # Merge chain (later layers override earlier ones at top-level keys):
16
+ # 1. <repo>/infra-kit.yml committed project config (required)
17
+ # 2. ~/.infra-kit/config.yml — this file (user-global)
18
+ # 3. ~/.infra-kit/projects/<repo-name>/infra-kit.yml — user-scope per-project override
17
19
  #
18
- # Top-level keys (envManagement, ide, taskManager, environments) replace
19
- # wholesale when set here. Uncomment values you want to apply globally.
20
+ # Merge is shallow: setting a top-level key here replaces that whole section
21
+ # from layer 1. Arrays do not concatenate. Top-level keys recognized:
22
+ # environments, envManagement, ide, taskManager, worktrees.
23
+ #
24
+ # Uncomment the blocks you want to apply globally across every project on this
25
+ # machine. Per-project tweaks belong in layer 3 — run \`infra-kit config edit\`.
20
26
 
21
27
  # Per-developer IDE config
22
28
  # ide:
23
29
  # provider: cursor
24
30
  # config:
25
31
  # mode: workspace
26
- # workspaceConfigPath: ../Main.code-workspace
32
+ # workspaceConfigPath: /path/to/your.code-workspace
33
+
34
+ # Worktree prompt defaults — silences the follow-up prompts in \`worktrees-add\`
35
+ # worktrees:
36
+ # openInGithubDesktop: false
37
+ # openInCmux: true
27
38
  `
28
39
 
29
40
  /**
@@ -6,42 +6,27 @@ import { question } from 'zx'
6
6
 
7
7
  import { loadJiraConfig } from 'src/integrations/jira'
8
8
  import { commandEcho } from 'src/lib/command-echo'
9
+ import { OperationError } from 'src/lib/errors/operation-error'
9
10
  import { logger } from 'src/lib/logger'
10
11
  import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils'
11
- import type { ReleaseType } from 'src/lib/release-utils'
12
+ import type { ReleaseCreationResult, ReleaseType } from 'src/lib/release-utils'
12
13
  import {
13
- NEXT_TOKEN,
14
14
  NoPriorVersionsError,
15
15
  computeNextVersion,
16
+ hasNextToken,
16
17
  loadExistingVersions,
17
- resolveVersionTokens,
18
- splitVersionInput,
18
+ parseVersion,
19
+ resolveReleaseEntries,
19
20
  } from 'src/lib/version-utils'
20
- import type { SemVer } from 'src/lib/version-utils'
21
- import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
22
-
23
- import { releaseCreateBatch } from '../release-create-batch/release-create-batch'
21
+ import type { ReleaseEntry, SemVer } from 'src/lib/version-utils'
22
+ import { defineMcpTool, textContent } from 'src/types'
23
+ import type { RequiredConfirmedOptionArg } from 'src/types'
24
24
 
25
25
  interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
26
- version?: string
27
- description?: string
28
- type?: ReleaseType
26
+ releases?: ReleaseEntry[]
29
27
  }
30
28
 
31
- const VERSION_PROMPT_HINT = '"1.2.5", "next", or "next,next,1.2.7"'
32
-
33
- const promptForType = async (): Promise<ReleaseType> => {
34
- commandEcho.setInteractive()
35
-
36
- return select<ReleaseType>({
37
- message: 'Select release type:',
38
- choices: [
39
- { name: 'regular', value: 'regular' },
40
- { name: 'hotfix', value: 'hotfix' },
41
- ],
42
- default: 'regular',
43
- })
44
- }
29
+ const VERSION_PROMPT_HINT = '"1.2.5" or "next"'
45
30
 
46
31
  const trySuggestNext = (known: SemVer[], type: ReleaseType): string | null => {
47
32
  try {
@@ -53,116 +38,113 @@ const trySuggestNext = (known: SemVer[], type: ReleaseType): string | null => {
53
38
  }
54
39
  }
55
40
 
56
- const exitOnNoPrior = (err: unknown): never => {
57
- if (err instanceof NoPriorVersionsError) {
58
- logger.error(err.message)
59
- process.exit(1)
60
- }
41
+ const resolveOrExit = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
42
+ try {
43
+ return resolveReleaseEntries(entries, known)
44
+ } catch (err) {
45
+ if (err instanceof NoPriorVersionsError) {
46
+ throw new OperationError(err, {
47
+ operation: 'resolve release version',
48
+ remediation: 'pass an explicit version (e.g. "1.2.5") instead of "next" when there are no prior versions',
49
+ })
50
+ }
61
51
 
62
- throw err
52
+ throw err
53
+ }
63
54
  }
64
55
 
65
- interface ResolveTokensArgs {
66
- inputVersion: string | undefined
67
- type: ReleaseType
68
- ensureKnown: () => Promise<SemVer[]>
69
- }
56
+ const promptForReleasesInteractive = async (ensureKnown: () => Promise<SemVer[]>): Promise<ReleaseEntry[]> => {
57
+ commandEcho.setInteractive()
70
58
 
71
- const resolveRawTokens = async (args: ResolveTokensArgs): Promise<string[]> => {
72
- const { inputVersion, type, ensureKnown } = args
59
+ const baseKnown = await ensureKnown()
60
+ const running: SemVer[] = [...baseKnown]
61
+ const entries: ReleaseEntry[] = []
62
+ let addAnother = true
63
+
64
+ while (addAnother) {
65
+ const ordinal = entries.length + 1
66
+ const type = await select<ReleaseType>({
67
+ message: `Release #${ordinal} — select type:`,
68
+ choices: [
69
+ { name: 'regular', value: 'regular' },
70
+ { name: 'hotfix', value: 'hotfix' },
71
+ ],
72
+ default: 'regular',
73
+ })
73
74
 
74
- if (inputVersion && inputVersion.trim() !== '') {
75
- return splitVersionInput(inputVersion)
76
- }
75
+ const suggestion = trySuggestNext(running, type)
76
+ const defaultHint = suggestion ? ` [${suggestion}]` : ''
77
+ const versionAnswer = (await question(` Version (e.g. ${VERSION_PROMPT_HINT})${defaultHint}: `)).trim()
78
+ const versionInput = versionAnswer === '' ? (suggestion ?? '') : versionAnswer
77
79
 
78
- commandEcho.setInteractive()
80
+ if (versionInput === '') {
81
+ logger.error('No version provided. Exiting...')
82
+ process.exit(1)
83
+ }
79
84
 
80
- const suggestion = trySuggestNext(await ensureKnown(), type)
81
- const defaultHint = suggestion ? ` [${suggestion}]` : ''
82
- const answer = (await question(`Enter version(s) (e.g. ${VERSION_PROMPT_HINT})${defaultHint}: `)).trim()
85
+ const resolved = resolveOrExit([{ version: versionInput, type }], running)[0] as ReleaseEntry
83
86
 
84
- if (answer === '') return suggestion ? [suggestion] : []
87
+ running.push(parseVersion(`v${resolved.version}`))
85
88
 
86
- return splitVersionInput(answer)
87
- }
89
+ const description = (await question(' Description (optional, press Enter to skip): ')).trim()
88
90
 
89
- const resolveVersionList = async (args: ResolveTokensArgs): Promise<string[]> => {
90
- const rawTokens = await resolveRawTokens(args)
91
+ entries.push({ ...resolved, ...(description !== '' ? { description } : {}) })
91
92
 
92
- if (rawTokens.length === 0) {
93
- logger.error('No version provided. Exiting...')
94
- process.exit(1)
93
+ addAnother = await confirm({ message: 'Add another release?', default: false })
95
94
  }
96
95
 
97
- const needsKnown = rawTokens.some((t) => {
98
- return t.toLowerCase() === NEXT_TOKEN
99
- })
100
-
101
- try {
102
- return resolveVersionTokens(rawTokens, args.type, needsKnown ? await args.ensureKnown() : [])
103
- } catch (err) {
104
- return exitOnNoPrior(err)
105
- }
96
+ return entries
106
97
  }
107
98
 
108
- const resolveDescription = async (input: string | undefined): Promise<string> => {
109
- if (input !== undefined) return input
99
+ const formatReleaseSummary = (entry: ReleaseEntry): string => {
100
+ const parts = [`v${entry.version}`, entry.type]
110
101
 
111
- commandEcho.setInteractive()
112
- const answer = await question('Enter description (optional, press Enter to skip): ')
102
+ if (entry.description) parts.push(entry.description)
113
103
 
114
- return answer.trim()
104
+ return parts.join(' · ')
115
105
  }
116
106
 
117
- /**
118
- * Create a single release branch for the specified version
119
- * Includes Jira version creation and GitHub release branch creation
120
- */
121
- export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
122
- const { version: inputVersion, description: inputDescription, type: inputType, confirmedCommand } = args
123
-
124
- commandEcho.start('release-create')
125
-
126
- // Load Jira config - it is now mandatory
127
- const jiraConfig = await loadJiraConfig()
128
-
129
- const type: ReleaseType = inputType ?? (await promptForType())
130
-
131
- commandEcho.addOption('--type', type)
132
-
133
- let known: SemVer[] | null = null
134
- const ensureKnown = async (): Promise<SemVer[]> => {
135
- if (known === null) known = await loadExistingVersions()
107
+ const echoReleases = (entries: ReleaseEntry[]): void => {
108
+ for (const entry of entries) {
109
+ const spec = entry.description
110
+ ? `${entry.version}:${entry.type}:${entry.description}`
111
+ : `${entry.version}:${entry.type}`
136
112
 
137
- return known
113
+ commandEcho.addOption('--release', spec)
138
114
  }
115
+ }
139
116
 
140
- const resolvedVersions = await resolveVersionList({ inputVersion, type, ensureKnown })
117
+ interface FailedRelease {
118
+ version: string
119
+ error: string
120
+ }
141
121
 
142
- if (resolvedVersions.length > 1) {
143
- logger.info(`Detected ${resolvedVersions.length} versions, routing to release-create-batch...`)
122
+ const collectEntries = async (
123
+ inputReleases: ReleaseEntry[] | undefined,
124
+ ensureKnown: () => Promise<SemVer[]>,
125
+ ): Promise<ReleaseEntry[]> => {
126
+ if (inputReleases && inputReleases.length > 0) {
127
+ const known = hasNextToken(inputReleases) ? await ensureKnown() : []
128
+ const resolved = resolveOrExit(inputReleases, known)
144
129
 
145
- return releaseCreateBatch({
146
- versions: resolvedVersions.join(','),
147
- type,
148
- confirmedCommand,
149
- })
150
- }
130
+ echoReleases(resolved)
151
131
 
152
- const trimmedVersion = resolvedVersions[0] as string
132
+ return resolved
133
+ }
153
134
 
154
- commandEcho.addOption('--version', trimmedVersion)
135
+ const interactive = await promptForReleasesInteractive(ensureKnown)
155
136
 
156
- const description = await resolveDescription(inputDescription)
137
+ echoReleases(interactive)
157
138
 
158
- if (description) {
159
- commandEcho.addOption('--description', description)
160
- }
139
+ return interactive
140
+ }
161
141
 
142
+ const confirmReleases = async (entries: ReleaseEntry[], confirmedCommand: boolean): Promise<void> => {
143
+ const summary = entries.map(formatReleaseSummary).join('\n - ')
162
144
  const answer = confirmedCommand
163
145
  ? true
164
146
  : await confirm({
165
- message: `Are you sure you want to create release branch for version ${trimmedVersion}?`,
147
+ message: `Create the following ${entries.length} release(s)?\n - ${summary}\n`,
166
148
  })
167
149
 
168
150
  if (!confirmedCommand) {
@@ -174,62 +156,164 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
174
156
  process.exit(0)
175
157
  }
176
158
 
177
- // Track --yes flag if confirmation was interactive (user confirmed)
178
159
  commandEcho.addOption('--yes', true)
160
+ }
161
+
162
+ interface ExecuteOneArgs {
163
+ entry: ReleaseEntry
164
+ jiraConfig: Awaited<ReturnType<typeof loadJiraConfig>>
165
+ }
166
+
167
+ const executeOne = async (
168
+ args: ExecuteOneArgs,
169
+ ): Promise<{ result?: ReleaseCreationResult; failure?: FailedRelease }> => {
170
+ const { entry, jiraConfig } = args
179
171
 
180
- await prepareGitForRelease(type)
172
+ try {
173
+ await prepareGitForRelease(entry.type)
174
+
175
+ const result = await createSingleRelease({
176
+ version: entry.version,
177
+ jiraConfig,
178
+ description: entry.description,
179
+ type: entry.type,
180
+ })
181
+
182
+ logger.info(`✅ Successfully created release: v${entry.version} (${entry.type})`)
183
+ logger.info(`🔗 GitHub PR: ${result.prUrl}`)
184
+ logger.info(`🔗 Jira Version: ${result.jiraVersionUrl}\n`)
185
+
186
+ return { result }
187
+ } catch (error) {
188
+ const err = new OperationError(error, {
189
+ operation: `create release v${entry.version} (${entry.type})`,
190
+ remediation: 'verify the version is unique and the base branch is clean',
191
+ })
181
192
 
182
- const release = await createSingleRelease({ version: trimmedVersion, jiraConfig, description, type })
193
+ logger.error(`❌ ${err.message}\n`)
183
194
 
184
- logger.info(`✅ Successfully created release: v${trimmedVersion}`)
185
- logger.info(`🔗 GitHub PR: ${release.prUrl}`)
186
- logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}`)
195
+ return { failure: { version: entry.version, error: err.message } }
196
+ }
197
+ }
198
+
199
+ const logFinalSummary = (total: number, successCount: number, failureCount: number): void => {
200
+ if (successCount === total) {
201
+ logger.info(`✅ All ${total} release branch(es) were created successfully.`)
202
+ } else if (successCount > 0) {
203
+ logger.warn(`⚠️ ${successCount} of ${total} release branches were created successfully.`)
204
+ logger.warn(`❌ ${failureCount} release(s) failed.`)
205
+ } else {
206
+ logger.error(`❌ All ${total} release branch(es) failed to create.`)
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Create one or more release branches. Each release carries its own type
212
+ * (regular/hotfix) and optional Jira description, so a single invocation
213
+ * may mix regular and hotfix releases off their respective base branches.
214
+ */
215
+ export const releaseCreate = async (args: ReleaseCreateArgs) => {
216
+ const { releases: inputReleases, confirmedCommand } = args
217
+
218
+ commandEcho.start('release-create')
219
+
220
+ const jiraConfig = await loadJiraConfig()
221
+
222
+ let known: SemVer[] | null = null
223
+ const ensureKnown = async (): Promise<SemVer[]> => {
224
+ if (known === null) known = await loadExistingVersions()
225
+
226
+ return known
227
+ }
228
+
229
+ const entries = await collectEntries(inputReleases, ensureKnown)
230
+
231
+ if (entries.length === 0) {
232
+ throw new OperationError(undefined, {
233
+ operation: 'create release',
234
+ remediation: 'pass at least one entry in "releases" (e.g. [{ version: "1.2.5", type: "regular" }])',
235
+ stderrExcerpt: 'no releases provided',
236
+ })
237
+ }
238
+
239
+ await confirmReleases(entries, Boolean(confirmedCommand))
240
+
241
+ const created: ReleaseCreationResult[] = []
242
+ const failed: FailedRelease[] = []
243
+
244
+ for (const entry of entries) {
245
+ const { result, failure } = await executeOne({ entry, jiraConfig })
246
+
247
+ if (result) created.push(result)
248
+ if (failure) failed.push(failure)
249
+ }
250
+
251
+ logFinalSummary(entries.length, created.length, failed.length)
187
252
 
188
253
  commandEcho.print()
189
254
 
190
255
  const structuredContent = {
191
- version: trimmedVersion,
192
- type,
193
- branchName: release.branchName,
194
- prUrl: release.prUrl,
195
- jiraVersionUrl: release.jiraVersionUrl,
256
+ createdBranches: created.map((r) => {
257
+ return r.branchName
258
+ }),
259
+ successCount: created.length,
260
+ failureCount: failed.length,
261
+ releases: created,
262
+ failedReleases: failed,
196
263
  }
197
264
 
198
265
  return {
199
- content: [
200
- {
201
- type: 'text',
202
- text: JSON.stringify(structuredContent, null, 2),
203
- },
204
- ],
266
+ content: textContent(JSON.stringify(structuredContent, null, 2)),
205
267
  structuredContent,
206
268
  }
207
269
  }
208
270
 
209
271
  // MCP Tool Registration
210
- export const releaseCreateMcpTool = {
272
+ export const releaseCreateMcpTool = defineMcpTool({
211
273
  name: 'release-create',
212
274
  description:
213
- 'Create a new release: cuts the release branch off the appropriate base (dev for regular releases, main for hotfixes), opens a GitHub release PR, and creates the matching Jira fix version. Does not switch the working tree to the new branch the caller stays on the base branch. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the interactive input prompt is unreachable without a TTY); pass "next" to auto-compute the next version (regular bumps minor + resets patch; hotfix bumps patch on the highest minor) using the union of remote release branches and Jira fix versions. "type" / "description" default to regular / empty when omitted. For multiple versions in one call, prefer release-create-batch.',
275
+ 'Create one or more releases in a single call. Each entry in "releases" carries its own version, type (regular|hotfix, default regular), and optional description, so regular and hotfix releases can be mixed in the same invocation. For each release this tool switches to the appropriate base branch (dev for regular, main for hotfix), cuts the release branch, opens a GitHub release PR, and creates the matching Jira fix version. The literal token "next" auto-increments from the union of remote release branches and Jira fix versions (regular bumps minor + resets patch; hotfix bumps patch on the highest minor); multiple "next" tokens advance sequentially across mixed types. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. Continues on per-release failure and reports successes/failures.',
214
276
  inputSchema: {
215
- version: z
216
- .string()
217
- .describe(
218
- 'Version to create (e.g., "1.2.5") or the literal token "next" for auto-increment. Required for MCP calls.',
219
- ),
220
- description: z.string().optional().describe('Optional description for the Jira version'),
221
- type: z
222
- .enum(['regular', 'hotfix'])
223
- .optional()
224
- .default('regular')
225
- .describe('Release type: "regular" or "hotfix" (default: "regular")'),
277
+ releases: z
278
+ .array(
279
+ z.object({
280
+ version: z
281
+ .string()
282
+ .describe('Version to create (e.g., "1.2.5") or the literal token "next" for auto-increment.'),
283
+ type: z
284
+ .enum(['regular', 'hotfix'])
285
+ .optional()
286
+ .default('regular')
287
+ .describe('Release type: "regular" (branches off dev) or "hotfix" (branches off main).'),
288
+ description: z.string().optional().describe('Optional description for the Jira version.'),
289
+ }),
290
+ )
291
+ .min(1)
292
+ .describe('One or more releases to create. Each entry has its own version, type, and optional description.'),
226
293
  },
227
294
  outputSchema: {
228
- version: z.string().describe('Version number'),
229
- type: z.enum(['regular', 'hotfix']).describe('Release type'),
230
- branchName: z.string().describe('Release branch name'),
231
- prUrl: z.string().describe('GitHub PR URL'),
232
- jiraVersionUrl: z.string().describe('Jira version URL'),
295
+ createdBranches: z.array(z.string()).describe('List of created release branch names'),
296
+ successCount: z.number().describe('Number of releases created successfully'),
297
+ failureCount: z.number().describe('Number of releases that failed'),
298
+ releases: z
299
+ .array(
300
+ z.object({
301
+ version: z.string().describe('Version number'),
302
+ type: z.enum(['regular', 'hotfix']).describe('Release type'),
303
+ branchName: z.string().describe('Release branch name'),
304
+ prUrl: z.string().describe('GitHub PR URL'),
305
+ jiraVersionUrl: z.string().describe('Jira version URL'),
306
+ }),
307
+ )
308
+ .describe('Detailed information for each created release with URLs'),
309
+ failedReleases: z
310
+ .array(
311
+ z.object({
312
+ version: z.string().describe('Version number that failed'),
313
+ error: z.string().describe('Error message'),
314
+ }),
315
+ )
316
+ .describe('List of releases that failed with error messages'),
233
317
  },
234
318
  handler: releaseCreate,
235
- }
319
+ })
@@ -0,0 +1 @@
1
+ export { releaseDescEdit, releaseDescEditMcpTool } from './release-desc-edit'