infra-kit 0.1.102 → 0.1.105

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 (127) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-0a58307d-2a37-4c69-851c-83a646502d62.jsonl +1 -0
  3. package/.omc/state/agent-replay-11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc.jsonl +16 -0
  4. package/.omc/state/agent-replay-4cf1c186-81b2-497c-b002-d7f84e7839f3.jsonl +9 -0
  5. package/.omc/state/agent-replay-5c4ab554-64f1-42ae-83e3-21e0237e955c.jsonl +11 -0
  6. package/.omc/state/agent-replay-a60ac2ec-afbd-449f-a540-6df287392fc2.jsonl +1 -0
  7. package/.omc/state/agent-replay-be37e426-6fc8-47f4-8178-221c8494551c.jsonl +3 -0
  8. package/.omc/state/agent-replay-c967c819-3d1c-447b-ab48-56a8448ef9f8.jsonl +2 -0
  9. package/.omc/state/idle-notif-cooldown.json +3 -0
  10. package/.omc/state/last-tool-error.json +4 -4
  11. package/.omc/state/mission-state.json +53 -0
  12. package/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/pre-tool-advisory-throttle.json +18 -0
  13. package/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/subagent-tracking-state.json +7 -0
  14. package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/last-tool-error-state.json +7 -0
  15. package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/mission-state.json +117 -0
  16. package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/pre-tool-advisory-throttle.json +42 -0
  17. package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/subagent-tracking-state.json +53 -0
  18. package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/last-tool-error-state.json +7 -0
  19. package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/pre-tool-advisory-throttle.json +18 -0
  20. package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/subagent-tracking-state.json +7 -0
  21. package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/mission-state.json +117 -0
  22. package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/pre-tool-advisory-throttle.json +18 -0
  23. package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/subagent-tracking-state.json +17 -0
  24. package/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/pre-tool-advisory-throttle.json +18 -0
  25. package/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/subagent-tracking-state.json +7 -0
  26. package/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/pre-tool-advisory-throttle.json +10 -0
  27. package/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/subagent-tracking-state.json +7 -0
  28. package/.omc/state/subagent-tracking.json +14 -4
  29. package/.turbo/turbo-build.log +7 -0
  30. package/.turbo/turbo-check.log +14 -0
  31. package/.turbo/turbo-prettier-fix.log +2 -1
  32. package/.turbo/turbo-test.log +28 -5
  33. package/.turbo/turbo-validate.log +14 -0
  34. package/dist/cli.js +81 -74
  35. package/dist/cli.js.map +4 -4
  36. package/dist/entry/index.d.ts +2 -0
  37. package/dist/index.js +2 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/lib/package-config/package-config.d.ts +71 -0
  40. package/dist/mcp.js +43 -41
  41. package/dist/mcp.js.map +4 -4
  42. package/eslint.config.js +1 -1
  43. package/infra-kit.config.ts +5 -0
  44. package/package.json +20 -13
  45. package/scripts/build.js +32 -3
  46. package/src/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/pre-tool-advisory-throttle.json +18 -0
  47. package/src/commands/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/pre-tool-advisory-throttle.json +18 -0
  48. package/src/commands/audit/__tests__/audit.test.ts +59 -0
  49. package/src/commands/audit/audit.ts +177 -0
  50. package/src/commands/audit/index.ts +1 -0
  51. package/src/commands/config/config.ts +49 -7
  52. package/src/commands/doctor/doctor.ts +3 -3
  53. package/src/commands/env-clear/env-clear.ts +1 -1
  54. package/src/commands/env-list/env-list.ts +3 -3
  55. package/src/commands/env-load/env-load.ts +1 -1
  56. package/src/commands/env-status/env-status.ts +1 -1
  57. package/src/commands/gh-merge-dev/gh-merge-dev.ts +3 -8
  58. package/src/commands/gh-release-deliver/gh-release-deliver.ts +47 -21
  59. package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +13 -7
  60. package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +12 -6
  61. package/src/commands/gh-release-list/gh-release-list.ts +19 -8
  62. package/src/commands/init/__tests__/migrate-config.test.ts +160 -0
  63. package/src/commands/init/init.ts +48 -35
  64. package/src/commands/init/migrate-config.ts +146 -0
  65. package/src/commands/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  66. package/src/commands/release-create/__tests__/release-create.test.ts +55 -0
  67. package/src/commands/release-create/release-create.ts +142 -38
  68. package/src/commands/release-desc-edit/release-desc-edit.ts +28 -8
  69. package/src/commands/version/version.ts +1 -1
  70. package/src/commands/worktrees-add/worktrees-add.ts +7 -12
  71. package/src/commands/worktrees-list/worktrees-list.ts +13 -5
  72. package/src/commands/worktrees-open/worktrees-open.ts +1 -1
  73. package/src/commands/worktrees-remove/worktrees-remove.ts +6 -10
  74. package/src/commands/worktrees-sync/worktrees-sync.ts +3 -5
  75. package/src/entry/cli.ts +49 -6
  76. package/src/entry/index.ts +5 -0
  77. package/src/integrations/cmux/open-workspace-with-layout.ts +4 -4
  78. package/src/integrations/cmux/workspace-title.ts +10 -4
  79. package/src/integrations/doppler/doppler-project.ts +1 -1
  80. package/src/integrations/gh/gh-release-prs/__tests__/gh-release-prs.test.ts +115 -0
  81. package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +49 -32
  82. package/src/lib/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/pre-tool-advisory-throttle.json +14 -0
  83. package/src/lib/constants/index.ts +15 -0
  84. package/src/lib/git-utils/__tests__/git-utils.test.ts +49 -0
  85. package/src/lib/git-utils/git-utils.ts +3 -1
  86. package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +270 -0
  87. package/src/lib/infra-kit-config/index.ts +7 -1
  88. package/src/lib/infra-kit-config/infra-kit-config.ts +46 -28
  89. package/src/lib/package-config/__tests__/package-config.test.ts +95 -0
  90. package/src/lib/package-config/index.ts +3 -0
  91. package/src/lib/package-config/package-config-schema.ts +19 -0
  92. package/src/lib/package-config/package-config.ts +99 -0
  93. package/src/lib/package-validator/__tests__/package-validator.test.ts +263 -0
  94. package/src/lib/package-validator/checks/__tests__/checks.test.ts +130 -0
  95. package/src/lib/package-validator/checks/config-check.ts +30 -0
  96. package/src/lib/package-validator/checks/files-check.ts +29 -0
  97. package/src/lib/package-validator/checks/index.ts +4 -0
  98. package/src/lib/package-validator/checks/scripts-check.ts +23 -0
  99. package/src/lib/package-validator/checks/turbo-check.ts +47 -0
  100. package/src/lib/package-validator/fs-utils.ts +18 -0
  101. package/src/lib/package-validator/index.ts +3 -0
  102. package/src/lib/package-validator/loader/config-loader.ts +77 -0
  103. package/src/lib/package-validator/loader/index.ts +2 -0
  104. package/src/lib/package-validator/loader/package-discovery.ts +98 -0
  105. package/src/lib/package-validator/package-validator.ts +48 -0
  106. package/src/lib/package-validator/types.ts +15 -0
  107. package/src/lib/release-id/__tests__/release-id.test.ts +351 -0
  108. package/src/lib/release-id/__tests__/versioned-regression.test.ts +69 -0
  109. package/src/lib/release-id/index.ts +15 -0
  110. package/src/lib/release-id/release-id.ts +257 -0
  111. package/src/lib/release-utils/__tests__/release-utils.test.ts +122 -0
  112. package/src/lib/release-utils/index.ts +4 -0
  113. package/src/lib/release-utils/release-utils.ts +85 -17
  114. package/src/lib/version-utils/__tests__/load-existing-versions.test.ts +37 -0
  115. package/src/lib/version-utils/__tests__/next-version.test.ts +119 -13
  116. package/src/lib/version-utils/index.ts +3 -0
  117. package/src/lib/version-utils/load-existing-versions.ts +29 -10
  118. package/src/lib/version-utils/next-version.ts +67 -12
  119. package/src/lib/version-utils/version-utils.ts +13 -4
  120. package/src/mcp/tools/index.ts +2 -0
  121. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  122. package/src/types.ts +1 -1
  123. package/tsconfig.tsbuildinfo +1 -1
  124. package/src/lib/__tests__/infra-kit-config.test.ts +0 -231
  125. /package/src/integrations/{clickup → linear}/.gitkeep +0 -0
  126. /package/src/lib/{__tests__ → constants/__tests__}/constants.test.ts +0 -0
  127. /package/src/lib/{constants.ts → constants/constants.ts} +0 -0
@@ -1,12 +1,26 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
+ import { InvalidReleaseNameError, formatBranchName, formatJiraName, formatPrTitle } from '../../release-id'
4
+ import type { ReleaseId } from '../../release-id'
3
5
  import {
4
6
  NoPriorVersionsError,
5
7
  collectKnownVersions,
6
8
  computeNextVersion,
9
+ hasNextToken,
7
10
  parseReleaseSpec,
8
11
  resolveReleaseEntries,
9
12
  } from '../next-version'
13
+ import type { ReleaseEntry } from '../next-version'
14
+
15
+ const versionId = (raw: string): ReleaseId => {
16
+ const [major, minor, patch] = raw.split('.').map(Number)
17
+
18
+ return { kind: 'version', semver: { major: major!, minor: minor!, patch: patch! }, raw }
19
+ }
20
+
21
+ const nameId = (name: string): ReleaseId => {
22
+ return { kind: 'name', name, raw: name }
23
+ }
10
24
 
11
25
  describe('collectKnownVersions', () => {
12
26
  it('parses remote branch refs and Jira version names and dedupes', () => {
@@ -123,7 +137,7 @@ describe('parseReleaseSpec', () => {
123
137
  describe('resolveReleaseEntries', () => {
124
138
  const known = collectKnownVersions({ remoteBranches: ['release/v1.63.0'] })
125
139
 
126
- it('passes through explicit semver entries unchanged', () => {
140
+ it('passes through explicit semver entries, wrapping them in a version ReleaseId', () => {
127
141
  expect(
128
142
  resolveReleaseEntries(
129
143
  [
@@ -133,14 +147,14 @@ describe('resolveReleaseEntries', () => {
133
147
  known,
134
148
  ),
135
149
  ).toEqual([
136
- { version: '1.70.0', type: 'regular' },
137
- { version: '1.70.1', type: 'hotfix' },
150
+ { id: versionId('1.70.0'), type: 'regular' },
151
+ { id: versionId('1.70.1'), type: 'hotfix' },
138
152
  ])
139
153
  })
140
154
 
141
155
  it('resolves a single "next" using the entry type', () => {
142
156
  expect(resolveReleaseEntries([{ version: 'next', type: 'regular' }], known)).toEqual([
143
- { version: '1.64.0', type: 'regular' },
157
+ { id: versionId('1.64.0'), type: 'regular' },
144
158
  ])
145
159
  })
146
160
 
@@ -154,8 +168,8 @@ describe('resolveReleaseEntries', () => {
154
168
  known,
155
169
  ),
156
170
  ).toEqual([
157
- { version: '1.64.0', type: 'regular' },
158
- { version: '1.65.0', type: 'regular' },
171
+ { id: versionId('1.64.0'), type: 'regular' },
172
+ { id: versionId('1.65.0'), type: 'regular' },
159
173
  ])
160
174
  })
161
175
 
@@ -169,8 +183,8 @@ describe('resolveReleaseEntries', () => {
169
183
  known,
170
184
  ),
171
185
  ).toEqual([
172
- { version: '1.64.0', type: 'regular' },
173
- { version: '1.64.1', type: 'hotfix' },
186
+ { id: versionId('1.64.0'), type: 'regular' },
187
+ { id: versionId('1.64.1'), type: 'hotfix' },
174
188
  ])
175
189
  })
176
190
 
@@ -185,21 +199,21 @@ describe('resolveReleaseEntries', () => {
185
199
  known,
186
200
  ),
187
201
  ).toEqual([
188
- { version: '1.64.0', type: 'regular' },
189
- { version: '1.70.0', type: 'regular' },
190
- { version: '1.71.0', type: 'regular' },
202
+ { id: versionId('1.64.0'), type: 'regular' },
203
+ { id: versionId('1.70.0'), type: 'regular' },
204
+ { id: versionId('1.71.0'), type: 'regular' },
191
205
  ])
192
206
  })
193
207
 
194
208
  it('preserves description through resolution', () => {
195
209
  expect(resolveReleaseEntries([{ version: 'next', type: 'regular', description: 'Holiday' }], known)).toEqual([
196
- { version: '1.64.0', type: 'regular', description: 'Holiday' },
210
+ { id: versionId('1.64.0'), type: 'regular', description: 'Holiday' },
197
211
  ])
198
212
  })
199
213
 
200
214
  it('accepts case-insensitive "next"', () => {
201
215
  expect(resolveReleaseEntries([{ version: 'NEXT', type: 'regular' }], known)).toEqual([
202
- { version: '1.64.0', type: 'regular' },
216
+ { id: versionId('1.64.0'), type: 'regular' },
203
217
  ])
204
218
  })
205
219
 
@@ -214,4 +228,96 @@ describe('resolveReleaseEntries', () => {
214
228
  return resolveReleaseEntries([{ version: 'next', type: 'regular' }], [])
215
229
  }).toThrow(NoPriorVersionsError)
216
230
  })
231
+
232
+ describe('named entries', () => {
233
+ it('resolves a valid name into a name ReleaseId', () => {
234
+ expect(resolveReleaseEntries([{ name: 'checkout-redesign', type: 'regular' }], known)).toEqual([
235
+ { id: nameId('checkout-redesign'), type: 'regular' },
236
+ ])
237
+ })
238
+
239
+ it('preserves type and description on named entries (named hotfix allowed)', () => {
240
+ expect(
241
+ resolveReleaseEntries([{ name: 'checkout-redesign', type: 'hotfix', description: 'Q3 work' }], known),
242
+ ).toEqual([{ id: nameId('checkout-redesign'), type: 'hotfix', description: 'Q3 work' }])
243
+ })
244
+
245
+ it('never auto-bumps a named entry (no version interaction)', () => {
246
+ expect(
247
+ resolveReleaseEntries(
248
+ [
249
+ { name: 'first-thing', type: 'regular' },
250
+ { version: 'next', type: 'regular' },
251
+ ],
252
+ known,
253
+ ),
254
+ ).toEqual([
255
+ { id: nameId('first-thing'), type: 'regular' },
256
+ { id: versionId('1.64.0'), type: 'regular' },
257
+ ])
258
+ })
259
+
260
+ it('throws InvalidReleaseNameError on an invalid (non-kebab) name', () => {
261
+ expect(() => {
262
+ return resolveReleaseEntries([{ name: 'Checkout_Redesign', type: 'regular' }], known)
263
+ }).toThrow(InvalidReleaseNameError)
264
+ })
265
+
266
+ it('throws InvalidReleaseNameError when the name is the reserved word "next"', () => {
267
+ expect(() => {
268
+ return resolveReleaseEntries([{ name: 'next', type: 'regular' }], known)
269
+ }).toThrow(InvalidReleaseNameError)
270
+ })
271
+ })
272
+
273
+ describe('formatting round-trip (zero-regression for versioned, correct for named)', () => {
274
+ const resolve = (entry: Parameters<typeof resolveReleaseEntries>[0][number]): ReleaseEntry => {
275
+ return resolveReleaseEntries([entry], known)[0] as ReleaseEntry
276
+ }
277
+
278
+ it('versioned literal "1.62.0" produces byte-identical branch/PR/Jira output', () => {
279
+ const { id } = resolve({ version: '1.62.0', type: 'regular' })
280
+
281
+ expect(formatBranchName(id)).toBe('release/v1.62.0')
282
+ expect(formatPrTitle(id, 'regular')).toBe('Release v1.62.0')
283
+ expect(formatJiraName(id)).toBe('v1.62.0')
284
+ })
285
+
286
+ it('versioned "next" resolves then formats identically to the equivalent literal', () => {
287
+ const { id } = resolve({ version: 'next', type: 'regular' })
288
+
289
+ // known max is 1.63.0 → regular next is 1.64.0
290
+ expect(formatBranchName(id)).toBe('release/v1.64.0')
291
+ expect(formatPrTitle(id, 'hotfix')).toBe('Hotfix v1.64.0')
292
+ expect(formatJiraName(id)).toBe('v1.64.0')
293
+ })
294
+
295
+ it('named entry produces the named branch/PR/Jira output', () => {
296
+ const { id } = resolve({ name: 'checkout-redesign', type: 'regular' })
297
+
298
+ expect(formatBranchName(id)).toBe('release/n/checkout-redesign')
299
+ expect(formatPrTitle(id, 'regular')).toBe('Release checkout-redesign')
300
+ expect(formatJiraName(id)).toBe('checkout-redesign')
301
+ })
302
+ })
303
+ })
304
+
305
+ describe('hasNextToken', () => {
306
+ it('is true when any versioned entry is "next"', () => {
307
+ expect(
308
+ hasNextToken([
309
+ { name: 'checkout-redesign', type: 'regular' },
310
+ { version: 'next', type: 'regular' },
311
+ ]),
312
+ ).toBe(true)
313
+ })
314
+
315
+ it('is false for explicit versions and named entries only', () => {
316
+ expect(
317
+ hasNextToken([
318
+ { version: '1.2.3', type: 'regular' },
319
+ { name: 'next-thing', type: 'regular' },
320
+ ]),
321
+ ).toBe(false)
322
+ })
217
323
  })
@@ -4,10 +4,13 @@ export {
4
4
  computeNextVersion,
5
5
  type ExistingVersionsSources,
6
6
  hasNextToken,
7
+ type NamedReleaseInput,
7
8
  NEXT_TOKEN,
8
9
  NoPriorVersionsError,
9
10
  parseReleaseSpec,
10
11
  type ReleaseEntry,
12
+ type ReleaseInput,
13
+ type ReleaseSpec,
11
14
  resolveReleaseEntries,
12
15
  type SemVer,
13
16
  } from './next-version'
@@ -2,27 +2,46 @@ import { $ } from 'zx'
2
2
 
3
3
  import { getProjectVersions, loadJiraConfigOptional } from 'src/integrations/jira'
4
4
  import { logger } from 'src/lib/logger'
5
+ import { parseBranchName } from 'src/lib/release-id'
5
6
 
6
7
  import { collectKnownVersions } from './next-version'
7
8
  import type { SemVer } from './next-version'
8
9
 
10
+ /**
11
+ * Extract version-branch tokens from raw `git ls-remote` stdout. Each line is
12
+ * `<sha>\t<ref>`; refs are routed through release-id's lenient
13
+ * {@link parseBranchName} and only `kind: 'version'` ids are kept (named
14
+ * `release/n/*` branches are irrelevant to `next`-bump math and are dropped).
15
+ * Returns the no-`v` semver tokens (e.g. `1.2.3`) that
16
+ * {@link collectKnownVersions} parses as versions. Pure — no I/O — so it is
17
+ * unit-testable without the network.
18
+ */
19
+ export const extractVersionBranches = (lsRemoteStdout: string): string[] => {
20
+ return lsRemoteStdout
21
+ .split('\n')
22
+ .map((line) => {
23
+ const tab = line.indexOf('\t')
24
+
25
+ if (tab === -1) return null
26
+
27
+ return parseBranchName(line.slice(tab + 1))
28
+ })
29
+ .filter((id): id is NonNullable<typeof id> => {
30
+ return id !== null && id.kind === 'version'
31
+ })
32
+ .map((id) => {
33
+ return id.raw
34
+ })
35
+ }
36
+
9
37
  const parseRemoteRefs = async (): Promise<string[]> => {
10
38
  const previousQuiet = $.quiet
11
39
 
12
40
  try {
13
41
  $.quiet = true
14
42
  const result = await $`git ls-remote --heads origin 'release/v*'`
15
- const lines = result.stdout.split('\n')
16
-
17
- return lines
18
- .map((line) => {
19
- const tab = line.indexOf('\t')
20
-
21
- if (tab === -1) return ''
22
43
 
23
- return line.slice(tab + 1).replace(/^refs\/heads\//, '')
24
- })
25
- .filter(Boolean)
44
+ return extractVersionBranches(result.stdout)
26
45
  } finally {
27
46
  $.quiet = previousQuiet
28
47
  }
@@ -1,3 +1,5 @@
1
+ import { parseReleaseRef, validateName } from 'src/lib/release-id'
2
+ import type { ReleaseId } from 'src/lib/release-id'
1
3
  import type { ReleaseType } from 'src/lib/release-utils'
2
4
 
3
5
  import { parseVersion, sortVersions } from './version-utils'
@@ -97,22 +99,56 @@ const isNextToken = (token: string): boolean => {
97
99
  return token.trim().toLowerCase() === NEXT_TOKEN
98
100
  }
99
101
 
100
- export interface ReleaseEntry {
102
+ /**
103
+ * A release spec is the parsed form of a versioned release request: a raw
104
+ * version token (`"1.2.5"` or `"next"`) plus its type and optional
105
+ * description. This is the unchanged output of {@link parseReleaseSpec} and the
106
+ * versioned-input shape consumed by {@link resolveReleaseEntries}.
107
+ */
108
+ export interface ReleaseSpec {
101
109
  version: string
102
110
  type: ReleaseType
103
111
  description?: string
104
112
  }
105
113
 
114
+ /**
115
+ * A named release request: a bare kebab-case name plus its type and optional
116
+ * description. Named releases never auto-bump; `"next"` is version-only.
117
+ */
118
+ export interface NamedReleaseInput {
119
+ name: string
120
+ type: ReleaseType
121
+ description?: string
122
+ }
123
+
124
+ /** Either a versioned spec or a named release request. */
125
+ export type ReleaseInput = ReleaseSpec | NamedReleaseInput
126
+
127
+ /**
128
+ * A fully resolved release entry. The {@link ReleaseId} carries the concrete
129
+ * identity (version or name) and is the single source for branch/PR/Jira
130
+ * formatting via the `release-id` module.
131
+ */
132
+ export interface ReleaseEntry {
133
+ id: ReleaseId
134
+ type: ReleaseType
135
+ description?: string
136
+ }
137
+
106
138
  const isReleaseType = (value: string): value is ReleaseType => {
107
139
  return value === 'regular' || value === 'hotfix'
108
140
  }
109
141
 
142
+ const isNamedReleaseInput = (input: ReleaseInput): input is NamedReleaseInput => {
143
+ return 'name' in input
144
+ }
145
+
110
146
  /**
111
147
  * Parse a CLI release spec of the form `version[:type[:description]]`.
112
148
  * Type defaults to "regular". Description is everything after the second
113
149
  * colon, so colons inside descriptions are preserved.
114
150
  */
115
- export const parseReleaseSpec = (raw: string): ReleaseEntry => {
151
+ export const parseReleaseSpec = (raw: string): ReleaseSpec => {
116
152
  const spec = raw.trim()
117
153
 
118
154
  if (spec === '') throw new Error('Release spec is empty')
@@ -135,23 +171,42 @@ export const parseReleaseSpec = (raw: string): ReleaseEntry => {
135
171
  throw new Error(`Invalid release type "${typeRaw}". Expected "regular" or "hotfix".`)
136
172
  }
137
173
 
138
- const entry: ReleaseEntry = { version, type: typeLower }
174
+ const entry: ReleaseSpec = { version, type: typeLower }
139
175
 
140
176
  if (description !== '') entry.description = description
141
177
 
142
178
  return entry
143
179
  }
144
180
 
181
+ const withDescription = (base: { id: ReleaseId; type: ReleaseType }, description?: string): ReleaseEntry => {
182
+ return description !== undefined && description !== '' ? { ...base, description } : base
183
+ }
184
+
185
+ const resolveNamedInput = (input: NamedReleaseInput): ReleaseEntry => {
186
+ const name = input.name.trim()
187
+
188
+ // validateName throws InvalidReleaseNameError with a specific message.
189
+ validateName(name)
190
+
191
+ return withDescription({ id: { kind: 'name', name, raw: name }, type: input.type }, input.description)
192
+ }
193
+
145
194
  /**
146
- * Resolve a list of release entries (each with its own type and optional
147
- * "next" version token) into entries with concrete versions. Each "next"
148
- * advances based on the running max so successive "next" tokens produce
149
- * sequential versions, even across mixed types.
195
+ * Resolve a list of release inputs into entries carrying a concrete
196
+ * {@link ReleaseId}. Versioned inputs use the existing spec format: the
197
+ * `"next"` token advances based on the running max so successive `"next"`
198
+ * tokens produce sequential versions (even across mixed types), then the
199
+ * concrete version is wrapped in a {@link ReleaseId}. Named inputs are
200
+ * validated via `validateName` and never auto-bump.
150
201
  */
151
- export const resolveReleaseEntries = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
202
+ export const resolveReleaseEntries = (entries: ReleaseInput[], known: SemVer[]): ReleaseEntry[] => {
152
203
  const running: SemVer[] = [...known]
153
204
 
154
205
  return entries.map((entry) => {
206
+ if (isNamedReleaseInput(entry)) {
207
+ return resolveNamedInput(entry)
208
+ }
209
+
155
210
  const trimmed = entry.version.trim()
156
211
 
157
212
  if (trimmed === '') {
@@ -163,7 +218,7 @@ export const resolveReleaseEntries = (entries: ReleaseEntry[], known: SemVer[]):
163
218
 
164
219
  running.push(parseVersion(`v${next}`))
165
220
 
166
- return { ...entry, version: next }
221
+ return withDescription({ id: parseReleaseRef(next), type: entry.type }, entry.description)
167
222
  }
168
223
 
169
224
  const parsed = tryParse(trimmed)
@@ -176,12 +231,12 @@ export const resolveReleaseEntries = (entries: ReleaseEntry[], known: SemVer[]):
176
231
 
177
232
  running.push(parsed)
178
233
 
179
- return { ...entry, version: explicit }
234
+ return withDescription({ id: parseReleaseRef(explicit), type: entry.type }, entry.description)
180
235
  })
181
236
  }
182
237
 
183
- export const hasNextToken = (entries: ReleaseEntry[]): boolean => {
238
+ export const hasNextToken = (entries: ReleaseInput[]): boolean => {
184
239
  return entries.some((e) => {
185
- return isNextToken(e.version)
240
+ return !isNamedReleaseInput(e) && isNextToken(e.version)
186
241
  })
187
242
  }
@@ -1,13 +1,22 @@
1
1
  /**
2
- * Parse version string into major, minor, patch numbers
2
+ * Parse a `v<semver>` token into major, minor, patch numbers.
3
+ *
4
+ * SemVer-ONLY: this assumes a `v<semver>` token and will produce `NaN`s for
5
+ * anything else. Callers that may see branch names or named releases
6
+ * (`release/n/<name>`) MUST use the `release-id` module (`parseBranchName` /
7
+ * `parseReleaseRef` + `compareReleaseIds`) instead. Only provably names-free
8
+ * callers (the `collectKnownVersions` / `next`-bump math path) may use this.
3
9
  */
4
10
  export const parseVersion = (versionStr: string): [number, number, number] => {
5
- return versionStr.replace('release/', '').slice(1).split('.').map(Number) as [number, number, number]
11
+ return versionStr.slice(1).split('.').map(Number) as [number, number, number]
6
12
  }
7
13
 
8
14
  /**
9
- * Sort version strings in ascending order
10
- * Note: Returns a new sorted array without mutating the original
15
+ * Sort version strings in ascending order.
16
+ * Note: Returns a new sorted array without mutating the original.
17
+ *
18
+ * SemVer-ONLY: relies on {@link parseVersion} and will NaN-sort named releases.
19
+ * Name-aware callers MUST use `compareReleaseIds` from the `release-id` module.
11
20
  */
12
21
  export const sortVersions = (versions: string[]): string[] => {
13
22
  return [...versions].sort((a, b) => {
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
 
3
+ import { auditMcpTool } from 'src/commands/audit'
3
4
  import { envClearMcpTool } from 'src/commands/env-clear'
4
5
  import { envListMcpTool } from 'src/commands/env-list'
5
6
  import { envLoadMcpTool } from 'src/commands/env-load'
@@ -31,6 +32,7 @@ const tools = [
31
32
  ghReleaseDeployAllMcpTool,
32
33
  ghReleaseDeploySelectedMcpTool,
33
34
  ghReleaseListMcpTool,
35
+ auditMcpTool,
34
36
  versionMcpTool,
35
37
  worktreesAddMcpTool,
36
38
  worktreesListMcpTool,
@@ -0,0 +1 @@
1
+ {"version":"4.1.8","results":[[":commands/env-load/__tests__/env-load.test.ts",{"duration":0,"failed":true}]]}
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { z } from 'zod/v4'
1
+ import type { z } from 'zod'
2
2
 
3
3
  export interface ToolsExecutionResult<TStructured = Record<string, unknown>> {
4
4
  [x: string]: unknown