@jspg-ai/coding-bb 0.0.2-beta.29 → 0.0.3-beta.1
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 +1 -1
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +13 -0
- package/cbb/lib/install/init.js +68 -195
- package/cbb/lib/install/workspaces.js +24 -0
- package/cbb/lib/utils/check-update.js +1 -1
- package/cbb/worktrees/commands/worktree-close.md +1 -1
- package/cbb/worktrees/commands/worktree-init.md +6 -10
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +4 -4
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +26 -51
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env-deep.js +3 -33
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env.js +5 -14
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +2 -2
- package/config/workspace-agents.sample.md +39 -37
- package/config/workspaces.json +5 -0
- package/package.json +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/auto-open.js +0 -136
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-ai.js +0 -177
package/cbb/bin/cbbm.js
CHANGED
|
@@ -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 中不必要的改动减少、不会因过度设计而重写、澄清问题在实现前而非出错后提出。
|
package/cbb/lib/install/init.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 使用方式:
|
|
10
10
|
* npm install -g @jspg-ai/coding-bb # 首次使用
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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()
|
|
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,13 @@ const SUPERPOWERS_WHITELIST = [
|
|
|
68
68
|
'test-driven-development', // TDD 计划结构 + TDD Evidence 方法论基底
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
-
// worktree 扩展 skills
|
|
72
|
-
//
|
|
73
|
-
const
|
|
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
|
-
//
|
|
75
|
+
// 装到空间的 tools skills:仅 cbb-design-to-wiki(openspec 设计阶段动作)。
|
|
76
76
|
// cbb-wiki-ops 当前不分发(保留在源仓库,有用户需要时再加入 USER_LEVEL_SKILLS);Java 工具不装。
|
|
77
|
-
const
|
|
77
|
+
const TOOLS_SKILLS = ['cbb-design-to-wiki'];
|
|
78
78
|
|
|
79
79
|
// settings.json hook 工具
|
|
80
80
|
const settingsManager = require('../utils/settings');
|
|
@@ -109,6 +109,18 @@ function detectExistingInstall() {
|
|
|
109
109
|
return fs.existsSync(path.join(TARGET_DIR, MANIFEST_FILE));
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 判定目标目录是否为业务空间根:workspace-config.json 存在且 .git 是目录。
|
|
114
|
+
* 需求工作树内也有 workspace-config.json(tracked 副本),但其 .git 是文件(gitdir 指针),
|
|
115
|
+
* 必须以 .git 形态排除——避免「在 worktree 里误跑 cbb update」被当作空间根
|
|
116
|
+
* (误装 worktree 管理入口、在错误位置报旧导航提示)。
|
|
117
|
+
*/
|
|
118
|
+
function detectWorkspaceRoot() {
|
|
119
|
+
if (!fs.existsSync(path.join(TARGET_DIR, 'workspace-config.json'))) return false;
|
|
120
|
+
const gitPath = path.join(TARGET_DIR, '.git');
|
|
121
|
+
return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
|
|
122
|
+
}
|
|
123
|
+
|
|
112
124
|
// ── 清单管理 ──────────────────────────────────────────────
|
|
113
125
|
|
|
114
126
|
/** 读取清单,返回 Set<path> */
|
|
@@ -801,7 +813,7 @@ async function detectUserLevelSuperpowers(selectedAdapters, yes = false) {
|
|
|
801
813
|
// ── 主流程 ────────────────────────────────────────────────
|
|
802
814
|
|
|
803
815
|
async function init(targetDir, options = {}) {
|
|
804
|
-
const { yes = false } = options;
|
|
816
|
+
const { yes = false, selectedAdapters: presetAdapters = null } = options;
|
|
805
817
|
|
|
806
818
|
// 允许调用方指定目标目录(cbb setup 在拉好仓库后切到仓库根调用)。
|
|
807
819
|
// 不传则保持模块级 TARGET_DIR(即调用 init 时的 cwd)。
|
|
@@ -812,16 +824,33 @@ async function init(targetDir, options = {}) {
|
|
|
812
824
|
const action = isUpgrade ? '升级' : '安装';
|
|
813
825
|
const managedPaths = readManifest();
|
|
814
826
|
const newPaths = new Set();
|
|
827
|
+
const isWorkspaceRoot = detectWorkspaceRoot();
|
|
815
828
|
|
|
816
829
|
console.log(`\n 🚀 ${action} @jspg-ai/coding-bb...\n`);
|
|
817
830
|
|
|
818
831
|
// 先选适配器:后续的前置依赖检测仅针对所选适配器,避免误导(如选了 Qoder 却提示安装 Claude Code 的 Superpowers)
|
|
819
832
|
// 非交互模式判断:显式 -y/--yes 或 stdin 非 TTY(pipe/ignore)均视为非交互
|
|
833
|
+
// 选择优先级:调用方预设(cbb setup)> .last-tools(非交互沿用)> 交互选择;无记录才回退全选
|
|
820
834
|
const isNonInteractive = yes || !process.stdin.isTTY;
|
|
821
|
-
|
|
822
|
-
|
|
835
|
+
let selectedAdapters;
|
|
836
|
+
if (presetAdapters && presetAdapters.length > 0) {
|
|
837
|
+
selectedAdapters = presetAdapters;
|
|
838
|
+
} else if (isNonInteractive) {
|
|
839
|
+
const lastTools = readLastTools();
|
|
840
|
+
const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
|
|
841
|
+
selectedAdapters = reused.length > 0 ? reused : adapters;
|
|
842
|
+
if (yes && process.stdin.isTTY) {
|
|
843
|
+
log(reused.length > 0
|
|
844
|
+
? `--yes 非交互模式,沿用上次工具选择:${reused.map(a => a.displayName).join('、')}`
|
|
845
|
+
: '--yes 非交互模式,无上次选择记录,已默认选中所有适配器', 'info');
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
selectedAdapters = await selectTools(isUpgrade);
|
|
849
|
+
}
|
|
850
|
+
// 业务空间根记录本次选择,供 cbb update 等非交互场景沿用
|
|
851
|
+
if (isWorkspaceRoot && presetAdapters && presetAdapters.length > 0) {
|
|
852
|
+
saveLastTools(selectedAdapters);
|
|
823
853
|
}
|
|
824
|
-
const selectedAdapters = isNonInteractive ? adapters : await selectTools(isUpgrade);
|
|
825
854
|
|
|
826
855
|
// 前置依赖检测(放在选适配器之后,只检测与所选工具相关的依赖)
|
|
827
856
|
const depProgress = progress('依赖');
|
|
@@ -864,14 +893,14 @@ async function init(targetDir, options = {}) {
|
|
|
864
893
|
}
|
|
865
894
|
// 官方 OpenSpec Skills(openspec/skills/)不部署——工作流入口统一为 /opsx:* 斜杠命令
|
|
866
895
|
//(openspec/commands/ 在下方 Commands 段部署),避免双形态重复
|
|
867
|
-
// worktree 扩展 Skills
|
|
868
|
-
const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc,
|
|
896
|
+
// worktree 扩展 Skills:init / close / push 三件套(单层安装,空间根是唯一安装点)
|
|
897
|
+
const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, WORKTREE_SKILLS);
|
|
869
898
|
if (osExtResult.count > 0) {
|
|
870
899
|
osCount = osExtResult.count;
|
|
871
900
|
osExtResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
872
901
|
}
|
|
873
902
|
// 工具 Skills:仅 cbb-design-to-wiki(设计文档发布属 openspec 设计阶段动作)
|
|
874
|
-
const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc,
|
|
903
|
+
const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, TOOLS_SKILLS);
|
|
875
904
|
if (extResult.count > 0) {
|
|
876
905
|
extCount = extResult.count;
|
|
877
906
|
extResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
@@ -902,14 +931,13 @@ async function init(targetDir, options = {}) {
|
|
|
902
931
|
await detectUserLevelSuperpowers(selectedAdapters, yes);
|
|
903
932
|
}
|
|
904
933
|
|
|
905
|
-
// 安装 Commands(官方 12 个 → /opsx:*;worktree 管理 → /cbb:worktree-*,
|
|
906
|
-
// 仅装 close/push,init 只在 workspace 根使用,由 installWorkspaceConfig 部署)
|
|
934
|
+
// 安装 Commands(官方 12 个 → /opsx:*;worktree 管理 → /cbb:worktree-*,init/close/push 三件套)
|
|
907
935
|
const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
|
|
908
936
|
if (commandsAdapters.length > 0) {
|
|
909
937
|
console.log(`\n ⚡ ${action} Commands...`);
|
|
910
938
|
const commandsBaseSrc = path.join(SHARED_STANDARDS_DIR, 'openspec', 'commands');
|
|
911
939
|
const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
|
|
912
|
-
const cmdFilter = ['worktree-close.md', 'worktree-push.md'];
|
|
940
|
+
const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
|
|
913
941
|
|
|
914
942
|
const cmdDests = [...new Set(commandsAdapters
|
|
915
943
|
.flatMap(a => [a.commandsDir, a.worktreeCommandsDir])
|
|
@@ -948,6 +976,15 @@ async function init(targetDir, options = {}) {
|
|
|
948
976
|
setupOpenSpec(isUpgrade, managedPaths);
|
|
949
977
|
newPaths.add('openspec/config.yaml');
|
|
950
978
|
|
|
979
|
+
// 业务空间根专属:空间导航 AGENTS.md(仅缺失时生成,永不覆盖)+ 旧版导航文案提示
|
|
980
|
+
let agentsMdState = null;
|
|
981
|
+
if (isWorkspaceRoot) {
|
|
982
|
+
agentsMdState = workspaces.ensureWorkspaceAgentsMd(TARGET_DIR);
|
|
983
|
+
if (workspaces.detectLegacyAgentsMd(TARGET_DIR)) {
|
|
984
|
+
log('空间根 AGENTS.md 仍是旧版导航文案(单层安装改造前模板)。建议删除该文件后重跑 cbb update 重新生成(仅缺失时生成,不会覆盖你的增补内容)', 'warn');
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
951
988
|
// 写入自动版本检查 hook
|
|
952
989
|
const hookAdapters = selectedAdapters.filter(a => a.settingsFile && a.hookEvents);
|
|
953
990
|
if (hookAdapters.length > 0) {
|
|
@@ -1015,7 +1052,7 @@ async function init(targetDir, options = {}) {
|
|
|
1015
1052
|
console.log(`\n ✅ ${action}完成!\n`);
|
|
1016
1053
|
|
|
1017
1054
|
if (yes) {
|
|
1018
|
-
console.log(c('dim',
|
|
1055
|
+
console.log(c('dim', ` (使用 --yes 非交互模式,工具:${selectedAdapters.map(a => a.displayName).join('、')})`));
|
|
1019
1056
|
}
|
|
1020
1057
|
|
|
1021
1058
|
// 适配器映射(单行)
|
|
@@ -1045,161 +1082,13 @@ async function init(targetDir, options = {}) {
|
|
|
1045
1082
|
if (hasSkills) extras.push('Skills');
|
|
1046
1083
|
if (hasCommands) extras.push('Commands');
|
|
1047
1084
|
if (hasHooks) extras.push('版本检查 hook');
|
|
1085
|
+
if (agentsMdState === 'created') extras.push('AGENTS.md(空间导航)');
|
|
1048
1086
|
if (extras.length > 0) {
|
|
1049
1087
|
console.log(` ${extras.join(' / ')} 已配置`);
|
|
1050
1088
|
}
|
|
1051
1089
|
console.log('');
|
|
1052
1090
|
}
|
|
1053
1091
|
|
|
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
1092
|
// ── cbb setup 命令 ──────────────────────────────────────
|
|
1204
1093
|
|
|
1205
1094
|
/**
|
|
@@ -1521,9 +1410,9 @@ async function initSingleWorkspace(w, opts = {}) {
|
|
|
1521
1410
|
return { ok: true, subdir: subdirName, upToDate: true };
|
|
1522
1411
|
}
|
|
1523
1412
|
|
|
1524
|
-
// 6.
|
|
1413
|
+
// 6. 安装空间 AI 配置(单层全量:开发配置 + worktree 管理,统一安装器自动识别空间根)
|
|
1525
1414
|
try {
|
|
1526
|
-
await
|
|
1415
|
+
await init(targetDir, { yes: true, selectedAdapters: opts.selectedAdapters });
|
|
1527
1416
|
return { ok: true, subdir: subdirName };
|
|
1528
1417
|
} catch (e) {
|
|
1529
1418
|
step('✗', 'AI 配置', e.message, { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
|
|
@@ -1733,13 +1622,16 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
|
|
|
1733
1622
|
}
|
|
1734
1623
|
|
|
1735
1624
|
/**
|
|
1736
|
-
* cbb update
|
|
1625
|
+
* cbb update:就地升级当前目录内容 + 用户级组件。
|
|
1737
1626
|
*
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1627
|
+
* 单层安装:统一安装器按空间根判定(workspace-config.json + .git 目录)补齐
|
|
1628
|
+
* worktree 管理入口与空间导航;旧子集清单升级时由清单差集机制自动补齐全量内容。
|
|
1740
1629
|
*/
|
|
1741
1630
|
async function updateInstalled() {
|
|
1742
|
-
|
|
1631
|
+
// 用户级组件沿用本目录的工具选择(.last-tools),无记录或为空才回退全量
|
|
1632
|
+
const lastTools = readLastTools();
|
|
1633
|
+
const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
|
|
1634
|
+
await setupUserLevelComponents(reused.length > 0 ? reused : adapters);
|
|
1743
1635
|
|
|
1744
1636
|
const manifest = readManifest();
|
|
1745
1637
|
if (manifest.size === 0) {
|
|
@@ -1747,17 +1639,7 @@ async function updateInstalled() {
|
|
|
1747
1639
|
process.exit(1);
|
|
1748
1640
|
}
|
|
1749
1641
|
|
|
1750
|
-
|
|
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
|
-
}
|
|
1642
|
+
await init(undefined, {});
|
|
1761
1643
|
}
|
|
1762
1644
|
|
|
1763
1645
|
// ── 卸载 ──────────────────────────────────────────────────
|
|
@@ -1957,7 +1839,7 @@ async function uninstall() {
|
|
|
1957
1839
|
|
|
1958
1840
|
// 入口身份:cbb(用户命令) / cbbm(内部命令,由流程、hook、Agent 技能调用)
|
|
1959
1841
|
const ENTRY = process.env.CBB_ENTRY === 'cbbm' ? 'cbbm' : 'cbb';
|
|
1960
|
-
const INTERNAL_COMMANDS = ['
|
|
1842
|
+
const INTERNAL_COMMANDS = ['check-update', 'wiki', 'wp'];
|
|
1961
1843
|
const UPSTREAM_FLAGS = ['--upstream-superpowers', '--upstream-openspec'];
|
|
1962
1844
|
|
|
1963
1845
|
const args = process.argv.slice(2);
|
|
@@ -2045,14 +1927,6 @@ switch (command) {
|
|
|
2045
1927
|
}
|
|
2046
1928
|
break;
|
|
2047
1929
|
}
|
|
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
1930
|
case 'uninstall':
|
|
2057
1931
|
uninstall().catch(err => {
|
|
2058
1932
|
log(err.message, 'error');
|
|
@@ -2092,7 +1966,6 @@ function showHelp() {
|
|
|
2092
1966
|
console.log(`
|
|
2093
1967
|
@jspg-ai/coding-bb v${pkg.version} — cbbm:内部命令(由流程 / hook / Agent 技能调用)
|
|
2094
1968
|
|
|
2095
|
-
cbbm init --yes worktree 沙箱内安装开发配置(/cbb:worktree-init Step 4 自动调用)
|
|
2096
1969
|
cbbm check-update 版本检查(用户级 hook 每日自动触发)
|
|
2097
1970
|
cbbm wiki / cbbm wp Wiki 查询与发布(AI 通过技能调用)
|
|
2098
1971
|
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
46
|
-
5. Step 4
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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 启动失败(可能是
|
|
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
|
|
63
|
+
console.log('🛡️ Qoder Agent 运行环境:移除 worktree 时如遇 IDE 锁,将降级处理');
|
|
64
64
|
} else {
|
|
65
|
-
console.log('✅
|
|
65
|
+
console.log('✅ 系统终端环境');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const summary = {
|