infra-kit 0.1.85 → 0.1.88

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 (37) hide show
  1. package/.eslintcache +1 -1
  2. package/.turbo/turbo-eslint-check.log +1 -1
  3. package/.turbo/turbo-prettier-check.log +2 -2
  4. package/.turbo/turbo-prettier-fix.log +10 -0
  5. package/.turbo/turbo-test.log +3 -3
  6. package/.turbo/turbo-ts-check.log +1 -1
  7. package/dist/cli.js +32 -32
  8. package/dist/cli.js.map +4 -4
  9. package/dist/mcp.js +22 -22
  10. package/dist/mcp.js.map +4 -4
  11. package/package.json +3 -3
  12. package/src/commands/doctor/doctor.ts +15 -2
  13. package/src/commands/env-clear/env-clear.ts +2 -1
  14. package/src/commands/env-list/env-list.ts +6 -4
  15. package/src/commands/env-load/env-load.ts +9 -4
  16. package/src/commands/env-status/env-status.ts +2 -1
  17. package/src/commands/gh-merge-dev/gh-merge-dev.ts +8 -2
  18. package/src/commands/gh-release-deliver/gh-release-deliver.ts +3 -2
  19. package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +18 -7
  20. package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +23 -8
  21. package/src/commands/gh-release-list/gh-release-list.ts +2 -1
  22. package/src/commands/init/init.ts +45 -3
  23. package/src/commands/release-create/release-create.ts +3 -2
  24. package/src/commands/release-create-batch/release-create-batch.ts +5 -2
  25. package/src/commands/worktrees-add/worktrees-add.ts +26 -5
  26. package/src/commands/worktrees-list/worktrees-list.ts +2 -1
  27. package/src/commands/worktrees-remove/worktrees-remove.ts +14 -3
  28. package/src/commands/worktrees-sync/worktrees-sync.ts +2 -1
  29. package/src/entry/cli.ts +0 -18
  30. package/src/integrations/doppler/doppler-project.ts +4 -17
  31. package/src/lib/constants.ts +0 -10
  32. package/src/lib/infra-kit-config.ts +49 -0
  33. package/src/mcp/tools/index.ts +0 -2
  34. package/tsconfig.tsbuildinfo +1 -1
  35. package/.turbo/turbo-build.log +0 -11
  36. package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +0 -193
  37. package/src/commands/gh-release-deploy-service/index.ts +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.85",
4
+ "version": "0.1.88",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -17,8 +17,8 @@
17
17
  "build": "pnpm run clean-artifacts && node ./scripts/build.js",
18
18
  "clean-artifacts": "rm -rf dist",
19
19
  "clean-cache": "rm -rf node_modules/.cache .eslintcache tsconfig.tsbuildinfo .turbo .swc",
20
- "prettier-fix": "pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../.prettierignore",
21
- "prettier-check": "pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../.prettierignore",
20
+ "prettier-fix": "pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../../.prettierignore",
21
+ "prettier-check": "pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../../.prettierignore",
22
22
  "eslint-check": "pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src",
23
23
  "eslint-fix": "pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src --fix",
24
24
  "ts-check": "tsc --noEmit",
@@ -26,7 +26,7 @@ const checkCommand = async (
26
26
  }
27
27
 
28
28
  /**
29
- * Check installation and authentication status of gh and doppler CLIs
29
+ * Check installation and authentication status of gh, doppler, and aws CLIs
30
30
  */
31
31
  export const doctor = async (): Promise<ToolsExecutionResult> => {
32
32
  const checks: CheckResult[] = await Promise.all([
@@ -54,6 +54,19 @@ export const doctor = async (): Promise<ToolsExecutionResult> => {
54
54
  'Doppler CLI is authenticated',
55
55
  'Doppler CLI is not authenticated. Run: doppler login',
56
56
  ),
57
+ checkCommand(
58
+ 'aws installed',
59
+ ['aws', '--version'],
60
+ 'AWS CLI is installed',
61
+ 'AWS CLI is not installed. Install from: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html',
62
+ ),
63
+ // INFO: no need now, util the user does not load the env variables, the aws cli is not authenticated
64
+ // checkCommand(
65
+ // 'aws authenticated',
66
+ // ['aws', 'sts', 'get-caller-identity'],
67
+ // 'AWS CLI is authenticated',
68
+ // 'AWS CLI is not authenticated. Run: aws configure (or aws sso login)',
69
+ // ),
57
70
  ])
58
71
 
59
72
  logger.info('Doctor check results:\n')
@@ -87,7 +100,7 @@ export const doctor = async (): Promise<ToolsExecutionResult> => {
87
100
  // MCP Tool Registration
88
101
  export const doctorMcpTool = {
89
102
  name: 'doctor',
90
- description: 'Check installation and authentication status of gh and doppler CLIs',
103
+ description: 'Check installation and authentication status of gh, doppler, and aws CLIs',
91
104
  inputSchema: {},
92
105
  outputSchema: {
93
106
  checks: z
@@ -60,6 +60,7 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
60
60
  fs.unlinkSync(envLoadPath)
61
61
 
62
62
  const structuredContent = {
63
+ filePath: clearFilePath,
63
64
  variableCount: varNames.length,
64
65
  unsetStatements: unsetLines,
65
66
  }
@@ -79,7 +80,7 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
79
80
  export const envClearMcpTool = {
80
81
  name: 'env-clear',
81
82
  description:
82
- 'Clear loaded env vars. Returns a file path that must be sourced (source <path>) to apply. The env-clear shell alias does this automatically.',
83
+ 'Generate a shell script that unsets every env var previously loaded by env-load for this session, plus the infra-kit session metadata vars. Does NOT mutate the calling process. When `infra-kit init` has installed the zsh shell integration, the user\'s terminal auto-sources the unset script on its next prompt (precmd hook) — so calling this via MCP will clear the vars in the shell that launched Claude Code automatically. Other callers must source "<filePath>" themselves or surface it to the user. Errors if no env is currently loaded.',
83
84
  inputSchema: {},
84
85
  outputSchema: {
85
86
  filePath: z.string().describe('Path to the file that must be sourced to apply'),
@@ -2,7 +2,7 @@ import { z } from 'zod'
2
2
 
3
3
  import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
4
4
  import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
5
- import { ENVs } from 'src/lib/constants'
5
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
6
6
  import { logger } from 'src/lib/logger'
7
7
  import type { ToolsExecutionResult } from 'src/types'
8
8
 
@@ -13,17 +13,18 @@ export const envList = async (): Promise<ToolsExecutionResult> => {
13
13
  await validateDopplerCliAndAuth()
14
14
 
15
15
  const project = await getDopplerProject()
16
+ const { environments } = await getInfraKitConfig()
16
17
 
17
18
  logger.info(`Doppler project: ${project}\n`)
18
19
  logger.info('Available configs:')
19
20
 
20
- for (const env of ENVs) {
21
+ for (const env of environments) {
21
22
  logger.info(` - ${env}`)
22
23
  }
23
24
 
24
25
  const structuredContent = {
25
26
  project,
26
- configs: ENVs,
27
+ configs: environments,
27
28
  }
28
29
 
29
30
  return {
@@ -40,7 +41,8 @@ export const envList = async (): Promise<ToolsExecutionResult> => {
40
41
  // MCP Tool Registration
41
42
  export const envListMcpTool = {
42
43
  name: 'env-list',
43
- description: 'List available Doppler configs for the detected project',
44
+ description:
45
+ 'List the environments the project is configured to support. Returns a static list defined in infra-kit constants (not a live fetch from Doppler) plus the Doppler project name auto-detected from the current directory. Read-only.',
44
46
  inputSchema: {},
45
47
  outputSchema: {
46
48
  project: z.string().describe('Detected Doppler project name'),
@@ -10,12 +10,12 @@ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
10
10
  import { commandEcho } from 'src/lib/command-echo'
11
11
  import {
12
12
  ENV_LOAD_FILE,
13
- ENVs,
14
13
  INFRA_KIT_ENV_CONFIG_VAR,
15
14
  INFRA_KIT_ENV_LOADED_AT_VAR,
16
15
  INFRA_KIT_ENV_PROJECT_VAR,
17
16
  getSessionCacheDir,
18
17
  } from 'src/lib/constants'
18
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
19
19
  import type { ToolsExecutionResult } from 'src/types'
20
20
 
21
21
  interface EnvLoadArgs {
@@ -37,11 +37,13 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
37
37
  if (config) {
38
38
  selectedConfig = config
39
39
  } else {
40
+ const { environments } = await getInfraKitConfig()
41
+
40
42
  commandEcho.setInteractive()
41
43
  selectedConfig = await select(
42
44
  {
43
45
  message: 'Select environment config',
44
- choices: ENVs.map((env) => {
46
+ choices: environments.map((env) => {
45
47
  return { name: env, value: env }
46
48
  }),
47
49
  },
@@ -88,6 +90,7 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
88
90
  }).length
89
91
 
90
92
  const structuredContent = {
93
+ filePath: envFilePath,
91
94
  variableCount: varCount,
92
95
  project,
93
96
  config: selectedConfig,
@@ -108,9 +111,11 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
108
111
  export const envLoadMcpTool = {
109
112
  name: 'env-load',
110
113
  description:
111
- 'Load Doppler env vars for a config. Returns a file path that must be sourced (source <path>) to apply variables. The env-load shell alias does this automatically.',
114
+ 'Download the env vars for a Doppler config and write them to a temporary shell script. Does NOT mutate the calling process — returns the path to a script that must be sourced ("source <filePath>") for the vars to take effect. The infra-kit shell wrapper auto-sources; direct MCP callers must handle sourcing themselves or surface filePath to the user. "config" is required when invoked via MCP (the CLI interactive picker is unreachable without a TTY).',
112
115
  inputSchema: {
113
- config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
116
+ config: z
117
+ .string()
118
+ .describe('Doppler config / environment name to load (e.g. "dev", "arthur", "renana"). Required for MCP calls.'),
114
119
  },
115
120
  outputSchema: {
116
121
  filePath: z.string().describe('Path to the file that must be sourced to apply variables'),
@@ -78,7 +78,8 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
78
78
  // MCP Tool Registration
79
79
  export const envStatusMcpTool = {
80
80
  name: 'env-status',
81
- description: 'Show Doppler authentication status and detected project info',
81
+ description:
82
+ 'Report which Doppler project/config is currently loaded in the terminal session, when it was loaded, and how many variables are cached. Read-only — use env-load / env-clear to change the terminal session.',
82
83
  inputSchema: {},
83
84
  outputSchema: {
84
85
  sessionId: z.string().describe('Current terminal session ID'),
@@ -186,9 +186,15 @@ const mergeDev = async (branch: string): Promise<boolean> => {
186
186
  // MCP Tool Registration
187
187
  export const ghMergeDevMcpTool = {
188
188
  name: 'gh-merge-dev',
189
- description: 'Merge dev branch into selected release branches',
189
+ description:
190
+ 'Merge origin/dev into every open regular (non-hotfix) release branch and push the result. Mutates local git state and the remote release branches. When invoked via MCP, pass all=true — the branch picker is unreachable without a TTY, and the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. Irreversible once pushed.',
190
191
  inputSchema: {
191
- all: z.boolean().describe('Merge dev into all release branches without prompting'),
192
+ all: z
193
+ .boolean()
194
+ .optional()
195
+ .describe(
196
+ 'Target every open regular release branch. Must be true for MCP calls (the interactive picker is unavailable without a TTY).',
197
+ ),
192
198
  },
193
199
  outputSchema: {
194
200
  successfulMerges: z.number().describe('Number of successful merges'),
@@ -162,9 +162,10 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
162
162
  // MCP Tool Registration
163
163
  export const ghReleaseDeliverMcpTool = {
164
164
  name: 'gh-release-deliver',
165
- description: 'Deliver a release branch to production',
165
+ description:
166
+ 'Deliver a release to production. For hotfixes: squash-merges the release branch to main and dispatches the deploy-all workflow. For regular releases: squash-merges to dev, opens an RC PR, merges dev into main, dispatches the deploy-all workflow, then syncs main back to dev. Also releases the matching Jira fix version if Jira is configured. Dispatches the deploy workflow fire-and-forget — the tool returns once the workflow is accepted by GitHub, not when the deployment finishes. Irreversible production operation: the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the picker is unreachable without a TTY).',
166
167
  inputSchema: {
167
- version: z.string().describe('Version to deliver to production (e.g., "1.2.5")'),
168
+ version: z.string().describe('Release version to deliver to production (e.g., "1.2.5"). Required for MCP calls.'),
168
169
  },
169
170
  outputSchema: {
170
171
  releaseBranch: z.string().describe('The release branch that was delivered'),
@@ -5,7 +5,7 @@ import { $ } from 'zx'
5
5
 
6
6
  import { getReleasePRsWithInfo } from 'src/integrations/gh'
7
7
  import { commandEcho } from 'src/lib/command-echo'
8
- import { ENVs } from 'src/lib/constants'
8
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
9
9
  import { logger } from 'src/lib/logger'
10
10
  import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
11
11
  import type { ReleaseType } from 'src/lib/release-utils'
@@ -56,6 +56,8 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
56
56
 
57
57
  commandEcho.addOption('--version', selectedVersion)
58
58
 
59
+ const { environments } = await getInfraKitConfig()
60
+
59
61
  let selectedEnv = ''
60
62
 
61
63
  if (env) {
@@ -65,7 +67,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
65
67
 
66
68
  selectedEnv = await select({
67
69
  message: '🧪 Select environment',
68
- choices: ENVs.map((env) => {
70
+ choices: environments.map((env) => {
69
71
  return {
70
72
  name: env,
71
73
  value: env,
@@ -76,7 +78,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
76
78
 
77
79
  commandEcho.addOption('--env', selectedEnv)
78
80
 
79
- if (!ENVs.includes(selectedEnv)) {
81
+ if (!environments.includes(selectedEnv)) {
80
82
  logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
81
83
  process.exit(1)
82
84
  }
@@ -128,11 +130,20 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
128
130
  // MCP Tool Registration
129
131
  export const ghReleaseDeployAllMcpTool = {
130
132
  name: 'gh-release-deploy-all',
131
- description: 'Deploy a release branch to a specified environment',
133
+ description:
134
+ 'Dispatch the deploy-all.yml GitHub Actions workflow to deploy every service from a release branch to the given environment. Fire-and-forget — returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Use gh-release-deploy-selected for a subset of services. Pass version="dev" to deploy from the dev branch instead of a release branch. Both "version" and "env" are required when invoked via MCP (interactive pickers are unavailable without a TTY).',
132
135
  inputSchema: {
133
- version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
134
- env: z.string().describe('Environment to deploy to (e.g., "dev", "renana", "oriana")'),
135
- skipTerraform: z.boolean().optional().describe('Skip terraform deployment step'),
136
+ version: z
137
+ .string()
138
+ .describe(
139
+ 'Release version to deploy from (e.g. "1.2.5") — resolves to the release/vX.Y.Z branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.',
140
+ ),
141
+ env: z
142
+ .string()
143
+ .describe(
144
+ 'Target environment name — must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.',
145
+ ),
146
+ skipTerraform: z.boolean().optional().describe('Skip the terraform deployment stage.'),
136
147
  },
137
148
  outputSchema: {
138
149
  releaseBranch: z.string().describe('The release branch that was deployed'),
@@ -9,8 +9,8 @@ import { $ } from 'zx'
9
9
 
10
10
  import { getReleasePRsWithInfo } from 'src/integrations/gh'
11
11
  import { commandEcho } from 'src/lib/command-echo'
12
- import { ENVs } from 'src/lib/constants'
13
12
  import { getProjectRoot } from 'src/lib/git-utils'
13
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
14
14
  import { logger } from 'src/lib/logger'
15
15
  import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
16
16
  import type { ReleaseType } from 'src/lib/release-utils'
@@ -63,6 +63,8 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
63
63
 
64
64
  commandEcho.addOption('--version', selectedVersion)
65
65
 
66
+ const { environments } = await getInfraKitConfig()
67
+
66
68
  let selectedEnv = ''
67
69
 
68
70
  if (env) {
@@ -72,7 +74,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
72
74
 
73
75
  selectedEnv = await select({
74
76
  message: '🧪 Select environment',
75
- choices: ENVs.map((env) => {
77
+ choices: environments.map((env) => {
76
78
  return {
77
79
  name: env,
78
80
  value: env,
@@ -83,7 +85,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
83
85
 
84
86
  commandEcho.addOption('--env', selectedEnv)
85
87
 
86
- if (!ENVs.includes(selectedEnv)) {
88
+ if (!environments.includes(selectedEnv)) {
87
89
  logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
88
90
  process.exit(1)
89
91
  }
@@ -211,12 +213,25 @@ const parseServicesFromWorkflow = async (): Promise<string[]> => {
211
213
  // MCP Tool Registration
212
214
  export const ghReleaseDeploySelectedMcpTool = {
213
215
  name: 'gh-release-deploy-selected',
214
- description: 'Deploy selected services from a release branch to a specified environment',
216
+ description:
217
+ 'Dispatch the deploy-selected-services.yml GitHub Actions workflow to deploy a chosen subset of services from a release branch to the given environment. Fire-and-forget — returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Service names are validated against the boolean inputs declared in the workflow. Use gh-release-deploy-all for every service. "version", "env", and "services" are all required when invoked via MCP (interactive pickers are unavailable without a TTY).',
215
218
  inputSchema: {
216
- version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
217
- env: z.string().describe('Environment to deploy to (e.g., "dev", "renana", "oriana")'),
218
- services: z.array(z.string()).describe('List of services to deploy (e.g., ["client-be", "client-fe"])'),
219
- skipTerraform: z.boolean().optional().describe('Skip terraform deployment step'),
219
+ version: z
220
+ .string()
221
+ .describe(
222
+ 'Release version to deploy from (e.g. "1.2.5") — resolves to the release/vX.Y.Z branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.',
223
+ ),
224
+ env: z
225
+ .string()
226
+ .describe(
227
+ 'Target environment name — must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.',
228
+ ),
229
+ services: z
230
+ .array(z.string())
231
+ .describe(
232
+ 'Service names to deploy. Each must match a boolean input declared in .github/workflows/deploy-selected-services.yml (e.g. "client-be", "client-fe"). Required for MCP calls.',
233
+ ),
234
+ skipTerraform: z.boolean().optional().describe('Skip the terraform deployment stage.'),
220
235
  },
221
236
  outputSchema: {
222
237
  releaseBranch: z.string().describe('The release branch that was deployed'),
@@ -65,7 +65,8 @@ export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
65
65
  // MCP Tool Registration
66
66
  export const ghReleaseListMcpTool = {
67
67
  name: 'gh-release-list',
68
- description: 'List all open release branches',
68
+ description:
69
+ 'List every open release PR with its version, type (regular / hotfix), and associated Jira fix-version description. Read-only; sourced from GitHub and Jira.',
69
70
  inputSchema: {},
70
71
  outputSchema: {
71
72
  releases: z
@@ -38,7 +38,14 @@ const isBlockLine = (line: string): boolean => {
38
38
  line.startsWith('env-status') ||
39
39
  line.startsWith('if ') ||
40
40
  line.startsWith(' export INFRA_KIT_SESSION') ||
41
- line.startsWith('fi')
41
+ line.startsWith('export _INFRA_KIT_LAST_') ||
42
+ line.startsWith('export _INFRA_KIT_') ||
43
+ line.startsWith(': ${_INFRA_KIT_') ||
44
+ line.startsWith('fi') ||
45
+ line.startsWith('zmodload ') ||
46
+ line.startsWith('autoload ') ||
47
+ line.startsWith('add-zsh-hook ') ||
48
+ line.startsWith('_infra_kit_autoload')
42
49
  )
43
50
  }
44
51
 
@@ -93,14 +100,49 @@ const buildShellBlock = (): string => {
93
100
 
94
101
  return [
95
102
  MARKER_START,
103
+ 'zmodload zsh/stat 2>/dev/null',
104
+ 'zmodload zsh/datetime 2>/dev/null',
96
105
  // eslint-disable-next-line no-template-curly-in-string
97
106
  'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
98
107
  ' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
99
108
  'fi',
100
- `env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f" && ${runCmd} env-status; }`,
101
- `env-clear() { local f; f=$(${runCmd} env-clear) && source "$f" && ${runCmd} env-status; }`,
109
+ // eslint-disable-next-line no-template-curly-in-string
110
+ ': ${_INFRA_KIT_LAST_LOAD_MTIME:=0}',
111
+ // eslint-disable-next-line no-template-curly-in-string
112
+ ': ${_INFRA_KIT_LAST_CLEAR_MTIME:=0}',
113
+ // eslint-disable-next-line no-template-curly-in-string
114
+ ': ${_INFRA_KIT_SHELL_STARTED:=${EPOCHSECONDS:-0}}',
115
+ 'export _INFRA_KIT_LAST_LOAD_MTIME _INFRA_KIT_LAST_CLEAR_MTIME _INFRA_KIT_SHELL_STARTED',
116
+ `env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f" && _INFRA_KIT_LAST_LOAD_MTIME=$(zstat +mtime -- "$f" 2>/dev/null || echo 0) && ${runCmd} env-status; }`,
117
+ `env-clear() { local f; f=$(${runCmd} env-clear) && source "$f" && _INFRA_KIT_LAST_CLEAR_MTIME=$(zstat +mtime -- "$f" 2>/dev/null || echo 0) && ${runCmd} env-status; }`,
102
118
  `env-status() { ${runCmd} env-status; }`,
103
119
  `alias ik='${runCmd}'`,
120
+ '_infra_kit_autoload() {',
121
+ ' [[ -z "$INFRA_KIT_SESSION" ]] && return',
122
+ ' local dir="./node_modules/.cache/infra-kit/$INFRA_KIT_SESSION"',
123
+ ' local load_file="$dir/env-load.sh"',
124
+ ' local clear_file="$dir/env-clear.sh"',
125
+ ' local mtime',
126
+ ' if [[ -f "$load_file" ]]; then',
127
+ ' mtime=$(zstat +mtime -- "$load_file" 2>/dev/null || echo 0)',
128
+ ' if (( mtime > _INFRA_KIT_LAST_LOAD_MTIME && mtime >= _INFRA_KIT_SHELL_STARTED )); then',
129
+ ' source "$load_file"',
130
+ ' _INFRA_KIT_LAST_LOAD_MTIME=$mtime',
131
+ // eslint-disable-next-line no-template-curly-in-string
132
+ ' print -u2 "infra-kit: auto-loaded vars for ${INFRA_KIT_ENV_CONFIG:-?}"',
133
+ ' fi',
134
+ ' fi',
135
+ ' if [[ -f "$clear_file" ]]; then',
136
+ ' mtime=$(zstat +mtime -- "$clear_file" 2>/dev/null || echo 0)',
137
+ ' if (( mtime > _INFRA_KIT_LAST_CLEAR_MTIME && mtime >= _INFRA_KIT_SHELL_STARTED )); then',
138
+ ' source "$clear_file"',
139
+ ' _INFRA_KIT_LAST_CLEAR_MTIME=$mtime',
140
+ ' print -u2 "infra-kit: auto-cleared env"',
141
+ ' fi',
142
+ ' fi',
143
+ '}',
144
+ 'autoload -Uz add-zsh-hook',
145
+ 'add-zsh-hook precmd _infra_kit_autoload',
104
146
  MARKER_END,
105
147
  ].join('\n')
106
148
  }
@@ -160,9 +160,10 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
160
160
  // MCP Tool Registration
161
161
  export const releaseCreateMcpTool = {
162
162
  name: 'release-create',
163
- description: 'Create a single release branch for specified version with Jira version creation',
163
+ description:
164
+ 'Create a new release: cuts the release branch off the appropriate base (dev for regular releases, main for hotfixes), opens a GitHub release PR, creates the matching Jira fix version, and optionally checks out to the new 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); "type" / "description" / "checkout" default to regular / empty / true when omitted.',
164
165
  inputSchema: {
165
- version: z.string().describe('Version to create (e.g., "1.2.5")'),
166
+ version: z.string().describe('Version to create (e.g., "1.2.5"). Required for MCP calls.'),
166
167
  description: z.string().optional().describe('Optional description for the Jira version'),
167
168
  type: z
168
169
  .enum(['regular', 'hotfix'])
@@ -158,9 +158,12 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
158
158
  // MCP Tool Registration
159
159
  export const releaseCreateBatchMcpTool = {
160
160
  name: 'release-create-batch',
161
- description: 'Create multiple release branches for specified versions with Jira version creation (batch operation)',
161
+ description:
162
+ 'Create several releases in one pass: for each comma-separated version in "versions", cuts the release branch off the appropriate base (dev for regular releases, main for hotfixes), opens a GitHub PR, and creates the Jira fix version. Continues on per-version failure and reports which versions succeeded and which failed. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. "versions" is required when invoked via MCP (the interactive input prompt is unreachable without a TTY). Use release-create for a single version with optional checkout.',
162
163
  inputSchema: {
163
- versions: z.string().describe('Comma-separated list of versions to create (e.g., "1.2.5, 1.2.6")'),
164
+ versions: z
165
+ .string()
166
+ .describe('Comma-separated list of versions to create (e.g., "1.2.5, 1.2.6"). Required for MCP calls.'),
164
167
  type: z
165
168
  .enum(['regular', 'hotfix'])
166
169
  .optional()
@@ -268,12 +268,33 @@ const logResults = (created: string[]): void => {
268
268
  // MCP Tool Registration
269
269
  export const worktreesAddMcpTool = {
270
270
  name: 'worktrees-add',
271
- description: 'Create git worktrees for selected release branches',
271
+ description:
272
+ 'Create local git worktrees for release branches under the worktrees directory and run "pnpm install" in each. Mutates the local filesystem. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker and "open in Cursor / GitHub Desktop" follow-up prompts are unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls.',
272
273
  inputSchema: {
273
- all: z.boolean().describe('Add git worktrees for all release branches without prompting'),
274
- versions: z.string().optional().describe('Specify versions by comma, e.g. 1.2.5, 1.2.6'),
275
- cursor: z.boolean().optional().describe('Open created git worktrees in Cursor'),
276
- githubDesktop: z.boolean().optional().describe('Open created git worktrees in GitHub Desktop'),
274
+ all: z
275
+ .boolean()
276
+ .optional()
277
+ .describe(
278
+ 'Add worktrees for every open release branch. Either "all" or "versions" must be provided for MCP calls (the interactive picker is unavailable without a TTY). Ignored if "versions" is provided.',
279
+ ),
280
+ versions: z
281
+ .string()
282
+ .optional()
283
+ .describe(
284
+ 'Comma-separated release versions to target (e.g. "1.2.5, 1.2.6"). Either "versions" or all=true must be provided for MCP calls. Overrides "all" when set.',
285
+ ),
286
+ cursor: z
287
+ .boolean()
288
+ .optional()
289
+ .describe(
290
+ 'Open each created worktree in Cursor. Defaults to false in MCP mode (the follow-up prompt is not shown).',
291
+ ),
292
+ githubDesktop: z
293
+ .boolean()
294
+ .optional()
295
+ .describe(
296
+ 'Open each created worktree in GitHub Desktop. Defaults to false in MCP mode (the follow-up prompt is not shown).',
297
+ ),
277
298
  },
278
299
  outputSchema: {
279
300
  createdWorktrees: z.array(z.string()).describe('List of created git worktree branches'),
@@ -83,7 +83,8 @@ export const worktreesList = async (): Promise<ToolsExecutionResult> => {
83
83
  // MCP Tool Registration
84
84
  export const worktreesListMcpTool = {
85
85
  name: 'worktrees-list',
86
- description: 'List all git worktrees with detailed information',
86
+ description:
87
+ 'List existing release-branch worktrees with version, release type (regular / hotfix), and Jira fix-version description. Read-only.',
87
88
  inputSchema: {},
88
89
  outputSchema: {
89
90
  worktrees: z
@@ -188,10 +188,21 @@ const logResults = (removed: string[]): void => {
188
188
  // MCP Tool Registration
189
189
  export const worktreesRemoveMcpTool = {
190
190
  name: 'worktrees-remove',
191
- description: 'Remove selected worktrees',
191
+ description:
192
+ 'Remove local git worktrees for release branches. When everything is removed, also runs "git worktree prune" and deletes the worktrees directory. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker is unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
192
193
  inputSchema: {
193
- all: z.boolean().describe('Remove all git worktrees without prompting'),
194
- versions: z.string().optional().describe('Specify versions by comma, e.g. 1.2.5, 1.2.6'),
194
+ all: z
195
+ .boolean()
196
+ .optional()
197
+ .describe(
198
+ 'Remove every existing worktree. Either "all" or "versions" must be provided for MCP calls (the interactive picker is unavailable without a TTY). Ignored if "versions" is provided.',
199
+ ),
200
+ versions: z
201
+ .string()
202
+ .optional()
203
+ .describe(
204
+ 'Comma-separated release versions to target (e.g. "1.2.5, 1.2.6"). Either "versions" or all=true must be provided for MCP calls. Overrides "all" when set.',
205
+ ),
195
206
  },
196
207
  outputSchema: {
197
208
  removedWorktrees: z.array(z.string()).describe('List of removed git worktree branches'),
@@ -145,7 +145,8 @@ const logResults = (removed: string[]): void => {
145
145
  // MCP Tool Registration
146
146
  export const worktreesSyncMcpTool = {
147
147
  name: 'worktrees-sync',
148
- description: 'Synchronize worktrees with active release branches',
148
+ description:
149
+ 'Remove worktrees whose release PR is no longer open (stale cleanup). Only removes — never creates; use worktrees-add to create worktrees for new releases. The CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
149
150
  inputSchema: {},
150
151
  outputSchema: {
151
152
  removedWorktrees: z.array(z.string()).describe('List of removed worktree branches'),
package/src/entry/cli.ts CHANGED
@@ -11,7 +11,6 @@ import { ghMergeDev } from 'src/commands/gh-merge-dev'
11
11
  import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
12
12
  import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'
13
13
  import { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected'
14
- import { ghReleaseDeployService } from 'src/commands/gh-release-deploy-service'
15
14
  import { ghReleaseList } from 'src/commands/gh-release-list'
16
15
  import { init } from 'src/commands/init'
17
16
  import { releaseCreate } from 'src/commands/release-create'
@@ -81,22 +80,6 @@ program
81
80
  await ghReleaseDeployAll({ version: options.version, env: options.env, skipTerraform: options.skipTerraform })
82
81
  })
83
82
 
84
- program
85
- .command('release-deploy-service')
86
- .description('Deploy specific service in release branch to any environment')
87
- .option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
88
- .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
89
- .option('-s, --service <service>', 'Specify the service to deploy, e.g. client-be')
90
- .option('--skip-terraform', 'Skip terraform deployment step')
91
- .action(async (options) => {
92
- await ghReleaseDeployService({
93
- version: options.version,
94
- env: options.env,
95
- service: options.service,
96
- skipTerraform: options.skipTerraform,
97
- })
98
- })
99
-
100
83
  program
101
84
  .command('release-deploy-selected')
102
85
  .description('Deploy selected services from release branch to any environment')
@@ -217,7 +200,6 @@ if (process.argv.length <= 2) {
217
200
  'release-create',
218
201
  'release-create-batch',
219
202
  'release-deploy-all',
220
- 'release-deploy-service',
221
203
  'release-deploy-selected',
222
204
  'release-deliver',
223
205
  ]
@@ -1,23 +1,10 @@
1
- import path from 'node:path'
2
-
3
- import { DOPPLER_PROJECT_MAP } from 'src/lib/constants'
4
- import { getProjectRoot } from 'src/lib/git-utils'
1
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
5
2
 
6
3
  /**
7
- * Resolve Doppler project name from the current working directory
4
+ * Resolve Doppler project name from infra-kit.yml at the project root
8
5
  */
9
6
  export const getDopplerProject = async (): Promise<string> => {
10
- const projectRoot = await getProjectRoot()
11
-
12
- const dirName = path.basename(projectRoot)
13
- const dopplerProject = DOPPLER_PROJECT_MAP[dirName]
14
-
15
- if (!dopplerProject) {
16
- throw new Error(
17
- `Could not determine Doppler project for directory "${dirName}". ` +
18
- `Expected one of: ${Object.keys(DOPPLER_PROJECT_MAP).join(', ')}`,
19
- )
20
- }
7
+ const { dopplerProjectName } = await getInfraKitConfig()
21
8
 
22
- return dopplerProject
9
+ return dopplerProjectName
23
10
  }
@@ -2,16 +2,6 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import process from 'node:process'
4
4
 
5
- /**
6
- * List of environments for the project deployment
7
- */
8
- export const ENVs = ['dev', 'arthur', 'renana', 'roman', 'eliran', 'oriana']
9
-
10
- export const DOPPLER_PROJECT_MAP: Record<string, string> = {
11
- 'hulyo-monorepo': 'hulyo',
12
- 'travelist-monorepo': 'travelist',
13
- }
14
-
15
5
  export const ENV_CACHE_DIR = './node_modules/.cache/infra-kit'
16
6
  export const ENV_LOAD_FILE = 'env-load.sh'
17
7
  export const ENV_CLEAR_FILE = 'env-clear.sh'