diff --git a/vscode/src/panel/KanbanPanel.ts b/vscode/src/panel/KanbanPanel.ts index 998111b..0aedbc9 100644 --- a/vscode/src/panel/KanbanPanel.ts +++ b/vscode/src/panel/KanbanPanel.ts @@ -865,7 +865,9 @@ export class KanbanWebviewPanel { autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, devBranch: s.devBranch, - autoBuildBranch: s.autoBuildBranch, + releaseBranchPrefix: s.releaseBranchPrefix, + jenkinsJob: s.jenkinsJob, + jenkinsProdHost: s.jenkinsProdHost, worktreeDirectory: s.worktreeDirectory, worktreeOpenWith: s.worktreeOpenWith, pycharmCommand: s.pycharmCommand, @@ -945,7 +947,9 @@ export class KanbanWebviewPanel { autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, devBranch: s.devBranch, - autoBuildBranch: s.autoBuildBranch, + releaseBranchPrefix: s.releaseBranchPrefix, + jenkinsJob: s.jenkinsJob, + jenkinsProdHost: s.jenkinsProdHost, worktreeDirectory: s.worktreeDirectory, worktreeOpenWith: s.worktreeOpenWith, pycharmCommand: s.pycharmCommand, diff --git a/vscode/src/panel/handlers/branchCleanup.ts b/vscode/src/panel/handlers/branchCleanup.ts index cd8b8ee..6c7462b 100644 --- a/vscode/src/panel/handlers/branchCleanup.ts +++ b/vscode/src/panel/handlers/branchCleanup.ts @@ -5,7 +5,7 @@ import { logger } from '../../logging/logger' /** * 清理一个 feature 分支(远程 + 本地),供「拖到完成」和「手动删除 worktree」两处共用。 * - * - 受保护分支(main/master/devBranch/autoBuildBranch)直接跳过,绝不删。 + * - 受保护分支(main/master/devBranch)直接跳过,绝不删。 * - 远程删除走 gitea API,需 remote + token;缺任一则只删本地,不阻断。 * - 本地删除是强删(deleteLocalBranch 内部 `git branch -D`):调用方语义是「连分支一起删」, * 未合并也照删。 @@ -20,17 +20,16 @@ export async function cleanupFeatureBranch(opts: { branch: string issueNumber: number devBranch?: string - autoBuildBranch?: string /** 缺 remote 或 token 时跳过远程删除,只删本地。 */ remote?: { host: string, owner: string, repo: string } token?: string }): Promise { - const { workspaceRoot, branch, issueNumber, devBranch, autoBuildBranch, remote, token } = opts + const { workspaceRoot, branch, issueNumber, devBranch, remote, token } = opts if (!branch) return const protectedBranches = new Set( - ['main', 'master', devBranch, autoBuildBranch].filter((b): b is string => Boolean(b)), + ['main', 'master', devBranch].filter((b): b is string => Boolean(b)), ) if (protectedBranches.has(branch)) { logger.add({ level: 'warn', source: 'panel', message: `跳过受保护分支清理 ${branch} (issue #${issueNumber})` }) diff --git a/vscode/src/panel/handlers/handoffFlow.ts b/vscode/src/panel/handlers/handoffFlow.ts index 447b11e..2062148 100644 --- a/vscode/src/panel/handlers/handoffFlow.ts +++ b/vscode/src/panel/handlers/handoffFlow.ts @@ -342,7 +342,7 @@ export async function handleHandoffStart(panel: KanbanWebviewPanel, issueNumber: } if (branch) { const settings = getSettings(panel.context) - await cleanupFeatureBranch({ workspaceRoot, branch, issueNumber, devBranch: settings.devBranch, autoBuildBranch: settings.autoBuildBranch }) + await cleanupFeatureBranch({ workspaceRoot, branch, issueNumber, devBranch: settings.devBranch }) } // ⑦ 本机墓碑:sid/profile/worktreePath 归零。worktree 没删掉时保留 diff --git a/vscode/src/panel/handlers/issues.ts b/vscode/src/panel/handlers/issues.ts index b42b23e..63d6cfd 100644 --- a/vscode/src/panel/handlers/issues.ts +++ b/vscode/src/panel/handlers/issues.ts @@ -506,7 +506,6 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber: branch: featureBranch, issueNumber, devBranch: settingsForBranchCleanup.devBranch, - autoBuildBranch: settingsForBranchCleanup.autoBuildBranch, remote, token, }) @@ -786,7 +785,6 @@ export async function handleResetToTodo(panel: KanbanWebviewPanel, issueNumber: branch: featureBranch, issueNumber, devBranch: settingsForBranchCleanup.devBranch, - autoBuildBranch: settingsForBranchCleanup.autoBuildBranch, remote, token, }) diff --git a/vscode/src/panel/handlers/sessions.ts b/vscode/src/panel/handlers/sessions.ts index 48abcf8..fd7911f 100644 --- a/vscode/src/panel/handlers/sessions.ts +++ b/vscode/src/panel/handlers/sessions.ts @@ -761,7 +761,6 @@ export async function handleImplement( branch, issueNumber, devBranch: settings.devBranch, - autoBuildBranch: settings.autoBuildBranch, remote, token, }) diff --git a/vscode/src/panel/handlers/settings.ts b/vscode/src/panel/handlers/settings.ts index d55d44f..16aa037 100644 --- a/vscode/src/panel/handlers/settings.ts +++ b/vscode/src/panel/handlers/settings.ts @@ -28,7 +28,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -55,7 +57,10 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { const trimmedYtClose = payload.youtrackCloseCommand.trim() const trimmedYtToken = payload.youtrackToken.trim() const trimmedDevBranch = payload.devBranch.trim() - const trimmedAutoBuildBranch = payload.autoBuildBranch.trim() + // 发布前缀 / Jenkins 任务 / 正式服 host:只 trim,空值回退在 getSettings 读时做。 + const trimmedReleasePrefix = payload.releaseBranchPrefix.trim() + const trimmedJenkinsJob = payload.jenkinsJob.trim() + const trimmedJenkinsProdHost = payload.jenkinsProdHost.trim() // worktree dir template: '' = "use default", so don't preserve blanks // specially — getSettings falls back to the default at read time. const trimmedWorktreeDir = payload.worktreeDirectory.trim() @@ -101,7 +106,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { autoReview: payload.autoReview, reviewPrompt: payload.reviewPrompt || prev.reviewPrompt, devBranch: trimmedDevBranch || prev.devBranch, - autoBuildBranch: trimmedAutoBuildBranch, + releaseBranchPrefix: trimmedReleasePrefix || prev.releaseBranchPrefix, + jenkinsJob: trimmedJenkinsJob, + jenkinsProdHost: trimmedJenkinsProdHost || prev.jenkinsProdHost, worktreeDirectory: trimmedWorktreeDir || prev.worktreeDirectory, worktreeOpenWith: payload.worktreeOpenWith || prev.worktreeOpenWith, pycharmCommand: trimmedPycharmCmd || prev.pycharmCommand, @@ -126,11 +133,10 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { implementPlanPrompt: payload.implementPlanPrompt, autoReview: payload.autoReview, reviewPrompt: payload.reviewPrompt, - // Persist trimmed values; '' is meaningful for autoBuildBranch - // ("follow devBranch"), so don't coerce — getSettings handles the - // fallback at read time. devBranch: trimmedDevBranch, - autoBuildBranch: trimmedAutoBuildBranch, + releaseBranchPrefix: trimmedReleasePrefix, + jenkinsJob: trimmedJenkinsJob, + jenkinsProdHost: trimmedJenkinsProdHost, worktreeDirectory: trimmedWorktreeDir, worktreeOpenWith: payload.worktreeOpenWith, pycharmCommand: trimmedPycharmCmd, @@ -233,7 +239,9 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom autoReview: s.autoReview, reviewPrompt: s.reviewPrompt, devBranch: s.devBranch, - autoBuildBranch: s.autoBuildBranch, + releaseBranchPrefix: s.releaseBranchPrefix, + jenkinsJob: s.jenkinsJob, + jenkinsProdHost: s.jenkinsProdHost, worktreeDirectory: s.worktreeDirectory, worktreeOpenWith: s.worktreeOpenWith, pycharmCommand: s.pycharmCommand, diff --git a/vscode/src/panel/handlers/worktree.ts b/vscode/src/panel/handlers/worktree.ts index 254f5a6..e6fac33 100644 --- a/vscode/src/panel/handlers/worktree.ts +++ b/vscode/src/panel/handlers/worktree.ts @@ -139,7 +139,6 @@ export async function handleDeleteWorktree(panel: KanbanWebviewPanel, issueNumbe branch, issueNumber, devBranch: settingsForHook.devBranch, - autoBuildBranch: settingsForHook.autoBuildBranch, remote: remote ? { host: remote.host, owner: remote.owner, repo: remote.repo } : undefined, token, }) diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index 028f70c..65b690b 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -73,7 +73,9 @@ export type ExtensionToWebview autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -109,13 +111,15 @@ export type ExtensionToWebview | { type: 'logs/cleared' } | { type: 'commit/state', running: boolean } | { type: 'commit/has-changes', value: boolean } + // 发布点状态:nextBranch 是今天要切的分支全名,由扩展侧算好,webview 不自己拼日期。 | { - type: 'branch-sync/status' - behind: number + type: 'release/status' devBranch: string - autoBuildBranch: string - unavailable?: boolean - reason?: string + current?: string + nextBranch: string + unreleased: number + unavailable?: string + running: boolean } | { type: 'env-lock/status', locked: boolean, fileCount: number, failedCount?: number } | { type: 'issue/remove', issueNumber: number } @@ -145,7 +149,9 @@ export type WebviewToExtension autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -200,8 +206,10 @@ export type WebviewToExtension | { type: 'logs/clear' } | { type: 'commit/run' } | { type: 'session/close-tab', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' } - | { type: 'branch-sync/check' } - | { type: 'branch-sync/run' } + | { type: 'release/check' } + | { type: 'release/cut' } + | { type: 'release/cherry-pick' } + | { type: 'release/deploy' } | { type: 'env-lock/check' } | { type: 'env-lock/toggle' } | { type: 'issue/delete', issueNumber: number } diff --git a/vscode/src/settings/store.ts b/vscode/src/settings/store.ts index 626e119..8a3173a 100644 --- a/vscode/src/settings/store.ts +++ b/vscode/src/settings/store.ts @@ -15,6 +15,7 @@ import { readFileSync } from 'node:fs' import path from 'node:path' import { seedCommitProfilePath } from '../cc/commitProfile' import { listClaudeProfiles } from '../cc/profiles' +import { normalizeReleasePrefix } from '../git/release' /** * Fallback prompt strings used when the on-disk markdown can't be read. @@ -88,11 +89,14 @@ export interface Settings { /** Day-to-day development branch (e.g. `main`). Jenkins doesn't watch it. */ devBranch: string /** - * Branch the Gitea webhook → Jenkins job listens on. Empty string means - * "same as `devBranch`", in which case the branch-sync button stays - * disabled because there's nothing to fast-forward. + * 发布分支名前缀(如 `release/`),远端该前缀下字典序最大的分支即当期发布点。 + * 读出时已归一化:trim、空回退默认、末尾补 `/`。 */ - autoBuildBranch: string + releaseBranchPrefix: string + /** 正式服部署的 Jenkins 任务名;空串 = 不提供「部署到正式服」。 */ + jenkinsJob: string + /** Jenkins 任务的 `REMOTE_HOST` 参数取值,空回退 `正式服`。 */ + jenkinsProdHost: string /** * Directory template for the worktree created when implementing a plan. * Placeholders: `$project_root` (workspace abs path), `$project_name` (its @@ -215,7 +219,9 @@ function defaults(ctx: ExtensionContext): Settings { autoReview: true, reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'), devBranch: 'main', - autoBuildBranch: '', + releaseBranchPrefix: 'release/', + jenkinsJob: '', + jenkinsProdHost: '正式服', worktreeDirectory: '~/Sources/worktree/$project_name/$feature_name', worktreeOpenWith: 'vscode', pycharmCommand: 'pycharm', @@ -266,12 +272,17 @@ export function getSettings(ctx: ExtensionContext): Settings { const devBranch = typeof stored.devBranch === 'string' && stored.devBranch.length > 0 ? stored.devBranch : base.devBranch - // autoBuildBranch is kept as-is — '' is meaningful ("follow devBranch, sync - // disabled"), so we don't coerce to a default here. Anything non-string - // (legacy installs that never wrote it) collapses to ''. - const autoBuildBranch = typeof stored.autoBuildBranch === 'string' - ? stored.autoBuildBranch - : base.autoBuildBranch + // 发布前缀读出时归一化(空 → 默认、补尾 `/`),下游拼分支名不用再防御。 + const releaseBranchPrefix = normalizeReleasePrefix( + typeof stored.releaseBranchPrefix === 'string' ? stored.releaseBranchPrefix : base.releaseBranchPrefix, + ) + // jenkinsJob:'' 有意义(= 不提供部署动作),只认字符串类型。 + const jenkinsJob = typeof stored.jenkinsJob === 'string' + ? stored.jenkinsJob.trim() + : base.jenkinsJob + const jenkinsProdHost = typeof stored.jenkinsProdHost === 'string' && stored.jenkinsProdHost.trim().length > 0 + ? stored.jenkinsProdHost.trim() + : base.jenkinsProdHost // worktree dir template: empty/blank means "use the default template", so // fall back to base (unlike the hook paths below, where '' is meaningful). const worktreeDirectory = typeof stored.worktreeDirectory === 'string' && stored.worktreeDirectory.length > 0 @@ -349,7 +360,9 @@ export function getSettings(ctx: ExtensionContext): Settings { autoReview, reviewPrompt, devBranch, - autoBuildBranch, + releaseBranchPrefix, + jenkinsJob, + jenkinsProdHost, worktreeDirectory, worktreeOpenWith, pycharmCommand, diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 8f358a8..8d80a9f 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -523,7 +523,9 @@ export function App() { initialAutoReview={settings?.autoReview ?? true} initialReviewPrompt={settings?.reviewPrompt ?? ''} initialDevBranch={settings?.devBranch ?? 'main'} - initialAutoBuildBranch={settings?.autoBuildBranch ?? ''} + initialReleaseBranchPrefix={settings?.releaseBranchPrefix ?? 'release/'} + initialJenkinsJob={settings?.jenkinsJob ?? ''} + initialJenkinsProdHost={settings?.jenkinsProdHost ?? '正式服'} initialWorktreeDirectory={settings?.worktreeDirectory ?? ''} initialWorktreeOpenWith={settings?.worktreeOpenWith ?? 'vscode'} initialPycharmCommand={settings?.pycharmCommand ?? 'pycharm'} diff --git a/vscode/webview-ui/src/components/SettingsModal.tsx b/vscode/webview-ui/src/components/SettingsModal.tsx index f5ab49d..0a31e08 100644 --- a/vscode/webview-ui/src/components/SettingsModal.tsx +++ b/vscode/webview-ui/src/components/SettingsModal.tsx @@ -26,7 +26,9 @@ interface SubmitValues { autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -73,7 +75,9 @@ export interface SettingsModalProps { initialAutoReview: boolean initialReviewPrompt: string initialDevBranch: string - initialAutoBuildBranch: string + initialReleaseBranchPrefix: string + initialJenkinsJob: string + initialJenkinsProdHost: string initialWorktreeDirectory: string initialWorktreeOpenWith: 'vscode' | 'pycharm' initialPycharmCommand: string @@ -156,7 +160,9 @@ export function SettingsModal({ initialAutoReview, initialReviewPrompt, initialDevBranch, - initialAutoBuildBranch, + initialReleaseBranchPrefix, + initialJenkinsJob, + initialJenkinsProdHost, initialWorktreeDirectory, initialWorktreeOpenWith, initialPycharmCommand, @@ -191,7 +197,9 @@ export function SettingsModal({ const [autoReview, setAutoReview] = useState(initialAutoReview) const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt) const [devBranch, setDevBranch] = useState(initialDevBranch) - const [autoBuildBranch, setAutoBuildBranch] = useState(initialAutoBuildBranch) + const [releaseBranchPrefix, setReleaseBranchPrefix] = useState(initialReleaseBranchPrefix) + const [jenkinsJob, setJenkinsJob] = useState(initialJenkinsJob) + const [jenkinsProdHost, setJenkinsProdHost] = useState(initialJenkinsProdHost) const [worktreeDirectory, setWorktreeDirectory] = useState(initialWorktreeDirectory) const [worktreeOpenWith, setWorktreeOpenWith] = useState(initialWorktreeOpenWith) const [pycharmCommand, setPycharmCommand] = useState(initialPycharmCommand) @@ -228,7 +236,9 @@ export function SettingsModal({ setAutoReview(initialAutoReview) setReviewPrompt(initialReviewPrompt) setDevBranch(initialDevBranch) - setAutoBuildBranch(initialAutoBuildBranch) + setReleaseBranchPrefix(initialReleaseBranchPrefix) + setJenkinsJob(initialJenkinsJob) + setJenkinsProdHost(initialJenkinsProdHost) setWorktreeDirectory(initialWorktreeDirectory) setWorktreeOpenWith(initialWorktreeOpenWith) setPycharmCommand(initialPycharmCommand) @@ -313,7 +323,9 @@ export function SettingsModal({ autoReview, reviewPrompt, devBranch: devBranch.trim(), - autoBuildBranch: autoBuildBranch.trim(), + releaseBranchPrefix: releaseBranchPrefix.trim(), + jenkinsJob: jenkinsJob.trim(), + jenkinsProdHost: jenkinsProdHost.trim(), // worktree 目录模板:trim 后留空 = 用默认模板(getSettings 读时回退) worktreeDirectory: worktreeDirectory.trim(), worktreeOpenWith, @@ -455,7 +467,7 @@ export function SettingsModal({ 日常推送的分支,留空默认 {' '} main - 。同步按钮把这条分支的最新提交快进到下面的构建分支。 + 。发布按钮从这条分支切发布分支、统计未发布提交。 )} > @@ -469,18 +481,43 @@ export function SettingsModal({ + setReleaseBranchPrefix(e.target.value)} + placeholder="release/" + className={inputClass} + /> + + + - gitea webhook → Jenkins 监听的分支。留空表示与开发分支相同,同步按钮会一直禁用。 + 留空则不显示「部署到正式服」;触发时执行 + {' '} + opencli jks build <任务名> -p REF=… -p REMOTE_HOST=… )} > setAutoBuildBranch(e.target.value)} - placeholder="留空 = 与开发分支相同" + value={jenkinsJob} + onChange={e => setJenkinsJob(e.target.value)} + placeholder="留空 = 不提供部署" + className={inputClass} + /> + + + + setJenkinsProdHost(e.target.value)} + placeholder="正式服" className={inputClass} /> diff --git a/vscode/webview-ui/src/hooks/useIssues.ts b/vscode/webview-ui/src/hooks/useIssues.ts index 3d33562..762710c 100644 --- a/vscode/webview-ui/src/hooks/useIssues.ts +++ b/vscode/webview-ui/src/hooks/useIssues.ts @@ -44,7 +44,9 @@ export interface SettingsValues { autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -77,7 +79,9 @@ export interface SettingsOverlayState { autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -369,7 +373,9 @@ export function useIssues(): UseIssuesResult { autoReview: values.autoReview, reviewPrompt: values.reviewPrompt, devBranch: values.devBranch, - autoBuildBranch: values.autoBuildBranch, + releaseBranchPrefix: values.releaseBranchPrefix, + jenkinsJob: values.jenkinsJob, + jenkinsProdHost: values.jenkinsProdHost, worktreeDirectory: values.worktreeDirectory, worktreeOpenWith: values.worktreeOpenWith, pycharmCommand: values.pycharmCommand, @@ -756,7 +762,9 @@ export function useIssues(): UseIssuesResult { autoReview: msg.autoReview, reviewPrompt: msg.reviewPrompt, devBranch: msg.devBranch, - autoBuildBranch: msg.autoBuildBranch, + releaseBranchPrefix: msg.releaseBranchPrefix, + jenkinsJob: msg.jenkinsJob, + jenkinsProdHost: msg.jenkinsProdHost, worktreeDirectory: msg.worktreeDirectory, worktreeOpenWith: msg.worktreeOpenWith, pycharmCommand: msg.pycharmCommand, diff --git a/vscode/webview-ui/src/lib/messages.ts b/vscode/webview-ui/src/lib/messages.ts index be094b0..f1d4bc5 100644 --- a/vscode/webview-ui/src/lib/messages.ts +++ b/vscode/webview-ui/src/lib/messages.ts @@ -88,7 +88,9 @@ export type ExtensionToWebview autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -124,13 +126,15 @@ export type ExtensionToWebview | { type: 'logs/cleared' } | { type: 'commit/state', running: boolean } | { type: 'commit/has-changes', value: boolean } + // 发布点状态:nextBranch 是今天要切的分支全名,由扩展侧算好,webview 不自己拼日期。 | { - type: 'branch-sync/status' - behind: number + type: 'release/status' devBranch: string - autoBuildBranch: string - unavailable?: boolean - reason?: string + current?: string + nextBranch: string + unreleased: number + unavailable?: string + running: boolean } | { type: 'env-lock/status', locked: boolean, fileCount: number, failedCount?: number } | { type: 'issue/remove', issueNumber: number } @@ -160,7 +164,9 @@ export type WebviewToExtension autoReview: boolean reviewPrompt: string devBranch: string - autoBuildBranch: string + releaseBranchPrefix: string + jenkinsJob: string + jenkinsProdHost: string worktreeDirectory: string worktreeOpenWith: 'vscode' | 'pycharm' pycharmCommand: string @@ -215,8 +221,10 @@ export type WebviewToExtension | { type: 'logs/clear' } | { type: 'commit/run' } | { type: 'session/close-tab', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' } - | { type: 'branch-sync/check' } - | { type: 'branch-sync/run' } + | { type: 'release/check' } + | { type: 'release/cut' } + | { type: 'release/cherry-pick' } + | { type: 'release/deploy' } | { type: 'env-lock/check' } | { type: 'env-lock/toggle' } | { type: 'issue/delete', issueNumber: number }