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.
- package/.eslintcache +1 -1
- package/.omc/state/agent-replay-d367c3be-9c2a-48e7-bcea-b45861af568c.jsonl +2 -0
- package/.omc/state/agent-replay-f2846d8f-974c-486c-b16f-4bdaa28ca45f.jsonl +1 -0
- package/.omc/state/last-tool-error.json +7 -0
- package/.omc/state/subagent-tracking.json +7 -0
- package/.turbo/turbo-eslint-check.log +2 -4
- package/.turbo/turbo-eslint-fix.log +1 -0
- package/.turbo/turbo-prettier-check.log +2 -4
- package/.turbo/turbo-prettier-fix.log +1 -10
- package/.turbo/turbo-test.log +12 -191
- package/.turbo/turbo-ts-check.log +2 -9
- package/dist/cli.js +69 -44
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +45 -34
- package/dist/mcp.js.map +4 -4
- package/package.json +11 -11
- package/src/commands/config/config.ts +1 -1
- package/src/commands/doctor/doctor.ts +62 -12
- package/src/commands/env-clear/env-clear.ts +5 -10
- package/src/commands/env-list/env-list.ts +5 -10
- package/src/commands/env-load/env-load.ts +5 -10
- package/src/commands/env-status/env-status.ts +5 -10
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +17 -18
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +290 -89
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +15 -14
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +30 -23
- package/src/commands/gh-release-list/gh-release-list.ts +5 -10
- package/src/commands/init/init.ts +17 -6
- package/src/commands/release-create/release-create.ts +223 -139
- package/src/commands/release-desc-edit/index.ts +1 -0
- package/src/commands/release-desc-edit/release-desc-edit.ts +207 -0
- package/src/commands/version/version.ts +5 -10
- package/src/commands/worktrees-add/worktrees-add.ts +34 -26
- package/src/commands/worktrees-list/worktrees-list.ts +6 -11
- package/src/commands/worktrees-open/worktrees-open.ts +10 -6
- package/src/commands/worktrees-remove/worktrees-remove.ts +18 -14
- package/src/commands/worktrees-sync/worktrees-sync.ts +17 -12
- package/src/entry/cli.ts +24 -21
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +21 -0
- package/src/integrations/gh/gh-release-prs/index.ts +1 -1
- package/src/integrations/gh/index.ts +1 -1
- package/src/integrations/jira/api.ts +8 -17
- package/src/integrations/jira/index.ts +2 -0
- package/src/lib/__tests__/infra-kit-config.test.ts +50 -0
- package/src/lib/errors/__tests__/operation-error.test.ts +62 -0
- package/src/lib/errors/format-zx-error.ts +54 -0
- package/src/lib/errors/operation-error.ts +80 -0
- package/src/lib/infra-kit-config/infra-kit-config.ts +7 -0
- package/src/lib/version-utils/__tests__/next-version.test.ts +128 -23
- package/src/lib/version-utils/index.ts +4 -2
- package/src/lib/version-utils/next-version.ts +64 -25
- package/src/mcp/tools/index.ts +2 -2
- package/src/types.ts +56 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/commands/release-create-batch/index.ts +0 -1
- package/src/commands/release-create-batch/release-create-batch.ts +0 -222
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
NoPriorVersionsError,
|
|
5
5
|
collectKnownVersions,
|
|
6
6
|
computeNextVersion,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
parseReleaseSpec,
|
|
8
|
+
resolveReleaseEntries,
|
|
9
9
|
} from '../next-version'
|
|
10
10
|
|
|
11
11
|
describe('collectKnownVersions', () => {
|
|
@@ -70,43 +70,148 @@ describe('computeNextVersion', () => {
|
|
|
70
70
|
})
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
-
describe('
|
|
74
|
-
|
|
73
|
+
describe('parseReleaseSpec', () => {
|
|
74
|
+
it('parses bare version as regular with no description', () => {
|
|
75
|
+
expect(parseReleaseSpec('1.2.5')).toEqual({ version: '1.2.5', type: 'regular' })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('parses version:type', () => {
|
|
79
|
+
expect(parseReleaseSpec('1.2.5:hotfix')).toEqual({ version: '1.2.5', type: 'hotfix' })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('parses version:type:description', () => {
|
|
83
|
+
expect(parseReleaseSpec('1.2.5:regular:Holiday backend')).toEqual({
|
|
84
|
+
version: '1.2.5',
|
|
85
|
+
type: 'regular',
|
|
86
|
+
description: 'Holiday backend',
|
|
87
|
+
})
|
|
88
|
+
})
|
|
75
89
|
|
|
76
|
-
it('
|
|
77
|
-
expect(
|
|
90
|
+
it('preserves colons inside the description', () => {
|
|
91
|
+
expect(parseReleaseSpec('1.2.5:regular:Fixes: A and B')).toEqual({
|
|
92
|
+
version: '1.2.5',
|
|
93
|
+
type: 'regular',
|
|
94
|
+
description: 'Fixes: A and B',
|
|
95
|
+
})
|
|
78
96
|
})
|
|
79
97
|
|
|
80
|
-
it('
|
|
81
|
-
expect(
|
|
98
|
+
it('accepts the literal "next" token', () => {
|
|
99
|
+
expect(parseReleaseSpec('next:hotfix')).toEqual({ version: 'next', type: 'hotfix' })
|
|
82
100
|
})
|
|
83
101
|
|
|
84
|
-
it('
|
|
85
|
-
expect(
|
|
102
|
+
it('lowercases the type', () => {
|
|
103
|
+
expect(parseReleaseSpec('1.2.5:HOTFIX')).toEqual({ version: '1.2.5', type: 'hotfix' })
|
|
86
104
|
})
|
|
87
105
|
|
|
88
|
-
it('
|
|
89
|
-
expect(
|
|
106
|
+
it('drops empty description', () => {
|
|
107
|
+
expect(parseReleaseSpec('1.2.5:regular:')).toEqual({ version: '1.2.5', type: 'regular' })
|
|
90
108
|
})
|
|
91
109
|
|
|
92
|
-
it('throws on
|
|
110
|
+
it('throws on unknown type', () => {
|
|
93
111
|
expect(() => {
|
|
94
|
-
return
|
|
95
|
-
}).toThrow(/Invalid
|
|
112
|
+
return parseReleaseSpec('1.2.5:major')
|
|
113
|
+
}).toThrow(/Invalid release type/)
|
|
96
114
|
})
|
|
97
115
|
|
|
98
|
-
it('
|
|
99
|
-
expect(
|
|
116
|
+
it('throws on empty spec', () => {
|
|
117
|
+
expect(() => {
|
|
118
|
+
return parseReleaseSpec(' ')
|
|
119
|
+
}).toThrow(/empty/)
|
|
100
120
|
})
|
|
101
121
|
})
|
|
102
122
|
|
|
103
|
-
describe('
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
describe('resolveReleaseEntries', () => {
|
|
124
|
+
const known = collectKnownVersions({ remoteBranches: ['release/v1.63.0'] })
|
|
125
|
+
|
|
126
|
+
it('passes through explicit semver entries unchanged', () => {
|
|
127
|
+
expect(
|
|
128
|
+
resolveReleaseEntries(
|
|
129
|
+
[
|
|
130
|
+
{ version: '1.70.0', type: 'regular' },
|
|
131
|
+
{ version: 'v1.70.1', type: 'hotfix' },
|
|
132
|
+
],
|
|
133
|
+
known,
|
|
134
|
+
),
|
|
135
|
+
).toEqual([
|
|
136
|
+
{ version: '1.70.0', type: 'regular' },
|
|
137
|
+
{ version: '1.70.1', type: 'hotfix' },
|
|
138
|
+
])
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('resolves a single "next" using the entry type', () => {
|
|
142
|
+
expect(resolveReleaseEntries([{ version: 'next', type: 'regular' }], known)).toEqual([
|
|
143
|
+
{ version: '1.64.0', type: 'regular' },
|
|
144
|
+
])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('advances sequential "next" tokens of the same type', () => {
|
|
148
|
+
expect(
|
|
149
|
+
resolveReleaseEntries(
|
|
150
|
+
[
|
|
151
|
+
{ version: 'next', type: 'regular' },
|
|
152
|
+
{ version: 'next', type: 'regular' },
|
|
153
|
+
],
|
|
154
|
+
known,
|
|
155
|
+
),
|
|
156
|
+
).toEqual([
|
|
157
|
+
{ version: '1.64.0', type: 'regular' },
|
|
158
|
+
{ version: '1.65.0', type: 'regular' },
|
|
159
|
+
])
|
|
106
160
|
})
|
|
107
161
|
|
|
108
|
-
it('
|
|
109
|
-
expect(
|
|
110
|
-
|
|
162
|
+
it('advances sequential "next" tokens across mixed types', () => {
|
|
163
|
+
expect(
|
|
164
|
+
resolveReleaseEntries(
|
|
165
|
+
[
|
|
166
|
+
{ version: 'next', type: 'regular' },
|
|
167
|
+
{ version: 'next', type: 'hotfix' },
|
|
168
|
+
],
|
|
169
|
+
known,
|
|
170
|
+
),
|
|
171
|
+
).toEqual([
|
|
172
|
+
{ version: '1.64.0', type: 'regular' },
|
|
173
|
+
{ version: '1.64.1', type: 'hotfix' },
|
|
174
|
+
])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('mixes literals and "next", advancing the running max', () => {
|
|
178
|
+
expect(
|
|
179
|
+
resolveReleaseEntries(
|
|
180
|
+
[
|
|
181
|
+
{ version: 'next', type: 'regular' },
|
|
182
|
+
{ version: '1.70.0', type: 'regular' },
|
|
183
|
+
{ version: 'next', type: 'regular' },
|
|
184
|
+
],
|
|
185
|
+
known,
|
|
186
|
+
),
|
|
187
|
+
).toEqual([
|
|
188
|
+
{ version: '1.64.0', type: 'regular' },
|
|
189
|
+
{ version: '1.70.0', type: 'regular' },
|
|
190
|
+
{ version: '1.71.0', type: 'regular' },
|
|
191
|
+
])
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('preserves description through resolution', () => {
|
|
195
|
+
expect(resolveReleaseEntries([{ version: 'next', type: 'regular', description: 'Holiday' }], known)).toEqual([
|
|
196
|
+
{ version: '1.64.0', type: 'regular', description: 'Holiday' },
|
|
197
|
+
])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('accepts case-insensitive "next"', () => {
|
|
201
|
+
expect(resolveReleaseEntries([{ version: 'NEXT', type: 'regular' }], known)).toEqual([
|
|
202
|
+
{ version: '1.64.0', type: 'regular' },
|
|
203
|
+
])
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('throws on invalid version', () => {
|
|
207
|
+
expect(() => {
|
|
208
|
+
return resolveReleaseEntries([{ version: 'nope', type: 'regular' }], known)
|
|
209
|
+
}).toThrow(/Invalid version/)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('throws NoPriorVersionsError when "next" with no known versions', () => {
|
|
213
|
+
expect(() => {
|
|
214
|
+
return resolveReleaseEntries([{ version: 'next', type: 'regular' }], [])
|
|
215
|
+
}).toThrow(NoPriorVersionsError)
|
|
111
216
|
})
|
|
112
217
|
})
|
|
@@ -3,10 +3,12 @@ export {
|
|
|
3
3
|
collectKnownVersions,
|
|
4
4
|
computeNextVersion,
|
|
5
5
|
type ExistingVersionsSources,
|
|
6
|
+
hasNextToken,
|
|
6
7
|
NEXT_TOKEN,
|
|
7
8
|
NoPriorVersionsError,
|
|
8
|
-
|
|
9
|
+
parseReleaseSpec,
|
|
10
|
+
type ReleaseEntry,
|
|
11
|
+
resolveReleaseEntries,
|
|
9
12
|
type SemVer,
|
|
10
|
-
splitVersionInput,
|
|
11
13
|
} from './next-version'
|
|
12
14
|
export { parseVersion, sortVersions } from './version-utils'
|
|
@@ -97,26 +97,73 @@ const isNextToken = (token: string): boolean => {
|
|
|
97
97
|
return token.trim().toLowerCase() === NEXT_TOKEN
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export interface ReleaseEntry {
|
|
101
|
+
version: string
|
|
102
|
+
type: ReleaseType
|
|
103
|
+
description?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isReleaseType = (value: string): value is ReleaseType => {
|
|
107
|
+
return value === 'regular' || value === 'hotfix'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse a CLI release spec of the form `version[:type[:description]]`.
|
|
112
|
+
* Type defaults to "regular". Description is everything after the second
|
|
113
|
+
* colon, so colons inside descriptions are preserved.
|
|
114
|
+
*/
|
|
115
|
+
export const parseReleaseSpec = (raw: string): ReleaseEntry => {
|
|
116
|
+
const spec = raw.trim()
|
|
117
|
+
|
|
118
|
+
if (spec === '') throw new Error('Release spec is empty')
|
|
119
|
+
|
|
120
|
+
const firstColon = spec.indexOf(':')
|
|
121
|
+
|
|
122
|
+
if (firstColon === -1) {
|
|
123
|
+
return { version: spec, type: 'regular' }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const version = spec.slice(0, firstColon).trim()
|
|
127
|
+
const rest = spec.slice(firstColon + 1)
|
|
128
|
+
const secondColon = rest.indexOf(':')
|
|
129
|
+
|
|
130
|
+
const typeRaw = secondColon === -1 ? rest.trim() : rest.slice(0, secondColon).trim()
|
|
131
|
+
const description = secondColon === -1 ? '' : rest.slice(secondColon + 1).trim()
|
|
132
|
+
const typeLower = typeRaw.toLowerCase()
|
|
133
|
+
|
|
134
|
+
if (!isReleaseType(typeLower)) {
|
|
135
|
+
throw new Error(`Invalid release type "${typeRaw}". Expected "regular" or "hotfix".`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const entry: ReleaseEntry = { version, type: typeLower }
|
|
139
|
+
|
|
140
|
+
if (description !== '') entry.description = description
|
|
141
|
+
|
|
142
|
+
return entry
|
|
143
|
+
}
|
|
144
|
+
|
|
100
145
|
/**
|
|
101
|
-
* Resolve a list of
|
|
102
|
-
* into concrete
|
|
103
|
-
* max so "next
|
|
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.
|
|
104
150
|
*/
|
|
105
|
-
export const
|
|
151
|
+
export const resolveReleaseEntries = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
|
|
106
152
|
const running: SemVer[] = [...known]
|
|
107
|
-
const resolved: string[] = []
|
|
108
153
|
|
|
109
|
-
|
|
110
|
-
const trimmed =
|
|
154
|
+
return entries.map((entry) => {
|
|
155
|
+
const trimmed = entry.version.trim()
|
|
111
156
|
|
|
112
|
-
if (trimmed === '')
|
|
157
|
+
if (trimmed === '') {
|
|
158
|
+
throw new Error('Release entry has an empty version')
|
|
159
|
+
}
|
|
113
160
|
|
|
114
161
|
if (isNextToken(trimmed)) {
|
|
115
|
-
const next = computeNextVersion(running, type)
|
|
162
|
+
const next = computeNextVersion(running, entry.type)
|
|
116
163
|
|
|
117
|
-
resolved.push(next)
|
|
118
164
|
running.push(parseVersion(`v${next}`))
|
|
119
|
-
|
|
165
|
+
|
|
166
|
+
return { ...entry, version: next }
|
|
120
167
|
}
|
|
121
168
|
|
|
122
169
|
const parsed = tryParse(trimmed)
|
|
@@ -127,22 +174,14 @@ export const resolveVersionTokens = (tokens: string[], type: ReleaseType, known:
|
|
|
127
174
|
|
|
128
175
|
const explicit = `${parsed[0]}.${parsed[1]}.${parsed[2]}`
|
|
129
176
|
|
|
130
|
-
resolved.push(explicit)
|
|
131
177
|
running.push(parsed)
|
|
132
|
-
}
|
|
133
178
|
|
|
134
|
-
|
|
179
|
+
return { ...entry, version: explicit }
|
|
180
|
+
})
|
|
135
181
|
}
|
|
136
182
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
export const splitVersionInput = (input: string): string[] => {
|
|
142
|
-
return input
|
|
143
|
-
.split(',')
|
|
144
|
-
.map((t) => {
|
|
145
|
-
return t.trim()
|
|
146
|
-
})
|
|
147
|
-
.filter(Boolean)
|
|
183
|
+
export const hasNextToken = (entries: ReleaseEntry[]): boolean => {
|
|
184
|
+
return entries.some((e) => {
|
|
185
|
+
return isNextToken(e.version)
|
|
186
|
+
})
|
|
148
187
|
}
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { ghReleaseDeployAllMcpTool } from 'src/commands/gh-release-deploy-all'
|
|
|
10
10
|
import { ghReleaseDeploySelectedMcpTool } from 'src/commands/gh-release-deploy-selected'
|
|
11
11
|
import { ghReleaseListMcpTool } from 'src/commands/gh-release-list'
|
|
12
12
|
import { releaseCreateMcpTool } from 'src/commands/release-create'
|
|
13
|
-
import {
|
|
13
|
+
import { releaseDescEditMcpTool } from 'src/commands/release-desc-edit'
|
|
14
14
|
import { versionMcpTool } from 'src/commands/version'
|
|
15
15
|
import { worktreesAddMcpTool } from 'src/commands/worktrees-add'
|
|
16
16
|
import { worktreesListMcpTool } from 'src/commands/worktrees-list'
|
|
@@ -26,7 +26,7 @@ const tools = [
|
|
|
26
26
|
envClearMcpTool,
|
|
27
27
|
ghMergeDevMcpTool,
|
|
28
28
|
releaseCreateMcpTool,
|
|
29
|
-
|
|
29
|
+
releaseDescEditMcpTool,
|
|
30
30
|
ghReleaseDeliverMcpTool,
|
|
31
31
|
ghReleaseDeployAllMcpTool,
|
|
32
32
|
ghReleaseDeploySelectedMcpTool,
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,66 @@
|
|
|
1
|
-
|
|
1
|
+
import type { z } from 'zod/v4'
|
|
2
|
+
|
|
3
|
+
export interface ToolsExecutionResult<TStructured = Record<string, unknown>> {
|
|
2
4
|
[x: string]: unknown
|
|
3
5
|
content: {
|
|
4
6
|
type: 'text'
|
|
5
7
|
text: string
|
|
6
8
|
}[]
|
|
7
|
-
structuredContent?:
|
|
9
|
+
structuredContent?: TStructured
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export interface RequiredConfirmedOptionArg {
|
|
11
13
|
confirmedCommand: boolean
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
export interface McpTool<TIn extends z.ZodRawShape = z.ZodRawShape, TOut extends z.ZodRawShape = z.ZodRawShape> {
|
|
17
|
+
name: string
|
|
18
|
+
description: string
|
|
19
|
+
inputSchema: TIn
|
|
20
|
+
outputSchema: TOut
|
|
21
|
+
handler: (
|
|
22
|
+
params: z.infer<z.ZodObject<TIn>> & RequiredConfirmedOptionArg,
|
|
23
|
+
) => Promise<ToolsExecutionResult<z.infer<z.ZodObject<TOut>>>>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the dual-channel content array shared by every MCP tool. Narrows the
|
|
28
|
+
* literal `type: 'text'` so handlers can use inferred return types without TS
|
|
29
|
+
* widening `type` to `string` — which would otherwise break assignability
|
|
30
|
+
* against the MCP SDK's content union.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* return {
|
|
34
|
+
* content: textContent(JSON.stringify(structuredContent, null, 2)),
|
|
35
|
+
* structuredContent,
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
export const textContent = (text: string): ToolsExecutionResult['content'] => {
|
|
39
|
+
return [{ type: 'text', text }]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Factory that ties the handler's return type to the declared `outputSchema`
|
|
44
|
+
* so `structuredContent` is checked against the schema at compile time. If a
|
|
45
|
+
* handler accidentally drops or renames a field, TS errors at the registration
|
|
46
|
+
* site rather than at runtime in an MCP client.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* export const envLoadMcpTool = defineMcpTool({
|
|
50
|
+
* name: 'env-load',
|
|
51
|
+
* description: '...',
|
|
52
|
+
* inputSchema: { config: z.string() },
|
|
53
|
+
* outputSchema: {
|
|
54
|
+
* filePath: z.string(),
|
|
55
|
+
* variableCount: z.number(),
|
|
56
|
+
* project: z.string(),
|
|
57
|
+
* config: z.string(),
|
|
58
|
+
* },
|
|
59
|
+
* handler: envLoad,
|
|
60
|
+
* })
|
|
61
|
+
*/
|
|
62
|
+
export const defineMcpTool = <TIn extends z.ZodRawShape, TOut extends z.ZodRawShape>(
|
|
63
|
+
tool: McpTool<TIn, TOut>,
|
|
64
|
+
): McpTool<TIn, TOut> => {
|
|
65
|
+
return tool
|
|
66
|
+
}
|