@jspg-ai/coding-bb 0.0.2-beta.29 → 0.0.3-beta.2

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/cbb/bin/cbbm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- // 内部命令入口:cbbm init / check-update / wiki / wp(由流程、hook、Agent 技能调用)
4
+ // 内部命令入口:cbbm check-update / wiki / wp(由流程、hook、Agent 技能调用)
5
5
  process.env.CBB_ENTRY = 'cbbm';
6
6
  require('../lib/install/init.js');
@@ -99,6 +99,19 @@ trigger: always_on
99
99
 
100
100
  **检验标准:** 用户从未因"AI 直接改了代码没打招呼"而感到意外。
101
101
 
102
+ ## 8. 业务空间目录纪律
103
+
104
+ **仅当工作目录是业务空间根(存在 `workspace-config.json`)时生效:写操作命令必须落在需求工作树内。**
105
+
106
+ 业务空间根(主分支)只负责组织与调度,不做需求开发。接到需求先建工作树(`/cbb:worktree-init` 或 `/cbb-worktree-init`),之后:
107
+
108
+ - 一切产生写操作的命令(git commit / openspec / 构建 / 测试 / 文件修改)必须以 `.worktrees/worktree-<需求名>/`(或其应用子目录)为工作目录:显式 `cd` 或绝对路径
109
+ - 禁止在空间根执行写操作:不在主分支提交代码、不在空间根创建 openspec 变更产物、不改 `.codespace/` 基准代码
110
+ - 只读命令(git status / log、查看文件)在空间根执行无妨
111
+ - 会话开始时确认本次需求对应的工作树路径;存在多个未完成工作树时,先与用户确认目标,不要猜
112
+
113
+ **检验标准:** 空间根主分支的 `git status` 永远干净(除工具维护的配置文件外),openspec 变更产物只出现在需求工作树内。
114
+
102
115
  ---
103
116
 
104
117
  **这些准则生效的标志:** diff 中不必要的改动减少、不会因过度设计而重写、澄清问题在实现前而非出错后提出。
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * 使用方式:
10
10
  * npm install -g @jspg-ai/coding-bb # 首次使用
11
- * npm install -g @jspg-ai/coding-bb
12
- * cd your-project && cbbm init
11
+ * cbb setup # 初始化业务空间(空间根全量安装)
12
+ * cbb update # 就地升级已安装目录
13
13
  */
14
14
 
15
15
  const fs = require('fs');
@@ -40,7 +40,7 @@ function execCmd(file, args, opts = {}) {
40
40
 
41
41
  const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
42
42
 
43
- // 目标项目根目录:默认取当前 cwd,init() / initWorkspace() 可在调用前覆盖。
43
+ // 目标项目根目录:默认取当前 cwd,init() 可在调用前覆盖。
44
44
  // 注意:使用 let 而非 const 是为了让 cbb setup 能在拉好仓库后把目标目录
45
45
  // 切到仓库根,再复用 init() 的完整安装流程,而无需把 TARGET_DIR 透传到所有 helper。
46
46
  let TARGET_DIR = process.cwd();
@@ -68,13 +68,17 @@ const SUPERPOWERS_WHITELIST = [
68
68
  'test-driven-development', // TDD 计划结构 + TDD Evidence 方法论基底
69
69
  ];
70
70
 
71
- // worktree 扩展 skills 部署到沙箱的白名单:init 只在 workspace 根执行
72
- // (由 installWorkspaceConfig 部署),沙箱内只需 push(推送)与 close(关闭)
73
- const SANDBOX_WORKTREE_SKILLS = ['cbb-worktree-push', 'cbb-worktree-close'];
71
+ // worktree 扩展 skills 部署白名单:init / close / push 三件套
72
+ // (单层安装:业务空间根是唯一安装点,worktree 管理入口 init 同样部署在根)
73
+ const WORKTREE_SKILLS = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
74
74
 
75
- // 装到沙箱的 tools skills:仅 cbb-design-to-wiki(openspec 设计阶段动作)。
76
- // cbb-wiki-ops 当前不分发(保留在源仓库,有用户需要时再加入 USER_LEVEL_SKILLS);Java 工具不装。
77
- const SANDBOX_TOOLS_SKILLS = ['cbb-design-to-wiki'];
75
+ // 装到空间的 tools skills:当前不分发。
76
+ // cbb-design-to-wiki(Wiki 发布)与 cbb-wiki-ops 保留在源仓库,需要时加入本清单即恢复分发;Java 工具不装。
77
+ const TOOLS_SKILLS = [];
78
+
79
+ // 不随安装分发的官方 OpenSpec 命令(源仓库保留,仍由 CLI 接管升级):
80
+ // onboard 为新手引导教程,团队入门已由文档覆盖;sync 是 archive 流程承重件,保留分发。
81
+ const OPENSPEC_COMMANDS_EXCLUDE = ['onboard.md'];
78
82
 
79
83
  // settings.json hook 工具
80
84
  const settingsManager = require('../utils/settings');
@@ -109,6 +113,18 @@ function detectExistingInstall() {
109
113
  return fs.existsSync(path.join(TARGET_DIR, MANIFEST_FILE));
110
114
  }
111
115
 
116
+ /**
117
+ * 判定目标目录是否为业务空间根:workspace-config.json 存在且 .git 是目录。
118
+ * 需求工作树内也有 workspace-config.json(tracked 副本),但其 .git 是文件(gitdir 指针),
119
+ * 必须以 .git 形态排除——避免「在 worktree 里误跑 cbb update」被当作空间根
120
+ * (误装 worktree 管理入口、在错误位置报旧导航提示)。
121
+ */
122
+ function detectWorkspaceRoot() {
123
+ if (!fs.existsSync(path.join(TARGET_DIR, 'workspace-config.json'))) return false;
124
+ const gitPath = path.join(TARGET_DIR, '.git');
125
+ return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
126
+ }
127
+
112
128
  // ── 清单管理 ──────────────────────────────────────────────
113
129
 
114
130
  /** 读取清单,返回 Set<path> */
@@ -801,7 +817,7 @@ async function detectUserLevelSuperpowers(selectedAdapters, yes = false) {
801
817
  // ── 主流程 ────────────────────────────────────────────────
802
818
 
803
819
  async function init(targetDir, options = {}) {
804
- const { yes = false } = options;
820
+ const { yes = false, selectedAdapters: presetAdapters = null } = options;
805
821
 
806
822
  // 允许调用方指定目标目录(cbb setup 在拉好仓库后切到仓库根调用)。
807
823
  // 不传则保持模块级 TARGET_DIR(即调用 init 时的 cwd)。
@@ -812,16 +828,33 @@ async function init(targetDir, options = {}) {
812
828
  const action = isUpgrade ? '升级' : '安装';
813
829
  const managedPaths = readManifest();
814
830
  const newPaths = new Set();
831
+ const isWorkspaceRoot = detectWorkspaceRoot();
815
832
 
816
833
  console.log(`\n 🚀 ${action} @jspg-ai/coding-bb...\n`);
817
834
 
818
835
  // 先选适配器:后续的前置依赖检测仅针对所选适配器,避免误导(如选了 Qoder 却提示安装 Claude Code 的 Superpowers)
819
836
  // 非交互模式判断:显式 -y/--yes 或 stdin 非 TTY(pipe/ignore)均视为非交互
837
+ // 选择优先级:调用方预设(cbb setup)> .last-tools(非交互沿用)> 交互选择;无记录才回退全选
820
838
  const isNonInteractive = yes || !process.stdin.isTTY;
821
- if (yes && process.stdin.isTTY) {
822
- log('--yes 非交互模式,已默认选中所有适配器', 'info');
839
+ let selectedAdapters;
840
+ if (presetAdapters && presetAdapters.length > 0) {
841
+ selectedAdapters = presetAdapters;
842
+ } else if (isNonInteractive) {
843
+ const lastTools = readLastTools();
844
+ const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
845
+ selectedAdapters = reused.length > 0 ? reused : adapters;
846
+ if (yes && process.stdin.isTTY) {
847
+ log(reused.length > 0
848
+ ? `--yes 非交互模式,沿用上次工具选择:${reused.map(a => a.displayName).join('、')}`
849
+ : '--yes 非交互模式,无上次选择记录,已默认选中所有适配器', 'info');
850
+ }
851
+ } else {
852
+ selectedAdapters = await selectTools(isUpgrade);
853
+ }
854
+ // 业务空间根记录本次选择,供 cbb update 等非交互场景沿用
855
+ if (isWorkspaceRoot && presetAdapters && presetAdapters.length > 0) {
856
+ saveLastTools(selectedAdapters);
823
857
  }
824
- const selectedAdapters = isNonInteractive ? adapters : await selectTools(isUpgrade);
825
858
 
826
859
  // 前置依赖检测(放在选适配器之后,只检测与所选工具相关的依赖)
827
860
  const depProgress = progress('依赖');
@@ -864,14 +897,14 @@ async function init(targetDir, options = {}) {
864
897
  }
865
898
  // 官方 OpenSpec Skills(openspec/skills/)不部署——工作流入口统一为 /opsx:* 斜杠命令
866
899
  //(openspec/commands/ 在下方 Commands 段部署),避免双形态重复
867
- // worktree 扩展 Skills:沙箱内只需 push / close(init workspace 根的子集安装)
868
- const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, SANDBOX_WORKTREE_SKILLS);
900
+ // worktree 扩展 Skills:init / close / push 三件套(单层安装,空间根是唯一安装点)
901
+ const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, WORKTREE_SKILLS);
869
902
  if (osExtResult.count > 0) {
870
903
  osCount = osExtResult.count;
871
904
  osExtResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
872
905
  }
873
- // 工具 Skills:仅 cbb-design-to-wiki(设计文档发布属 openspec 设计阶段动作)
874
- const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, SANDBOX_TOOLS_SKILLS);
906
+ // 工具 Skills:当前不分发(恢复方式见 TOOLS_SKILLS 注释)
907
+ const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, TOOLS_SKILLS);
875
908
  if (extResult.count > 0) {
876
909
  extCount = extResult.count;
877
910
  extResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
@@ -902,14 +935,15 @@ async function init(targetDir, options = {}) {
902
935
  await detectUserLevelSuperpowers(selectedAdapters, yes);
903
936
  }
904
937
 
905
- // 安装 Commands(官方 12 个 → /opsx:*;worktree 管理 → /cbb:worktree-*,
906
- // 仅装 close/push,init 只在 workspace 根使用,由 installWorkspaceConfig 部署)
938
+ // 安装 Commands(官方 11 个 → /opsx:*,onboard 不分发;worktree 管理 → /cbb:worktree-*,init/close/push 三件套)
907
939
  const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
908
940
  if (commandsAdapters.length > 0) {
909
941
  console.log(`\n ⚡ ${action} Commands...`);
910
942
  const commandsBaseSrc = path.join(SHARED_STANDARDS_DIR, 'openspec', 'commands');
911
943
  const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
912
- const cmdFilter = ['worktree-close.md', 'worktree-push.md'];
944
+ const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
945
+ const openspecCmdInclude = fs.readdirSync(commandsBaseSrc)
946
+ .filter(f => f.endsWith('.md') && !OPENSPEC_COMMANDS_EXCLUDE.includes(f));
913
947
 
914
948
  const cmdDests = [...new Set(commandsAdapters
915
949
  .flatMap(a => [a.commandsDir, a.worktreeCommandsDir])
@@ -917,8 +951,8 @@ async function init(targetDir, options = {}) {
917
951
  let cmdCount = 0;
918
952
  commandsAdapters.forEach(adapter => {
919
953
  let adapterCmdCount = 0;
920
- // 官方 Commands(openspec/commands/)— 始终部署
921
- const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc);
954
+ // 官方 Commands(openspec/commands/)— 部署除不分发清单外的全部
955
+ const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc, openspecCmdInclude);
922
956
  if (baseResult.count > 0) {
923
957
  adapterCmdCount += baseResult.count;
924
958
  baseResult.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
@@ -948,6 +982,15 @@ async function init(targetDir, options = {}) {
948
982
  setupOpenSpec(isUpgrade, managedPaths);
949
983
  newPaths.add('openspec/config.yaml');
950
984
 
985
+ // 业务空间根专属:空间导航 AGENTS.md(仅缺失时生成,永不覆盖)+ 旧版导航文案提示
986
+ let agentsMdState = null;
987
+ if (isWorkspaceRoot) {
988
+ agentsMdState = workspaces.ensureWorkspaceAgentsMd(TARGET_DIR);
989
+ if (workspaces.detectLegacyAgentsMd(TARGET_DIR)) {
990
+ log('空间根 AGENTS.md 仍是旧版导航文案(单层安装改造前模板)。建议删除该文件后重跑 cbb update 重新生成(仅缺失时生成,不会覆盖你的增补内容)', 'warn');
991
+ }
992
+ }
993
+
951
994
  // 写入自动版本检查 hook
952
995
  const hookAdapters = selectedAdapters.filter(a => a.settingsFile && a.hookEvents);
953
996
  if (hookAdapters.length > 0) {
@@ -1015,7 +1058,7 @@ async function init(targetDir, options = {}) {
1015
1058
  console.log(`\n ✅ ${action}完成!\n`);
1016
1059
 
1017
1060
  if (yes) {
1018
- console.log(c('dim', ' (使用 --yes 非交互模式,已默认选中所有适配器)'));
1061
+ console.log(c('dim', ` (使用 --yes 非交互模式,工具:${selectedAdapters.map(a => a.displayName).join('、')})`));
1019
1062
  }
1020
1063
 
1021
1064
  // 适配器映射(单行)
@@ -1045,161 +1088,13 @@ async function init(targetDir, options = {}) {
1045
1088
  if (hasSkills) extras.push('Skills');
1046
1089
  if (hasCommands) extras.push('Commands');
1047
1090
  if (hasHooks) extras.push('版本检查 hook');
1091
+ if (agentsMdState === 'created') extras.push('AGENTS.md(空间导航)');
1048
1092
  if (extras.length > 0) {
1049
1093
  console.log(` ${extras.join(' / ')} 已配置`);
1050
1094
  }
1051
1095
  console.log('');
1052
1096
  }
1053
1097
 
1054
- /**
1055
- * 为 workspace 根目录安装 AI 配置子集(由 cbb setup 调用)。
1056
- *
1057
- * 只装 worktree 生命周期管理(init/close/push)+ openspec 运行时配置 + 空间导航
1058
- * AGENTS.md(仅缺失时生成,永不覆盖),不装 base 工作流、编码规范、superpowers、
1059
- * 版本检查 hook--这些属于 sandbox worktree 内的开发配置,由 worktree 内
1060
- * `cbbm init --yes` 完整模式安装。在 workspace 根目录
1061
- * (主分支)装 base 工作流会诱导绕过 worktree 直接在主分支做需求开发。
1062
- */
1063
- async function installWorkspaceConfig(targetDir, options = {}) {
1064
- const { yes = false, selectedAdapters: presetAdapters = null } = options;
1065
- if (targetDir) TARGET_DIR = targetDir;
1066
-
1067
- const isUpgrade = detectExistingInstall();
1068
- const managedPaths = readManifest();
1069
- const newPaths = new Set();
1070
-
1071
- let selectedAdapters;
1072
- if (presetAdapters && presetAdapters.length > 0) {
1073
- // cbb setup 场景:上层已交互选定适配器,持久化供 cbb update 预选
1074
- selectedAdapters = presetAdapters;
1075
- saveLastTools(selectedAdapters);
1076
- } else {
1077
- const isNonInteractive = yes || !process.stdin.isTTY;
1078
- if (isNonInteractive) {
1079
- // 非交互(cbb update / 管道运行):沿用上次工具选择,避免误装未使用的适配器
1080
- const lastTools = readLastTools();
1081
- const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
1082
- if (reused.length > 0) {
1083
- selectedAdapters = reused;
1084
- } else {
1085
- selectedAdapters = adapters;
1086
- }
1087
- } else {
1088
- selectedAdapters = await selectTools(isUpgrade);
1089
- }
1090
- }
1091
- // 只装 worktree 生命周期管理 Skills(init/close/push)
1092
- const skillsAdapters = selectedAdapters.filter(a => a.skillsDir);
1093
- const commandsAdapters = selectedAdapters.filter(a => a.worktreeCommandsDir);
1094
- let skillCount = 0;
1095
- let cmdCount = 0;
1096
- let adapterRoots = [];
1097
-
1098
- // 「AI 配置」阶段心跳:检查依赖 → 部署 skills/commands → 写入 openspec 配置 → 生成导航。
1099
- // 其中 npm 查询/安装最长可达分钟级,必须让用户看到进程仍活着;任何异常先清掉进度行。
1100
- const cfgProgress = progress('AI 配置');
1101
- let agentsMdState = 'exists';
1102
- let openspecRes;
1103
- try {
1104
- await ensureOpenSpecCli(t => cfgProgress.update(t));
1105
-
1106
- if (skillsAdapters.length > 0) {
1107
- cfgProgress.update('部署 skills');
1108
- const openspecExtendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
1109
- const osExtFilter = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
1110
- skillsAdapters.forEach(adapter => {
1111
- const r = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, osExtFilter);
1112
- if (r.count > 0) {
1113
- skillCount = Math.max(skillCount, r.count);
1114
- r.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
1115
- }
1116
- });
1117
- adapterRoots = skillsAdapters.map(a => a.skillsDir.split('/')[0]);
1118
- }
1119
-
1120
- // 只装 worktree 管理 Commands(cbb:worktree-init/close/push)
1121
- if (commandsAdapters.length > 0) {
1122
- cfgProgress.update('部署 worktree 命令');
1123
- const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
1124
- const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
1125
- commandsAdapters.forEach(adapter => {
1126
- const r = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
1127
- if (r.count > 0) {
1128
- cmdCount = Math.max(cmdCount, r.count);
1129
- r.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
1130
- }
1131
- });
1132
- if (adapterRoots.length === 0) adapterRoots = commandsAdapters.map(a => a.worktreeCommandsDir.split('/')[0]);
1133
- }
1134
-
1135
- // 设置 OpenSpec 工作流(config.yaml + schemas)
1136
- cfgProgress.update('写入 openspec 配置');
1137
- const schemasSrcs = [
1138
- path.join(SHARED_STANDARDS_DIR, 'config', 'openspec', 'schemas'),
1139
- ];
1140
- schemasSrcs.forEach(src => {
1141
- if (fs.existsSync(src)) {
1142
- fs.readdirSync(src).filter(f2 => fs.statSync(path.join(src, f2)).isDirectory())
1143
- .forEach(name => newPaths.add(`openspec/schemas/${name}`));
1144
- }
1145
- });
1146
- openspecRes = setupOpenSpec(isUpgrade, managedPaths);
1147
- newPaths.add('openspec/config.yaml');
1148
-
1149
- // 空间导航文件 AGENTS.md:仅缺失时生成,永不覆盖(不登记清单,卸载不删)
1150
- cfgProgress.update('生成空间导航');
1151
- agentsMdState = workspaces.ensureWorkspaceAgentsMd(TARGET_DIR);
1152
- } finally {
1153
- cfgProgress.done();
1154
- }
1155
-
1156
- // 升级时清理旧路径(workspace 曾装过的 base 工作流等不属于此子集,删除)
1157
- const cleaned = [];
1158
- const keptDocs = [];
1159
- if (isUpgrade) {
1160
- const oldPaths = pathsToDelete(managedPaths, newPaths);
1161
- oldPaths.forEach(oldPath => {
1162
- const targetPath = path.join(TARGET_DIR, oldPath);
1163
- if (fs.existsSync(targetPath)) {
1164
- removePath(targetPath);
1165
- cleaned.push(oldPath);
1166
- }
1167
- });
1168
- keptDocs.push(...protectedKeptPaths(managedPaths, newPaths));
1169
- (skillsAdapters || []).forEach(adapter => {
1170
- const legacyNodeModules = path.join(TARGET_DIR, adapter.skillsDir, 'node_modules');
1171
- if (fs.existsSync(legacyNodeModules)) {
1172
- removePath(legacyNodeModules);
1173
- cleaned.push(`${adapter.skillsDir}/node_modules`);
1174
- }
1175
- });
1176
- }
1177
-
1178
- writeManifest(newPaths, selectedAdapters);
1179
- updateGitignore();
1180
-
1181
- // 汇总输出(一行主结果 + 可选清理/保留明细)
1182
- const rootLabel = [...new Set(adapterRoots)].join('/');
1183
- const parts = [];
1184
- if (skillCount > 0) parts.push(`${rootLabel} skills×${skillCount}`);
1185
- if (cmdCount > 0) parts.push(`commands×${cmdCount}`);
1186
- if (openspecRes.installed.length > 0) {
1187
- parts.push(`openspec ${openspecRes.defaultSchema || openspecRes.installed.join('/')}`);
1188
- }
1189
- if (agentsMdState === 'created') parts.push('AGENTS.md(空间导航)');
1190
- const configLabel = 'AI 配置';
1191
- step('✓', configLabel, parts.join(' · '));
1192
- if (cleaned.length > 0) {
1193
- const MAX_DISPLAY = 3;
1194
- const preview = cleaned.slice(0, MAX_DISPLAY).join('、');
1195
- step('!', '清理残留', `${cleaned.length} 个(${preview}${cleaned.length > MAX_DISPLAY ? ' 等' : ''})`,
1196
- { iconColor: 'yellow' });
1197
- }
1198
- if (keptDocs.length > 0) {
1199
- step('!', '保留文档', `${keptDocs.join('、')}(本包已不再管理)`, { iconColor: 'yellow' });
1200
- }
1201
- }
1202
-
1203
1098
  // ── cbb setup 命令 ──────────────────────────────────────
1204
1099
 
1205
1100
  /**
@@ -1521,9 +1416,9 @@ async function initSingleWorkspace(w, opts = {}) {
1521
1416
  return { ok: true, subdir: subdirName, upToDate: true };
1522
1417
  }
1523
1418
 
1524
- // 6. 安装 workspace AI 配置子集(worktree 管理技能/命令 + openspec 配置,不含 base 工作流)
1419
+ // 6. 安装空间 AI 配置(单层全量:开发配置 + worktree 管理,统一安装器自动识别空间根)
1525
1420
  try {
1526
- await installWorkspaceConfig(targetDir, { yes: true, selectedAdapters: opts.selectedAdapters });
1421
+ await init(targetDir, { yes: true, selectedAdapters: opts.selectedAdapters });
1527
1422
  return { ok: true, subdir: subdirName };
1528
1423
  } catch (e) {
1529
1424
  step('✗', 'AI 配置', e.message, { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
@@ -1733,13 +1628,16 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
1733
1628
  }
1734
1629
 
1735
1630
  /**
1736
- * cbb update:按现有安装模式升级当前目录内容 + 用户级组件。
1631
+ * cbb update:就地升级当前目录内容 + 用户级组件。
1737
1632
  *
1738
- * 安装模式判定:清单含 rules 路径 → 完整模式(worktree 沙箱/单项目),
1739
- * 否则为 workspace 子集模式(空间根,只装 worktree 管理 + openspec 配置)。
1633
+ * 单层安装:统一安装器按空间根判定(workspace-config.json + .git 目录)补齐
1634
+ * worktree 管理入口与空间导航;旧子集清单升级时由清单差集机制自动补齐全量内容。
1740
1635
  */
1741
1636
  async function updateInstalled() {
1742
- await setupUserLevelComponents();
1637
+ // 用户级组件沿用本目录的工具选择(.last-tools),无记录或为空才回退全量
1638
+ const lastTools = readLastTools();
1639
+ const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
1640
+ await setupUserLevelComponents(reused.length > 0 ? reused : adapters);
1743
1641
 
1744
1642
  const manifest = readManifest();
1745
1643
  if (manifest.size === 0) {
@@ -1747,17 +1645,7 @@ async function updateInstalled() {
1747
1645
  process.exit(1);
1748
1646
  }
1749
1647
 
1750
- const isFullInstall = [...manifest].some(p =>
1751
- adapters.some(a => p.startsWith(`${a.rulesDir}/`))
1752
- );
1753
-
1754
- if (isFullInstall) {
1755
- log('检测到完整模式安装,升级开发配置(rules + skills + commands + 工作流)...', 'info');
1756
- await init(undefined, {});
1757
- } else {
1758
- log('检测到 workspace 子集模式,升级 worktree 管理 + openspec 配置...', 'info');
1759
- await installWorkspaceConfig(undefined, {});
1760
- }
1648
+ await init(undefined, {});
1761
1649
  }
1762
1650
 
1763
1651
  // ── 卸载 ──────────────────────────────────────────────────
@@ -1957,7 +1845,7 @@ async function uninstall() {
1957
1845
 
1958
1846
  // 入口身份:cbb(用户命令) / cbbm(内部命令,由流程、hook、Agent 技能调用)
1959
1847
  const ENTRY = process.env.CBB_ENTRY === 'cbbm' ? 'cbbm' : 'cbb';
1960
- const INTERNAL_COMMANDS = ['init', 'check-update', 'wiki', 'wp'];
1848
+ const INTERNAL_COMMANDS = ['check-update', 'wiki', 'wp'];
1961
1849
  const UPSTREAM_FLAGS = ['--upstream-superpowers', '--upstream-openspec'];
1962
1850
 
1963
1851
  const args = process.argv.slice(2);
@@ -2045,14 +1933,6 @@ switch (command) {
2045
1933
  }
2046
1934
  break;
2047
1935
  }
2048
- case 'init': {
2049
- const yes = restArgs.includes('-y') || restArgs.includes('--yes');
2050
- init(undefined, { yes }).catch(err => {
2051
- log(err.message, 'error');
2052
- process.exit(1);
2053
- });
2054
- break;
2055
- }
2056
1936
  case 'uninstall':
2057
1937
  uninstall().catch(err => {
2058
1938
  log(err.message, 'error');
@@ -2092,7 +1972,6 @@ function showHelp() {
2092
1972
  console.log(`
2093
1973
  @jspg-ai/coding-bb v${pkg.version} — cbbm:内部命令(由流程 / hook / Agent 技能调用)
2094
1974
 
2095
- cbbm init --yes worktree 沙箱内安装开发配置(/cbb:worktree-init Step 4 自动调用)
2096
1975
  cbbm check-update 版本检查(用户级 hook 每日自动触发)
2097
1976
  cbbm wiki / cbbm wp Wiki 查询与发布(AI 通过技能调用)
2098
1977
 
@@ -186,6 +186,29 @@ function ensureWorkspaceAgentsMd(targetDir) {
186
186
  return 'created';
187
187
  }
188
188
 
189
+ // 旧版空间导航模板(0.0.3 单层安装改造前)的特征语句。
190
+ // 该文件归用户所有、永不覆盖,故只能提示用户删除后重跑 cbb update 重新生成。
191
+ const LEGACY_AGENTS_MD_MARKERS = [
192
+ '故意不含编码规范',
193
+ '自动安装沙箱内的编码规范',
194
+ ];
195
+
196
+ /**
197
+ * 检测空间根 AGENTS.md 是否仍为旧版导航文案(命中任一特征语句即为旧版)。
198
+ * 仅用于安装时打印提示,不修改文件。
199
+ * @returns {boolean}
200
+ */
201
+ function detectLegacyAgentsMd(targetDir) {
202
+ const agentsPath = path.join(targetDir, 'AGENTS.md');
203
+ if (!fs.existsSync(agentsPath)) return false;
204
+ try {
205
+ const content = fs.readFileSync(agentsPath, 'utf-8');
206
+ return LEGACY_AGENTS_MD_MARKERS.some(m => content.includes(m));
207
+ } catch (_) {
208
+ return false;
209
+ }
210
+ }
211
+
189
212
  /**
190
213
  * 在指定目录执行 `git remote -v`,解析出 [{ name, url }] 列表。
191
214
  * 任何异常静默返回空数组,便于上层用 state 机判定。
@@ -285,6 +308,7 @@ module.exports = {
285
308
  loadWorkspaces,
286
309
  validateWorkspaceConfig,
287
310
  ensureWorkspaceAgentsMd,
311
+ detectLegacyAgentsMd,
288
312
  readGitRemotes,
289
313
  detectRepo,
290
314
  extractRepoBasename,
@@ -17,7 +17,7 @@ const path = require('path');
17
17
  const versionUtil = require('./version');
18
18
 
19
19
  // 缓存提升到用户级:registry 最新版本号是全局属性(npm 全局包只有一个版本),
20
- // 团队多 workspace / 多 worktree 沙箱共享一份,每日真实请求一次;
20
+ // 团队多 workspace / 多 worktree 共享一份,每日真实请求一次;
21
21
  // 「本项目是否落后」仍按项目清单版本在本地判定。
22
22
  const CACHE_FILE = path.join(os.homedir(), '.cbb', '.update-check-cache.json');
23
23
  const CACHE_TTL = 24 * 60 * 60 * 1000;
@@ -9,7 +9,7 @@ tags: [worktree, cleanup, worktree, git-worktree]
9
9
 
10
10
  ## 重要:第一步必须用 Read 工具直接打开 SKILL.md
11
11
 
12
- **禁止**用 `ls` / `find` / `cat` / `head` / `grep` 等 bash 命令搜索 SKILL.md。这些命令在 Qoder Windows PowerShell 沙箱中经常失败。
12
+ **禁止**用 `ls` / `find` / `cat` / `head` / `grep` 等 bash 命令搜索 SKILL.md。这些命令在 Agent 运行环境(Qoder Windows PowerShell 等)中经常失败。
13
13
 
14
14
  **直接路径**:
15
15
  - Qoder 适配器:`.qoder/skills/cbb-worktree-close/SKILL.md`
@@ -9,7 +9,7 @@ tags: [worktree, isolation, worktree, git-worktree]
9
9
 
10
10
  ## 重要:第一步必须用 Read 工具直接打开 SKILL.md
11
11
 
12
- **禁止**用 `ls` / `find` / `cat` / `head` / `grep` 等 bash 命令搜索 SKILL.md 是否存在。**这些命令在 Qoder Windows PowerShell 沙箱中经常失败**(如 `head` 不存在、stderr 重定向无效等)。
12
+ **禁止**用 `ls` / `find` / `cat` / `head` / `grep` 等 bash 命令搜索 SKILL.md 是否存在。**这些命令在 Agent 运行环境(Qoder Windows PowerShell 等)中经常失败**(如 `head` 不存在、stderr 重定向无效等)。
13
13
 
14
14
  **直接路径**:
15
15
  - Qoder 适配器:`.qoder/skills/cbb-worktree-init/SKILL.md`
@@ -17,7 +17,7 @@ tags: [worktree, isolation, worktree, git-worktree]
17
17
 
18
18
  **第一步**:
19
19
  1. 用 Read 工具直接打开 `.qoder/skills/cbb-worktree-init/SKILL.md`(或 `.claude/skills/...`)
20
- 2. 按 SKILL.md 的 Step 0 → 1 → 2 → 3 → 4 → 5 → 6 顺序执行
20
+ 2. 按 SKILL.md 的 Step 0 → 1 → 2 → 3 → 4 顺序执行
21
21
  3. 每次调用 Node.js 脚本时,使用**绝对路径**(避免依赖相对路径):
22
22
  ```
23
23
  node "D:\workspace\<用户工作空间>\.qoder\skills\cbb-worktree-init\scripts\<name>.js" <参数>
@@ -29,9 +29,7 @@ tags: [worktree, isolation, worktree, git-worktree]
29
29
  - `check-repos.js`:`<ws_root>`
30
30
  - `create-branches.js`:`<ws_root> <branch>`
31
31
  - `update-gitignore.js`:`<ws_root> <branch>`
32
- - `create-worktrees.js`:`<ws_root> <branch>`
33
- - `install-ai.js`:`<wt_path>`
34
- - `auto-open.js`:`<wt_path>`
32
+ - `create-worktrees.js`:`<ws_root> <branch>`
35
33
 
36
34
  ## Input
37
35
 
@@ -42,14 +40,12 @@ tags: [worktree, isolation, worktree, git-worktree]
42
40
  1. Step 0:环境探活 + 解析 workspace-config.json
43
41
  2. Step 1:同步 .codespace/ 仓库(fetch --all)
44
42
  3. Step 2:状态检查(5 项 / 3 项分级)
45
- 4. Step 3:创建分支(workspace HEAD,app 从 origin/master
46
- 5. Step 4:创建 worktree(workspace + 嵌套 app)
47
- 6. Step 5:在 worktree 中安装 AI 配置(cbbm init -y)
48
- 7. Step 6:环境感知 + 进入工作目录
43
+ 4. Step 3:创建分支 → 推送 upstream → 创建 worktree(workspace + 嵌套 app)
44
+ 5. Step 4:收尾与交接(继续当前会话,声明目录纪律)
49
45
 
50
46
  ## 关键 Guardrails
51
47
 
52
48
  - workspace 主分支 tracked 的所有内容(openspec/、cbb/ 等)由 git worktree 自动 checkout——**禁止 cp / clone / symlink “带过来”**
53
49
  - 必须用 `git branch` 而非 `git checkout -b`(分支不能同时在主目录与 worktree 检出)
54
- - 沙箱内 node 不可用时 fail-fast:终止流程并提示用户到系统终端重试
50
+ - 运行环境 node 不可用时 fail-fast:终止流程并提示用户到系统终端重试
55
51
  - **不要试图搜索 SKILL.md**——直接用 Read 工具打开
@@ -414,7 +414,7 @@ git -C "<ws_root>" worktree list
414
414
  - 删除工作空间根目录的 AI 配置目录(`.claude/`、`.qoder/`、`.cbb/` 等源目录)
415
415
  - 用 `AskUserQuestion` 收集自由文本(分支名等直接对话提问)
416
416
  - 手动改任何仓库的 `.gitignore`(关闭流程不碰它)
417
- - 沙箱内 node 不可用时强行继续 → 改为 fail-fast 提示用户
417
+ - 运行环境 node 不可用时强行继续 → 改为 fail-fast 提示用户
418
418
 
419
419
  ### 必须执行
420
420
 
@@ -34,7 +34,7 @@ const checks = {};
34
34
  // ── 必检:git ─────────────────────────────────────────
35
35
  checks.git = tryExec('git --version');
36
36
  if (!checks.git.ok) {
37
- console.error('❌ git 启动失败(可能是 Qoder 沙箱 DLL 问题 0xc0000142)');
37
+ console.error('❌ git 启动失败(可能是 Agent 运行环境 DLL 问题 0xc0000142)');
38
38
  console.error(' 解决方案:到系统终端重新执行本技能');
39
39
  process.exit(1);
40
40
  }
@@ -57,12 +57,12 @@ if (!checks.node.ok) {
57
57
  console.log('✅ node OK:', checks.node.out);
58
58
  }
59
59
 
60
- // ── 沙箱检测 ─────────────────────────────────────────
60
+ // ── 运行环境检测 ─────────────────────────────────────────
61
61
  const inWorktree = !!(process.env.QODER_AGENT || process.env.QODER_WORKTREE);
62
62
  if (inWorktree) {
63
- console.log('🛡️ Qoder 沙箱环境:移除 worktree 时如遇 IDE 锁,将降级处理');
63
+ console.log('🛡️ Qoder Agent 运行环境:移除 worktree 时如遇 IDE 锁,将降级处理');
64
64
  } else {
65
- console.log('✅ 非沙箱环境');
65
+ console.log('✅ 系统终端环境');
66
66
  }
67
67
 
68
68
  const summary = {