worktree-bay 2.5.0 → 4.0.0

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/README.md CHANGED
@@ -47,7 +47,7 @@ worktree-bay gc
47
47
 
48
48
  > 运行体随起随停(不动 worktree/代码):`worktree-bay stop drill-fix` 停掉(docker 容器 + dev server 一起)、`start` 起回来、`restart` 重启。
49
49
  >
50
- > 更细的控制:`claim <feature>` 单独占槽、`add <feature> <service> [branch]` 单加一个服务(branch 可自定义,省略则用功能名)、`rm <feature> [service]` 拆单个服务。
50
+ > 更细的控制:`claim <feature>` 单独占槽、`add <feature> <service> [branch]` 单加一个服务(branch 可自定义,省略则用功能名)、`down <feature> <service>` 只拆某个服务(省略服务则拆整功能)。
51
51
 
52
52
  ## 配置
53
53
 
package/dist/cli.js CHANGED
@@ -73,8 +73,10 @@ catch (e) {
73
73
  die(e.message);
74
74
  } });
75
75
  program.command('add <feature> <service> [branch] [base]').description(t('为功能在某服务开 worktree(branch 默认 = 功能名)', 'open a worktree for a feature on one service (branch defaults to feature name)'))
76
- .action(async (f, s, b, base) => { try {
77
- await addCommand(loadConfig(process.cwd()), f, s, b, base);
76
+ .option('--branch <branch>', t('要创建的分支名(默认 = 功能名)', 'branch to create (default = feature name)'))
77
+ .option('--base <base>', t('分支基点(默认 = origin/<主分支>)', 'base ref for the branch (default = origin/<main>)'))
78
+ .action(async (f, s, b, base, o) => { try {
79
+ await addCommand(loadConfig(process.cwd()), f, s, o.branch ?? b, o.base ?? base);
78
80
  }
79
81
  catch (e) {
80
82
  die(e.message);
@@ -83,37 +85,30 @@ program.command('run <feature> <service> <name> [args...]').description(t('在
83
85
  .action((f, s, n, args) => sync((c) => runCommand(c, f, s, n, args ?? [])));
84
86
  program.command('sh <feature> <service>').description(t('进入服务运行体的 shell', 'open a shell inside the service runtime'))
85
87
  .action((f, s) => sync((c) => shCommand(c, f, s)));
86
- program.command('start <feature> [service]').description(t('启动该功能的 dev server(worktree 已在,只起 start 进程,不动 worktree', 'start the feature\'s dev server(s) (worktree already exists; runs the start process only)'))
88
+ program.command('start <feature> [services...]').description(t('启动功能的运行体(docker 容器 + dev server);省略 service = 全部,可列多个。不动 worktree', 'start the feature\'s runtime (docker + dev server); omit services = all, or list several. Leaves the worktree untouched'))
87
89
  .action(async (f, s) => { try {
88
- await startCommand(loadConfig(process.cwd()), f, s);
90
+ await startCommand(loadConfig(process.cwd()), f, s ?? []);
89
91
  }
90
92
  catch (e) {
91
93
  die(e.message);
92
94
  } });
93
- program.command('stop <feature> [service]').description(t('停止该功能的 dev server(保留 worktree', 'stop the feature\'s dev server(s) (keeps the worktree)'))
95
+ program.command('stop <feature> [services...]').description(t('停止功能的运行体(停 docker + 杀 dev server);省略 = 全部,可列多个。保留 worktree', 'stop the feature\'s runtime (stop docker + kill dev server); omit = all, or list several. Keeps the worktree'))
94
96
  .action(async (f, s) => { try {
95
- await stopCommand(loadConfig(process.cwd()), f, s);
97
+ await stopCommand(loadConfig(process.cwd()), f, s ?? []);
96
98
  }
97
99
  catch (e) {
98
100
  die(e.message);
99
101
  } });
100
- program.command('restart <feature> [service]').description(t('重启该功能的 dev server(停掉再起)', 'restart the feature\'s dev server(s) (stop then start)'))
102
+ program.command('restart <feature> [services...]').description(t('重启功能的运行体(停掉再起);省略 = 全部,可列多个', 'restart the feature\'s runtime (stop then start); omit = all, or list several'))
101
103
  .action(async (f, s) => { try {
102
- await restartCommand(loadConfig(process.cwd()), f, s);
104
+ await restartCommand(loadConfig(process.cwd()), f, s ?? []);
103
105
  }
104
106
  catch (e) {
105
107
  die(e.message);
106
108
  } });
107
- program.command('down <feature>').description(t('拆除整个功能的所有服务 worktree(= rm <feature>)', 'tear down all of a feature\'s service worktrees (= rm <feature>)')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
108
- .action(async (f, o) => { try {
109
- await rmCommand(loadConfig(process.cwd()), f, undefined, !!o.force);
110
- }
111
- catch (e) {
112
- die(e.message);
113
- } });
114
- program.command('rm <feature> [service]').description(t('拆除某服务或整槽的 worktree(默认查脏/未推保护)', 'remove one service\'s or the whole slot\'s worktree (dirty/unpushed protected by default)')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
109
+ program.command('down <feature> [services...]').description(t('拆除功能的 worktree(停运行体 + teardown + 删 worktree);省略 services = 整功能,可列多个只拆这些。默认查脏/未推保护', 'tear down a feature\'s worktree(s) (stop runtime + teardown + remove worktree); omit services = whole feature, or list several. Dirty/unpushed protected by default')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
115
110
  .action(async (f, s, o) => { try {
116
- await rmCommand(loadConfig(process.cwd()), f, s, !!o.force);
111
+ await rmCommand(loadConfig(process.cwd()), f, s ?? [], !!o.force);
117
112
  }
118
113
  catch (e) {
119
114
  die(e.message);
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { readLabels } from '../slots.js';
5
5
  import { log } from '../util/log.js';
6
6
  import { t } from '../i18n.js';
7
- const SUBCMDS = ['init', 'doctor', 'claim', 'up', 'add', 'ls', 'path', 'gc', 'down', 'rm', 'run', 'sh', 'start', 'stop', 'restart', 'completion', 'mcp', 'skill', 'version', 'help'];
7
+ const SUBCMDS = ['init', 'doctor', 'claim', 'up', 'add', 'ls', 'path', 'gc', 'down', 'run', 'sh', 'start', 'stop', 'restart', 'completion', 'mcp', 'skill', 'version', 'help'];
8
8
  // words = 命令名 + 光标前已输入完的词(不含当前正在补的词)
9
9
  export function complete(cfg, words) {
10
10
  const prev = words.slice(1);
@@ -14,15 +14,15 @@ export function complete(cfg, words) {
14
14
  return []; // 无配置(不在工作区内):子命令已补全,feature/service 无从读取
15
15
  const sub = prev[0];
16
16
  const pos = prev.length;
17
- const featureSubs = ['up', 'add', 'rm', 'down', 'run', 'sh', 'path', 'start', 'stop', 'restart'];
17
+ const featureSubs = ['up', 'add', 'down', 'run', 'sh', 'path', 'start', 'stop', 'restart'];
18
18
  if (featureSubs.includes(sub) && pos === 1)
19
19
  return Object.values(readLabels(cfg));
20
- if (['add', 'run', 'sh', 'path', 'start', 'stop', 'restart'].includes(sub) && pos === 2)
21
- return Object.keys(cfg.services);
20
+ if (['add', 'run', 'sh', 'path'].includes(sub) && pos === 2)
21
+ return Object.keys(cfg.services); // 单服务
22
22
  if (sub === 'run' && pos === 3)
23
23
  return Object.keys(cfg.services[prev[2]]?.run ?? {}); // run <feature> <service> <name>:补该服务的 run 命令名
24
- if (sub === 'up' && pos >= 2)
25
- return Object.keys(cfg.services); // up 接变长服务列表
24
+ if (['up', 'start', 'stop', 'restart', 'down'].includes(sub) && pos >= 2)
25
+ return Object.keys(cfg.services); // 变长服务列表
26
26
  return [];
27
27
  }
28
28
  export function completionScript(shell) {
@@ -7,12 +7,18 @@ import { log } from '../util/log.js';
7
7
  import { color as c } from '../util/color.js';
8
8
  import { t } from '../i18n.js';
9
9
  // dev server + infra 生命周期:stop/start/restart 同时管 node(managed 进程)与 docker(stop 钩子 + setup 恢复),不动 worktree。
10
- function occupantsOf(cfg, feature, service) {
10
+ // services 为空 = 整功能;否则只这些服务
11
+ function occupantsOf(cfg, feature, services = []) {
11
12
  const slot = slotOfFeature(cfg, feature);
12
13
  if (slot === undefined)
13
14
  throw new Error(t(`功能「${feature}」未占槽。先 \`worktree-bay up ${feature} <服务...>\` 起它。`, `feature "${feature}" hasn't claimed a slot. Run \`worktree-bay up ${feature} <services...>\` first.`));
14
15
  const all = scanOccupancy(cfg).get(slot) ?? [];
15
- return service ? all.filter((o) => o.service === service) : all;
16
+ if (!services.length)
17
+ return all;
18
+ for (const s of services)
19
+ if (!all.some((o) => o.service === s))
20
+ throw new Error(t(`服务「${s}」不在功能「${feature}」里。用 \`worktree-bay ls\` 看已起的服务。`, `service "${s}" is not in feature "${feature}". See \`worktree-bay ls\`.`));
21
+ return all.filter((o) => services.includes(o.service));
16
22
  }
17
23
  function ctxOf(cfg, o) {
18
24
  const base = { cfg, service: o.service, sp: cfg.services[o.service], slot: o.slot, slug: o.slug, dir: o.dir, repo: repoPath(cfg, o.service) };
@@ -20,10 +26,10 @@ function ctxOf(cfg, o) {
20
26
  }
21
27
  // 该服务是否有「可停起的运行体」:managed dev server(start) 或可停的 infra(stop 钩子)
22
28
  const hasRuntime = (cfg, service) => { const sp = cfg.services[service]; return !!(sp.start || sp.stop); };
23
- export async function stopCommand(cfg, feature, service) {
29
+ export async function stopCommand(cfg, feature, services = []) {
24
30
  await withLock(cfg.workspaceRoot, async () => {
25
31
  let any = false;
26
- for (const o of occupantsOf(cfg, feature, service)) {
32
+ for (const o of occupantsOf(cfg, feature, services)) {
27
33
  if (!hasRuntime(cfg, o.service))
28
34
  continue;
29
35
  any = true;
@@ -34,10 +40,10 @@ export async function stopCommand(cfg, feature, service) {
34
40
  log(t('没有可停止的运行体(相关服务未配置 start/stop)', 'nothing to stop (no start/stop configured for those services)'));
35
41
  });
36
42
  }
37
- export async function startCommand(cfg, feature, service) {
43
+ export async function startCommand(cfg, feature, services = []) {
38
44
  await withLock(cfg.workspaceRoot, async () => {
39
45
  let any = false;
40
- for (const o of occupantsOf(cfg, feature, service)) {
46
+ for (const o of occupantsOf(cfg, feature, services)) {
41
47
  if (!hasRuntime(cfg, o.service))
42
48
  continue;
43
49
  any = true;
@@ -48,9 +54,9 @@ export async function startCommand(cfg, feature, service) {
48
54
  log(t('没有可启动的运行体(相关服务未配置 start/stop)', 'nothing to start (no start/stop configured for those services)'));
49
55
  });
50
56
  }
51
- export async function restartCommand(cfg, feature, service) {
57
+ export async function restartCommand(cfg, feature, services = []) {
52
58
  await withLock(cfg.workspaceRoot, async () => {
53
- for (const o of occupantsOf(cfg, feature, service)) {
59
+ for (const o of occupantsOf(cfg, feature, services)) {
54
60
  if (!hasRuntime(cfg, o.service))
55
61
  continue;
56
62
  const ctx = ctxOf(cfg, o);
@@ -9,17 +9,24 @@ import { stopManaged } from '../proc.js';
9
9
  import { log, warn } from '../util/log.js';
10
10
  import { color as c } from '../util/color.js';
11
11
  import { t } from '../i18n.js';
12
- export function resolveRm(cfg, feature, service) {
12
+ // services 为空 = 整功能;否则只这些服务(顺带校验服务名确实在该功能里)
13
+ export function resolveRm(cfg, feature, services = []) {
13
14
  const slot = slotOfFeature(cfg, feature);
14
15
  if (slot === undefined)
15
16
  throw new Error(t(`功能「${feature}」未占槽,无需拆除。用 \`worktree-bay ls\` 看在用的功能。`, `feature "${feature}" has no slot — nothing to tear down. See \`worktree-bay ls\`.`));
16
17
  const all = scanOccupancy(cfg).get(slot) ?? [];
17
- return service ? all.filter((o) => o.service === service) : all;
18
+ if (!services.length)
19
+ return all;
20
+ for (const s of services)
21
+ if (!all.some((o) => o.service === s))
22
+ throw new Error(t(`服务「${s}」不在功能「${feature}」里。用 \`worktree-bay ls\` 看已起的服务。`, `service "${s}" is not in feature "${feature}". See \`worktree-bay ls\`.`));
23
+ return all.filter((o) => services.includes(o.service));
18
24
  }
19
- export async function rmCommand(cfg, feature, service, force) {
25
+ export async function rmCommand(cfg, feature, services, force) {
20
26
  await withLock(cfg.workspaceRoot, async () => {
21
27
  let removed = 0;
22
- const occs = resolveRm(cfg, feature, service);
28
+ const wholeFeature = services.length === 0;
29
+ const occs = resolveRm(cfg, feature, services);
23
30
  for (const o of occs) {
24
31
  const repo = repoPath(cfg, o.service);
25
32
  const branch = currentBranch(o.dir);
@@ -41,7 +48,7 @@ export async function rmCommand(cfg, feature, service, force) {
41
48
  removed++;
42
49
  }
43
50
  const slot = slotOfFeature(cfg, feature);
44
- if (!service && (scanOccupancy(cfg).get(slot) ?? []).length === 0) {
51
+ if (wholeFeature && (scanOccupancy(cfg).get(slot) ?? []).length === 0) {
45
52
  removeLabel(cfg, slot);
46
53
  if (removed === 0)
47
54
  log(`${c.green('✓')} ` + t(`释放空槽预约 "${feature}"(槽 ${slot})`, `released empty slot reservation "${feature}" (slot ${slot})`));
package/dist/mcp.js CHANGED
@@ -52,18 +52,18 @@ export const TOOLS = [
52
52
  { name: 'worktree_bay_run', description: '在某功能某服务的运行体里跑预设命令(如 test),可透传额外参数',
53
53
  inputSchema: { type: 'object', properties: { feature: str, service: str, name: str, args: { type: 'array', items: str } }, required: ['feature', 'service', 'name'] },
54
54
  toArgs: (a) => ['run', String(a.feature), String(a.service), String(a.name), ...(a.args ?? [])] },
55
- { name: 'worktree_bay_start', description: '启动功能的运行体(docker 容器 + node dev server 一起),不动 worktree。service 省略=该功能所有服务。',
56
- inputSchema: { type: 'object', properties: { feature: str, service: str }, required: ['feature'] },
57
- toArgs: (a) => ['start', String(a.feature), ...(a.service ? [String(a.service)] : [])] },
58
- { name: 'worktree_bay_stop', description: '停止功能的运行体(停 docker 容器 + 杀 node dev server),保留 worktree。service 省略=该功能所有服务。',
59
- inputSchema: { type: 'object', properties: { feature: str, service: str }, required: ['feature'] },
60
- toArgs: (a) => ['stop', String(a.feature), ...(a.service ? [String(a.service)] : [])] },
61
- { name: 'worktree_bay_restart', description: '重启功能的运行体(停掉再起,docker + node 一起)。改了配置或端口卡住时用。service 省略=该功能所有服务。',
62
- inputSchema: { type: 'object', properties: { feature: str, service: str }, required: ['feature'] },
63
- toArgs: (a) => ['restart', String(a.feature), ...(a.service ? [String(a.service)] : [])] },
64
- { name: 'worktree_bay_down', description: '拆除功能的 worktree:省略 service 拆整个功能的所有服务,传 service 只拆该服务(默认查脏/未推保护,force=true 强删)',
65
- inputSchema: { type: 'object', properties: { feature: str, service: str, force: { type: 'boolean' } }, required: ['feature'] },
66
- toArgs: (a) => ['rm', String(a.feature), ...(a.service ? [String(a.service)] : []), ...(a.force ? ['-f'] : [])] },
55
+ { name: 'worktree_bay_start', description: '启动功能的运行体(docker 容器 + node dev server 一起),不动 worktree。services 省略=该功能所有服务,也可列多个。',
56
+ inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
57
+ toArgs: (a) => ['start', String(a.feature), ...(a.services ?? [])] },
58
+ { name: 'worktree_bay_stop', description: '停止功能的运行体(停 docker 容器 + 杀 node dev server),保留 worktree。services 省略=全部,也可列多个。',
59
+ inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
60
+ toArgs: (a) => ['stop', String(a.feature), ...(a.services ?? [])] },
61
+ { name: 'worktree_bay_restart', description: '重启功能的运行体(停掉再起,docker + node 一起)。改了配置或端口卡住时用。services 省略=全部,也可列多个。',
62
+ inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
63
+ toArgs: (a) => ['restart', String(a.feature), ...(a.services ?? [])] },
64
+ { name: 'worktree_bay_down', description: '拆除 worktree:省略 services 拆整个功能(所有服务),给 services 只拆这些服务(默认查脏/未推保护,force=true 强删)',
65
+ inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str }, force: { type: 'boolean' } }, required: ['feature'] },
66
+ toArgs: (a) => ['down', String(a.feature), ...(a.services ?? []), ...(a.force ? ['-f'] : [])] },
67
67
  { name: 'worktree_bay_gc', description: '合并感知回收:默认 dry-run 只列建议,apply=true 才实际删除「已合并且干净」的功能',
68
68
  inputSchema: { type: 'object', properties: { apply: { type: 'boolean' } } },
69
69
  toArgs: (a) => ['gc', ...(a.apply ? ['--apply'] : [])] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-bay",
3
- "version": "2.5.0",
3
+ "version": "4.0.0",
4
4
  "description": "Per-feature git worktree + port slots for parallel multi-service development: auto deps, env wiring, frontend-to-backend, merge-aware reclaim, plus an MCP server for AI agents.",
5
5
  "keywords": [
6
6
  "git",
package/skill.md CHANGED
@@ -35,11 +35,10 @@ worktree-bay completion install # 一键装 shell 补全(可选)
35
35
  | `worktree-bay path <feature> <service>` | 打印某服务 worktree 的绝对路径(可 `cd $(worktree-bay path f api)`) |
36
36
  | `worktree-bay run <feature> <service> <name> [args...]` | 在某服务运行体里跑配置的 `run.<name>`(如 test),透传 args |
37
37
  | `worktree-bay sh <feature> <service>` | 进入某服务运行体的 shell |
38
- | `worktree-bay start <feature> [service]` | 启动功能的运行体(docker 容器 + node dev server 一起),不动 worktree |
39
- | `worktree-bay stop <feature> [service]` | 停止功能的运行体(停 docker + 杀 node dev server),保留 worktree |
40
- | `worktree-bay restart <feature> [service]` | 重启运行体(停掉再起;改了配置/端口卡住时用) |
41
- | `worktree-bay down <feature> [-f]` | 拆除整个功能的所有服务 worktree(= `rm <feature>`) |
42
- | `worktree-bay rm <feature> [service] [-f]` | 拆除某服务或整槽。默认查脏/未推保护,`-f` 强删 |
38
+ | `worktree-bay start <feature> [services...]` | 启动功能的运行体(docker 容器 + node dev server 一起);**省略 = 全部**,也可列多个。不动 worktree |
39
+ | `worktree-bay stop <feature> [services...]` | 停止功能的运行体(停 docker + 杀 node dev server);省略 = 全部,可列多个。保留 worktree |
40
+ | `worktree-bay restart <feature> [services...]` | 重启运行体(停掉再起);省略 = 全部,可列多个 |
41
+ | `worktree-bay down <feature> [services...]` | 拆除 worktree(停运行体 + teardown + 删 worktree);**省略 services = 整功能**,也可列多个只拆这些。默认查脏/未推保护,`-f` 强删 |
43
42
  | `worktree-bay gc [--apply]` | 合并感知回收:默认 dry-run 只列建议,`--apply` 才删「已合并且干净」的 |
44
43
  | `worktree-bay completion <install\|bash\|zsh\|fish>` | `install` 一键装进 shell;或打印补全脚本 |
45
44
  | `worktree-bay mcp` | 启动 MCP 服务(stdio,轻量脚本,客户端按需 spawn),供 AI 调用 |
@@ -157,8 +156,8 @@ worktree-bay gc # 回收已合并的
157
156
  | ③ 在运行体里执行 | 跑命令 / 开 shell | — | `run` / `sh` | — |
158
157
 
159
158
  - `up`:建 worktree+infra(首次)并起运行体;**重入 = 恢复运行体**(docker 挂了重跑 `up` 能拉回来,等价 `start`)。
160
- - `start`/`stop`/`restart`:只管②运行体,省略 service = 整功能、带 service = 单个;不碰 worktree 与代码。
161
- - `down`(=`rm <feature>`) `up`(功能级);`rm <feature> <service>` ↔ `add`(单服务级)。
159
+ - 整套 runtime/teardown 命令统一为「一个动词 + `[services...]`(省略=全部)」:`start` / `stop` / `restart` / `down`。
160
+ - `up`(创建,服务必填)↔ `down`(拆除,服务可省=整功能)。`add` 是「单服务 + 自定义分支/基点」的精细创建,其反操作 = `down <feature> <service>`。
162
161
 
163
162
  ## 工作原理要点
164
163
 
@@ -172,7 +171,7 @@ worktree-bay gc # 回收已合并的
172
171
 
173
172
  ## 给 AI(MCP)
174
173
 
175
- `worktree-bay mcp` 暴露工具:`worktree_bay_doctor / ls / up / claim / add / path / run / start / stop / restart / down / gc / init / skill`。`doctor` 列出全部服务(AI 借此得知服务名);`ls` 以 JSON 返回(含各 worktree 绝对路径、`▸run`);`path` 给某功能某服务的 worktree 目录;`start/stop/restart` 控制运行体(docker+node);`down` 省略 service 拆整功能、带 service 只拆该服务。要写或修改 `worktree-bay.config.json`、或拿不准命令/配置细节时,调用 `worktree_bay_skill` 取本指南全文。
174
+ `worktree-bay mcp` 暴露工具:`worktree_bay_doctor / ls / up / claim / add / path / run / start / stop / restart / down / gc / init / skill`。`doctor` 列出全部服务(AI 借此得知服务名);`ls` 以 JSON 返回(含各 worktree 绝对路径、`▸run`);`path` 给某功能某服务的 worktree 目录;`start/stop/restart` 控制运行体(docker+node);`down` 省略 services 拆整功能、给 services 只拆这些。要写或修改 `worktree-bay.config.json`、或拿不准命令/配置细节时,调用 `worktree_bay_skill` 取本指南全文。
176
175
 
177
176
  ## 常见坑
178
177