@jspg-ai/coding-bb 0.0.3-beta.24 → 0.0.3-beta.25
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/dev-standards/rules/cbb-ai-behavior.md +2 -2
- package/cbb/lib/install/init.js +23 -21
- package/cbb/lib/install/workspaces.js +15 -15
- package/cbb/lib/utils/githook.js +9 -7
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/_shared/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/_shared/scripts/start-local.js +4 -4
- package/cbb/worktrees/skills/cbb-local-mode/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/skills/cbb-local-mode/scripts/start-local.js +4 -4
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +7 -7
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +3 -3
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/delete-branches.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/discover-apps.js +4 -4
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/safety-check.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +22 -22
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-repos.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-branches.js +3 -3
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +4 -4
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-git-hook.js +1 -1
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/parse-config.js +7 -7
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/push-branches.js +3 -3
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/sync-repos.js +6 -6
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +7 -7
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/commit-worktrees.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/push-worktrees.js +2 -2
- package/config/{workspace-config.sample.json → CBB-WORKSPACE.sample.json} +1 -1
- package/config/workspace-agents.sample.md +4 -44
- package/package.json +1 -1
|
@@ -101,7 +101,7 @@ trigger: always_on
|
|
|
101
101
|
|
|
102
102
|
## 8. 业务空间开发模式(双模式,用户选择制)
|
|
103
103
|
|
|
104
|
-
**仅当工作目录是业务空间根(存在 `
|
|
104
|
+
**仅当工作目录是业务空间根(存在 `CBB-WORKSPACE.json` 且 `.git` 为目录)时生效。空间支持两种开发模式,能力对称(都能修改 workspace 内容与应用代码、都强制分支隔离、交付都走 MR),由用户显式选择,agent 只给推荐不代决。**
|
|
105
105
|
|
|
106
106
|
**模式一 · 本地模式(单线程快改,当前会话就地干):**
|
|
107
107
|
|
|
@@ -117,7 +117,7 @@ trigger: always_on
|
|
|
117
117
|
**基准与防线(两模式共同,ADR 0002):**
|
|
118
118
|
|
|
119
119
|
- **基准 = `origin/main`(唯一真相源);`.codespace/<app>/` 的检出 = 视图**——本地模式可暂态被需求分支占用,收尾恢复
|
|
120
|
-
- **严禁直接在任何 main 检出上提交载荷**:分支感知 pre-commit hook 机械拦截(workspace 仓库仅放行控制面白名单:`
|
|
120
|
+
- **严禁直接在任何 main 检出上提交载荷**:分支感知 pre-commit hook 机械拦截(workspace 仓库仅放行控制面白名单:`CBB-WORKSPACE.json` / `.gitignore` / `AGENTS.md` / `openspec/config.yaml` / `openspec/schemas/**`;应用仓库 main 全拦)——**被拦是防线生效,不是故障**;`--no-verify` 绕过仅限用户显式要求
|
|
121
121
|
- 远端推送唯一出口 = `cbb-worktree-push`(init 创建流程零远端操作,lazy push;空分支尸体不存在)
|
|
122
122
|
- **模式误判零代价**:本地模式中途要并行/隔离 → 重跑 `cbb-worktree-init`(分支已存在自动复用,提交随身带走)
|
|
123
123
|
|
package/cbb/lib/install/init.js
CHANGED
|
@@ -152,13 +152,15 @@ function detectExistingInstall() {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
* 判定目标目录是否为业务空间根:workspace-config.json
|
|
156
|
-
*
|
|
155
|
+
* 判定目标目录是否为业务空间根:CBB-WORKSPACE.json(或旧名 workspace-config.json)存在且 .git 是目录。
|
|
156
|
+
* 需求工作树内也有该文件(tracked 副本),但其 .git 是文件(gitdir 指针),
|
|
157
157
|
* 必须以 .git 形态排除——避免「在 worktree 里误跑 cbb update」被当作空间根
|
|
158
158
|
* (误装 worktree 管理入口、在错误位置报旧导航提示)。
|
|
159
|
+
* 双名识别用于改名过渡期:存量空间(旧名)跑 update 时仍能识别并装上新版 hook。
|
|
159
160
|
*/
|
|
160
161
|
function detectWorkspaceRoot() {
|
|
161
|
-
if (!fs.existsSync(path.join(TARGET_DIR, '
|
|
162
|
+
if (!fs.existsSync(path.join(TARGET_DIR, 'CBB-WORKSPACE.json'))
|
|
163
|
+
&& !fs.existsSync(path.join(TARGET_DIR, 'workspace-config.json'))) return false;
|
|
162
164
|
const gitPath = path.join(TARGET_DIR, '.git');
|
|
163
165
|
return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
|
|
164
166
|
}
|
|
@@ -1332,12 +1334,12 @@ function decideUpgradeIfInstalled(targetDir, selectedAdapters) {
|
|
|
1332
1334
|
}
|
|
1333
1335
|
|
|
1334
1336
|
/**
|
|
1335
|
-
* 检查业务空间根目录下是否有
|
|
1336
|
-
* 不存在时,将包内样例文件复制为
|
|
1337
|
+
* 检查业务空间根目录下是否有 CBB-WORKSPACE.json(init-worktree 技能依赖)。
|
|
1338
|
+
* 不存在时,将包内样例文件复制为 CBB-WORKSPACE.json。
|
|
1337
1339
|
* 返回 'exists' | 'created'(不打印,由调用方汇总输出)。
|
|
1338
1340
|
*/
|
|
1339
1341
|
function ensureWorkspaceConfig(targetDir) {
|
|
1340
|
-
const configPath = path.join(targetDir, '
|
|
1342
|
+
const configPath = path.join(targetDir, 'CBB-WORKSPACE.json');
|
|
1341
1343
|
if (fs.existsSync(configPath)) return 'exists';
|
|
1342
1344
|
|
|
1343
1345
|
const samplePath = path.join(SHARED_STANDARDS_DIR, workspaces.SAMPLE_CONFIG);
|
|
@@ -1346,12 +1348,12 @@ function ensureWorkspaceConfig(targetDir) {
|
|
|
1346
1348
|
}
|
|
1347
1349
|
|
|
1348
1350
|
/**
|
|
1349
|
-
* 读取
|
|
1351
|
+
* 读取 CBB-WORKSPACE.json 中的应用列表,将关联应用代码 clone/fetch 到 .codespace/ 目录。
|
|
1350
1352
|
* 首次 clone(耗时较长),后续 fetch --all(秒级更新)。
|
|
1351
1353
|
* 这是基准代码,init-worktree 技能后续只做轻量 fetch 检查。
|
|
1352
1354
|
*/
|
|
1353
1355
|
/**
|
|
1354
|
-
* 读取
|
|
1356
|
+
* 读取 CBB-WORKSPACE.json,将关联应用代码 clone/fetch 到 .codespace/ 目录。
|
|
1355
1357
|
* 不打印逐条日志,返回汇总对象由调用方渲染:
|
|
1356
1358
|
* { ok, fail, updated[], cloned[], fetchFailed[], sampleApps[], invalid[] }
|
|
1357
1359
|
* 长耗时(首次 clone),通过 onProgress 回调实时提示当前正在处理哪个应用。
|
|
@@ -1360,7 +1362,7 @@ function ensureWorkspaceConfig(targetDir) {
|
|
|
1360
1362
|
*/
|
|
1361
1363
|
async function syncAppRepos(targetDir, onProgress = () => {}) {
|
|
1362
1364
|
const summary = { ok: 0, fail: 0, updated: [], cloned: [], fetchFailed: [], sampleApps: [], invalid: [] };
|
|
1363
|
-
const configPath = path.join(targetDir, '
|
|
1365
|
+
const configPath = path.join(targetDir, 'CBB-WORKSPACE.json');
|
|
1364
1366
|
if (!fs.existsSync(configPath)) return summary;
|
|
1365
1367
|
|
|
1366
1368
|
let config;
|
|
@@ -1514,7 +1516,7 @@ async function syncWorkspaceRepo(targetDir, onProgress = () => {}) {
|
|
|
1514
1516
|
}
|
|
1515
1517
|
|
|
1516
1518
|
/**
|
|
1517
|
-
* 处理单个业务空间:计算子目录 → 检测/克隆 →
|
|
1519
|
+
* 处理单个业务空间:计算子目录 → 检测/克隆 → CBB-WORKSPACE.json → 拉取应用代码 → 升级询问 → 安装 AI 配置。
|
|
1518
1520
|
* 输出为紧凑步进行(✓ 标签 值),失败时不抛出也不退出,返回结果对象供调用方汇总。
|
|
1519
1521
|
* @param {object} [opts] - { selectedAdapters } cbb setup 上层选定的适配器(透传给空间级安装)
|
|
1520
1522
|
* @returns {Promise<{ok: boolean, error?: string, message?: string, subdir?: string, skipped?: boolean}>}
|
|
@@ -1576,28 +1578,28 @@ async function initSingleWorkspace(w, opts = {}) {
|
|
|
1576
1578
|
step('✓', '仓库', `已克隆 ${subdirName}/`);
|
|
1577
1579
|
}
|
|
1578
1580
|
|
|
1579
|
-
// 3.
|
|
1581
|
+
// 3. CBB-WORKSPACE.json:缺失则从样例生成,随后校验格式
|
|
1580
1582
|
const cfgState = ensureWorkspaceConfig(targetDir);
|
|
1581
|
-
const configPath = path.join(targetDir, '
|
|
1583
|
+
const configPath = path.join(targetDir, 'CBB-WORKSPACE.json');
|
|
1582
1584
|
const configValidation = workspaces.validateWorkspaceConfig(configPath);
|
|
1583
1585
|
if (!configValidation.valid) {
|
|
1584
|
-
step('✗', '配置', `
|
|
1586
|
+
step('✗', '配置', `CBB-WORKSPACE.json 校验失败:${configValidation.message}`,
|
|
1585
1587
|
{ iconColor: 'red', labelColor: 'red', valueColor: 'red' });
|
|
1586
|
-
// 撤销本次克隆,使工作空间回到初始状态,用户配好
|
|
1588
|
+
// 撤销本次克隆,使工作空间回到初始状态,用户配好 CBB-WORKSPACE.json 后可重新执行
|
|
1587
1589
|
if (wasCloned && fs.existsSync(targetDir)) {
|
|
1588
1590
|
removePath(targetDir);
|
|
1589
|
-
log(`已撤销本次克隆(${subdirName}/),请配好
|
|
1591
|
+
log(`已撤销本次克隆(${subdirName}/),请配好 CBB-WORKSPACE.json 后重新执行 cbb setup`, 'warn');
|
|
1590
1592
|
} else {
|
|
1591
|
-
log('请修正
|
|
1593
|
+
log('请修正 CBB-WORKSPACE.json 后重新执行 cbb setup', 'warn');
|
|
1592
1594
|
}
|
|
1593
1595
|
log(`参考格式:${path.join(SHARED_STANDARDS_DIR, workspaces.SAMPLE_CONFIG)}`, 'info');
|
|
1594
1596
|
return { ok: false, error: 'CONFIG_INVALID', message: configValidation.message };
|
|
1595
1597
|
}
|
|
1596
1598
|
if (cfgState === 'created') {
|
|
1597
|
-
step('!', '配置', '
|
|
1599
|
+
step('!', '配置', 'CBB-WORKSPACE.json 已从样例生成,请编辑 apps 后重跑 cbb setup',
|
|
1598
1600
|
{ iconColor: 'yellow', valueColor: 'yellow' });
|
|
1599
1601
|
} else {
|
|
1600
|
-
step('✓', '配置', `
|
|
1602
|
+
step('✓', '配置', `CBB-WORKSPACE.json(${configValidation.config.apps.length} 个应用)`);
|
|
1601
1603
|
}
|
|
1602
1604
|
|
|
1603
1605
|
// 4. 拉取关联应用代码到 .codespace/(基准代码,首次 clone 较慢,实时提示当前应用)
|
|
@@ -1615,7 +1617,7 @@ async function initSingleWorkspace(w, opts = {}) {
|
|
|
1615
1617
|
step(appWarn ? '!' : '✓', '应用', appParts.join(' · '), { iconColor: appWarn ? 'yellow' : 'green' });
|
|
1616
1618
|
if (apps.sampleApps.length > 0) {
|
|
1617
1619
|
const names = apps.sampleApps.map(a => a.name).join('、');
|
|
1618
|
-
console.log(` ${c('gray', `${names} → 编辑
|
|
1620
|
+
console.log(` ${c('gray', `${names} → 编辑 CBB-WORKSPACE.json 填入真实仓库地址`)}`);
|
|
1619
1621
|
}
|
|
1620
1622
|
|
|
1621
1623
|
// 5. 若已安装:版本一致且无新增工具才跳过;否则继续安装/升级(setup 已整体确认过,不再二次询问)
|
|
@@ -1641,7 +1643,7 @@ function describeBatchError(r) {
|
|
|
1641
1643
|
case 'DIR_MISMATCH': return '子目录已存在但不是该业务空间的仓库';
|
|
1642
1644
|
case 'CLONE_FAILED': return `git clone 失败:${r.message || ''}`.trim();
|
|
1643
1645
|
case 'PULL_FAILED': return `工作空间仓库同步失败:${r.message || ''}`.trim();
|
|
1644
|
-
case 'CONFIG_INVALID': return `
|
|
1646
|
+
case 'CONFIG_INVALID': return `CBB-WORKSPACE.json 不合法:${r.message || ''}`.trim();
|
|
1645
1647
|
case 'INIT_FAILED': return `初始化失败:${r.message || ''}`.trim();
|
|
1646
1648
|
default: return r.error || '未知错误';
|
|
1647
1649
|
}
|
|
@@ -1839,7 +1841,7 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
|
|
|
1839
1841
|
/**
|
|
1840
1842
|
* cbb update:就地升级当前目录内容 + 用户级组件。
|
|
1841
1843
|
*
|
|
1842
|
-
* 单层安装:统一安装器按空间根判定(
|
|
1844
|
+
* 单层安装:统一安装器按空间根判定(CBB-WORKSPACE.json + .git 目录)补齐
|
|
1843
1845
|
* worktree 管理入口与空间导航;旧子集清单升级时由清单差集机制自动补齐全量内容。
|
|
1844
1846
|
*/
|
|
1845
1847
|
async function updateInstalled() {
|
|
@@ -16,7 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const { execSync } = require('child_process');
|
|
17
17
|
|
|
18
18
|
const DEFAULT_CONFIG = 'config/workspaces.json';
|
|
19
|
-
const SAMPLE_CONFIG = 'config/
|
|
19
|
+
const SAMPLE_CONFIG = 'config/CBB-WORKSPACE.sample.json';
|
|
20
20
|
const SAMPLE_AGENTS_MD = 'config/workspace-agents.sample.md';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -104,7 +104,7 @@ function loadWorkspaces(configPath = DEFAULT_CONFIG) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* 校验业务工作空间
|
|
107
|
+
* 校验业务工作空间 CBB-WORKSPACE.json 的格式是否合法。
|
|
108
108
|
*
|
|
109
109
|
* 校验项:
|
|
110
110
|
* - 文件存在且为合法 JSON
|
|
@@ -113,31 +113,31 @@ function loadWorkspaces(configPath = DEFAULT_CONFIG) {
|
|
|
113
113
|
* - name、repo 为非空字符串;side 必须为 'front'、'back' 或 'mixed'
|
|
114
114
|
* - desc 为可选字段;一旦出现必须是非空字符串(应用职责描述)
|
|
115
115
|
*
|
|
116
|
-
* @param {string} configPath
|
|
116
|
+
* @param {string} configPath CBB-WORKSPACE.json 的绝对路径
|
|
117
117
|
* @returns {{ valid: boolean, message?: string, config?: object }}
|
|
118
118
|
*/
|
|
119
119
|
function validateWorkspaceConfig(configPath) {
|
|
120
120
|
if (!fs.existsSync(configPath)) {
|
|
121
|
-
return { valid: false, message: `
|
|
121
|
+
return { valid: false, message: `CBB-WORKSPACE.json 不存在:${configPath}` };
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
let raw;
|
|
125
125
|
try {
|
|
126
126
|
raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
127
127
|
} catch (e) {
|
|
128
|
-
return { valid: false, message: `
|
|
128
|
+
return { valid: false, message: `CBB-WORKSPACE.json 不是合法 JSON:${e.message}` };
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (!raw || typeof raw !== 'object') {
|
|
132
|
-
return { valid: false, message: '
|
|
132
|
+
return { valid: false, message: 'CBB-WORKSPACE.json 内容应为 JSON 对象' };
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (!Array.isArray(raw.apps)) {
|
|
136
|
-
return { valid: false, message: '
|
|
136
|
+
return { valid: false, message: 'CBB-WORKSPACE.json 缺少 apps 数组' };
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (raw.apps.length === 0) {
|
|
140
|
-
return { valid: false, message: '
|
|
140
|
+
return { valid: false, message: 'CBB-WORKSPACE.json 中 apps 数组为空,请至少配置一个关联应用' };
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const VALID_SIDES = ['front', 'back', 'mixed'];
|
|
@@ -146,22 +146,22 @@ function validateWorkspaceConfig(configPath) {
|
|
|
146
146
|
const label = `第 ${i + 1} 项`;
|
|
147
147
|
|
|
148
148
|
if (!app || typeof app !== 'object') {
|
|
149
|
-
return { valid: false, message: `
|
|
149
|
+
return { valid: false, message: `CBB-WORKSPACE.json ${label}不是对象` };
|
|
150
150
|
}
|
|
151
151
|
if (typeof app.name !== 'string' || !app.name.trim()) {
|
|
152
|
-
return { valid: false, message: `
|
|
152
|
+
return { valid: false, message: `CBB-WORKSPACE.json ${label}缺少 name(应用名)` };
|
|
153
153
|
}
|
|
154
154
|
if (typeof app.repo !== 'string' || !app.repo.trim()) {
|
|
155
|
-
return { valid: false, message: `
|
|
155
|
+
return { valid: false, message: `CBB-WORKSPACE.json "${app.name}" 缺少 repo(Git 仓库地址)` };
|
|
156
156
|
}
|
|
157
157
|
if (typeof app.side !== 'string' || !app.side.trim()) {
|
|
158
|
-
return { valid: false, message: `
|
|
158
|
+
return { valid: false, message: `CBB-WORKSPACE.json "${app.name}" 缺少 side(仓库类型,可选值:front / back / mixed)` };
|
|
159
159
|
}
|
|
160
160
|
if (!VALID_SIDES.includes(app.side.trim())) {
|
|
161
|
-
return { valid: false, message: `
|
|
161
|
+
return { valid: false, message: `CBB-WORKSPACE.json "${app.name}" 的 side 字段值无效:"${app.side}"(可选值:front / back / mixed)` };
|
|
162
162
|
}
|
|
163
163
|
if (app.desc !== undefined && (typeof app.desc !== 'string' || !app.desc.trim())) {
|
|
164
|
-
return { valid: false, message: `
|
|
164
|
+
return { valid: false, message: `CBB-WORKSPACE.json "${app.name}" 的 desc 字段应为非空字符串(该应用的职责描述),不需要可整条删除` };
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -272,7 +272,7 @@ function extractRepoBasename(url) {
|
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
274
|
* 判断 repo 是否为样例占位地址(RFC 2606 保留域名)。
|
|
275
|
-
* 样例
|
|
275
|
+
* 样例 CBB-WORKSPACE.json 的 repo 用 example.com 等占位,clone 必然失败;
|
|
276
276
|
* setup 据此逐条跳过空跑并给出明确编辑提示。
|
|
277
277
|
* 支持 SSH(git@host:path)与 URL(ssh://、https://、git://)两种形式的 host 提取。
|
|
278
278
|
*
|
package/cbb/lib/utils/githook.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 策略(对当前仓库生效,worktree 与主仓库共享 .git/hooks 故一并覆盖):
|
|
7
7
|
* - 当前分支非主干(含 detached HEAD)→ 一律放行(两模式开发态都在需求分支上)
|
|
8
|
-
* - workspace 仓库(检出含
|
|
9
|
-
*
|
|
10
|
-
* openspec/schemas/** / .cbb/**(配置变更与 cbb update
|
|
8
|
+
* - workspace 仓库(检出含 CBB-WORKSPACE.json)在主干上 → 只放行控制面白名单路径:
|
|
9
|
+
* CBB-WORKSPACE.json / .gitignore / AGENTS.md / openspec/config.yaml /
|
|
10
|
+
* openspec/schemas/** / .cbb/**(配置变更与 cbb update 部署物,直改即推);
|
|
11
|
+
* 另放行旧名 workspace-config.json 的删除条目(仅用于一次性更名迁移)
|
|
11
12
|
* - 应用仓库在主干上 → 全拦(交付只走 MR)
|
|
12
13
|
* - 逃生口:用户显式 git commit --no-verify(AI 侧纪律禁止自动绕过)
|
|
13
14
|
*
|
|
@@ -60,20 +61,21 @@ const PRE_COMMIT_SOURCE = [
|
|
|
60
61
|
"const files = staged.split(/\\r?\\n/).filter(Boolean);",
|
|
61
62
|
'if (files.length === 0) process.exit(0);',
|
|
62
63
|
'',
|
|
63
|
-
'// 仓库类型:workspace 仓库 = 检出含
|
|
64
|
-
"const isWorkspace = fs.existsSync('workspace-config.json');",
|
|
64
|
+
'// 仓库类型:workspace 仓库 = 检出含 CBB-WORKSPACE.json(或旧名,改名过渡期识别)',
|
|
65
|
+
"const isWorkspace = fs.existsSync('CBB-WORKSPACE.json') || fs.existsSync('workspace-config.json');",
|
|
65
66
|
'',
|
|
66
67
|
'function deny(bad) {',
|
|
67
68
|
" console.error('[cbb-guard] 主干上拦截载荷提交(ADR 0002):');",
|
|
68
69
|
" bad.forEach(f => console.error(' ' + f));",
|
|
69
70
|
" console.error('需求载荷须在需求分支上提交:本地模式先 git checkout -b <需求名>;worktree 模式在 .worktrees/ 工作树内提交。');",
|
|
70
|
-
" console.error('控制面白名单:
|
|
71
|
+
" console.error('控制面白名单:CBB-WORKSPACE.json / .gitignore / AGENTS.md / openspec/config.yaml / openspec/schemas/** / .cbb/**');",
|
|
71
72
|
" console.error('如确需在主干提交,须由用户显式 git commit --no-verify。');",
|
|
72
73
|
' process.exit(1);',
|
|
73
74
|
'}',
|
|
74
75
|
'',
|
|
75
76
|
'if (isWorkspace) {',
|
|
76
|
-
|
|
77
|
+
' // 旧名仅放行删除条目:更名迁移提交(git mv)会产生旧名删除 + 新名新增两条',
|
|
78
|
+
" const EXACT = new Set(['CBB-WORKSPACE.json', 'workspace-config.json', '.gitignore', 'AGENTS.md', 'openspec/config.yaml']);",
|
|
77
79
|
" const PREFIXES = ['openspec/schemas/', '.cbb/'];",
|
|
78
80
|
' const bad = files.filter(f => !EXACT.has(f) && !PREFIXES.some(p => f.indexOf(p) === 0));',
|
|
79
81
|
' if (bad.length) deny(bad);',
|
|
@@ -5,8 +5,8 @@ require('./silence-popup');
|
|
|
5
5
|
* 唯一真源:cbb/worktrees/_shared/scripts/find-workspace-root.js
|
|
6
6
|
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init / extend / close / push)。
|
|
7
7
|
*
|
|
8
|
-
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内
|
|
9
|
-
* 兜底:当前目录含
|
|
8
|
+
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内 CBB-WORKSPACE.json 副本干扰)
|
|
9
|
+
* 兜底:当前目录含 CBB-WORKSPACE.json
|
|
10
10
|
*
|
|
11
11
|
* 退出码:
|
|
12
12
|
* 0 - 定位成功,输出 WS_ROOT 绝对路径
|
|
@@ -44,19 +44,19 @@ if (worktreeList.ok) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// 兜底:当前目录含
|
|
47
|
+
// 兜底:当前目录含 CBB-WORKSPACE.json
|
|
48
48
|
if (!wsRoot) {
|
|
49
49
|
const cwd = process.cwd();
|
|
50
|
-
if (fs.existsSync(path.join(cwd, '
|
|
50
|
+
if (fs.existsSync(path.join(cwd, 'CBB-WORKSPACE.json'))) {
|
|
51
51
|
wsRoot = cwd;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// 校验
|
|
56
|
-
if (!wsRoot || !fs.existsSync(path.join(wsRoot, '
|
|
56
|
+
if (!wsRoot || !fs.existsSync(path.join(wsRoot, 'CBB-WORKSPACE.json'))) {
|
|
57
57
|
console.error('❌ 未找到工作空间根目录');
|
|
58
|
-
console.error(' 原因:git worktree list 失败且当前目录无
|
|
59
|
-
console.error(' 请在业务工作空间根目录(由 cbb setup 初始化、包含
|
|
58
|
+
console.error(' 原因:git worktree list 失败且当前目录无 CBB-WORKSPACE.json');
|
|
59
|
+
console.error(' 请在业务工作空间根目录(由 cbb setup 初始化、包含 CBB-WORKSPACE.json)下执行本技能');
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -20,7 +20,7 @@ const HOOK_FILE = 'pre-commit';
|
|
|
20
20
|
const BACKUP_SUFFIX = '.cbb-backup';
|
|
21
21
|
|
|
22
22
|
// 与 cbb/lib/utils/githook.js 的 buildPreCommitSource() 输出完全一致(构建时注入)
|
|
23
|
-
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace 仓库只放行控制面白名单,应用仓库全拦;非 main 一律放行。\n// 绕过逃生口仅限用户显式 git commit --no-verify。\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nfunction run(cmd) {\n try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }\n catch (e) { return null; }\n}\nconst branch = run('git branch --show-current') || '';\n// 主干名:优先远端 HEAD 指向;探测失败按本地 main/master 存在性回退\nlet main = null;\nconst remotes = run('git remote');\nif (remotes) {\n const r = remotes.split(/\\r?\\n/)[0];\n const sym = run('git symbolic-ref \"refs/remotes/' + r + '/HEAD\"');\n if (sym) main = sym.replace('refs/remotes/' + r + '/', '');\n}\nif (!main) {\n if (run('git rev-parse --verify --quiet refs/heads/main')) main = 'main';\n else if (run('git rev-parse --verify --quiet refs/heads/master')) main = 'master';\n}\n// 非主干(含 detached / 探测失败)→ 放行\nif (!main || branch !== main) process.exit(0);\n\n// 主干上的提交:收集 staged 变更(空提交放行)\nconst staged = run('git diff --cached --name-only') || '';\nconst files = staged.split(/\\r?\\n/).filter(Boolean);\nif (files.length === 0) process.exit(0);\n\n// 仓库类型:workspace 仓库 = 检出含
|
|
23
|
+
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace 仓库只放行控制面白名单,应用仓库全拦;非 main 一律放行。\n// 绕过逃生口仅限用户显式 git commit --no-verify。\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nfunction run(cmd) {\n try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }\n catch (e) { return null; }\n}\nconst branch = run('git branch --show-current') || '';\n// 主干名:优先远端 HEAD 指向;探测失败按本地 main/master 存在性回退\nlet main = null;\nconst remotes = run('git remote');\nif (remotes) {\n const r = remotes.split(/\\r?\\n/)[0];\n const sym = run('git symbolic-ref \"refs/remotes/' + r + '/HEAD\"');\n if (sym) main = sym.replace('refs/remotes/' + r + '/', '');\n}\nif (!main) {\n if (run('git rev-parse --verify --quiet refs/heads/main')) main = 'main';\n else if (run('git rev-parse --verify --quiet refs/heads/master')) main = 'master';\n}\n// 非主干(含 detached / 探测失败)→ 放行\nif (!main || branch !== main) process.exit(0);\n\n// 主干上的提交:收集 staged 变更(空提交放行)\nconst staged = run('git diff --cached --name-only') || '';\nconst files = staged.split(/\\r?\\n/).filter(Boolean);\nif (files.length === 0) process.exit(0);\n\n// 仓库类型:workspace 仓库 = 检出含 CBB-WORKSPACE.json(或旧名,改名过渡期识别)\nconst isWorkspace = fs.existsSync('CBB-WORKSPACE.json') || fs.existsSync('workspace-config.json');\n\nfunction deny(bad) {\n console.error('[cbb-guard] 主干上拦截载荷提交(ADR 0002):');\n bad.forEach(f => console.error(' ' + f));\n console.error('需求载荷须在需求分支上提交:本地模式先 git checkout -b <需求名>;worktree 模式在 .worktrees/ 工作树内提交。');\n console.error('控制面白名单:CBB-WORKSPACE.json / .gitignore / AGENTS.md / openspec/config.yaml / openspec/schemas/** / .cbb/**');\n console.error('如确需在主干提交,须由用户显式 git commit --no-verify。');\n process.exit(1);\n}\n\nif (isWorkspace) {\n // 旧名仅放行删除条目:更名迁移提交(git mv)会产生旧名删除 + 新名新增两条\n const EXACT = new Set(['CBB-WORKSPACE.json', 'workspace-config.json', '.gitignore', 'AGENTS.md', 'openspec/config.yaml']);\n const PREFIXES = ['openspec/schemas/', '.cbb/'];\n const bad = files.filter(f => !EXACT.has(f) && !PREFIXES.some(p => f.indexOf(p) === 0));\n if (bad.length) deny(bad);\n} else {\n deny(files);\n}\nprocess.exit(0);\n";
|
|
24
24
|
|
|
25
25
|
function isCbbHookContent(content) {
|
|
26
26
|
return typeof content === 'string' && content.includes(HOOK_MARKER);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 退出码 2(agent 应升级为用户决策:继续 / 收尾 / 升级 / 丢弃,不在本脚本内处理)
|
|
10
10
|
* 2. 参与仓库 = workspace + 应用子集:
|
|
11
11
|
* - --apps a,b → 指定应用(本地模式推荐:从需求语境提取涉及应用)
|
|
12
|
-
* - --all-apps →
|
|
12
|
+
* - --all-apps → CBB-WORKSPACE.json 全量应用
|
|
13
13
|
* - 缺省 → 仅 workspace(后续重跑幂等补齐,与 worktree-init 增补语义一致)
|
|
14
14
|
* 3. 每个仓库:
|
|
15
15
|
* - 当前已在 <branch> → reused
|
|
@@ -108,10 +108,10 @@ if (argApps >= 0) {
|
|
|
108
108
|
appNames = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
109
109
|
} else if (argAll) {
|
|
110
110
|
try {
|
|
111
|
-
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, '
|
|
111
|
+
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, 'CBB-WORKSPACE.json'), 'utf-8'));
|
|
112
112
|
appNames = (config.apps || []).map(a => a.name);
|
|
113
113
|
} catch (e) {
|
|
114
|
-
console.error('❌ 读取
|
|
114
|
+
console.error('❌ 读取 CBB-WORKSPACE.json 失败:' + e.message);
|
|
115
115
|
process.exit(2);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -120,7 +120,7 @@ const targets = [{ name: 'workspace', dir: wsRoot }];
|
|
|
120
120
|
for (const name of appNames) {
|
|
121
121
|
const appDir = path.join(wsRoot, '.codespace', name);
|
|
122
122
|
if (!fs.existsSync(path.join(appDir, '.git'))) {
|
|
123
|
-
console.error('❌ 应用仓库不存在:.codespace/' + name + '(请检查
|
|
123
|
+
console.error('❌ 应用仓库不存在:.codespace/' + name + '(请检查 CBB-WORKSPACE.json 或先跑 cbb setup)');
|
|
124
124
|
process.exit(2);
|
|
125
125
|
}
|
|
126
126
|
targets.push({ name, dir: appDir });
|
|
@@ -20,7 +20,7 @@ const HOOK_FILE = 'pre-commit';
|
|
|
20
20
|
const BACKUP_SUFFIX = '.cbb-backup';
|
|
21
21
|
|
|
22
22
|
// 与 cbb/lib/utils/githook.js 的 buildPreCommitSource() 输出完全一致(构建时注入)
|
|
23
|
-
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace 仓库只放行控制面白名单,应用仓库全拦;非 main 一律放行。\n// 绕过逃生口仅限用户显式 git commit --no-verify。\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nfunction run(cmd) {\n try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }\n catch (e) { return null; }\n}\nconst branch = run('git branch --show-current') || '';\n// 主干名:优先远端 HEAD 指向;探测失败按本地 main/master 存在性回退\nlet main = null;\nconst remotes = run('git remote');\nif (remotes) {\n const r = remotes.split(/\\r?\\n/)[0];\n const sym = run('git symbolic-ref \"refs/remotes/' + r + '/HEAD\"');\n if (sym) main = sym.replace('refs/remotes/' + r + '/', '');\n}\nif (!main) {\n if (run('git rev-parse --verify --quiet refs/heads/main')) main = 'main';\n else if (run('git rev-parse --verify --quiet refs/heads/master')) main = 'master';\n}\n// 非主干(含 detached / 探测失败)→ 放行\nif (!main || branch !== main) process.exit(0);\n\n// 主干上的提交:收集 staged 变更(空提交放行)\nconst staged = run('git diff --cached --name-only') || '';\nconst files = staged.split(/\\r?\\n/).filter(Boolean);\nif (files.length === 0) process.exit(0);\n\n// 仓库类型:workspace 仓库 = 检出含
|
|
23
|
+
const PRE_COMMIT_SOURCE = "#!/usr/bin/env node\n// managed-by-cbb v1 —— 分支感知提交防线(ADR 0002)\n// main 上:workspace 仓库只放行控制面白名单,应用仓库全拦;非 main 一律放行。\n// 绕过逃生口仅限用户显式 git commit --no-verify。\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nfunction run(cmd) {\n try { return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }\n catch (e) { return null; }\n}\nconst branch = run('git branch --show-current') || '';\n// 主干名:优先远端 HEAD 指向;探测失败按本地 main/master 存在性回退\nlet main = null;\nconst remotes = run('git remote');\nif (remotes) {\n const r = remotes.split(/\\r?\\n/)[0];\n const sym = run('git symbolic-ref \"refs/remotes/' + r + '/HEAD\"');\n if (sym) main = sym.replace('refs/remotes/' + r + '/', '');\n}\nif (!main) {\n if (run('git rev-parse --verify --quiet refs/heads/main')) main = 'main';\n else if (run('git rev-parse --verify --quiet refs/heads/master')) main = 'master';\n}\n// 非主干(含 detached / 探测失败)→ 放行\nif (!main || branch !== main) process.exit(0);\n\n// 主干上的提交:收集 staged 变更(空提交放行)\nconst staged = run('git diff --cached --name-only') || '';\nconst files = staged.split(/\\r?\\n/).filter(Boolean);\nif (files.length === 0) process.exit(0);\n\n// 仓库类型:workspace 仓库 = 检出含 CBB-WORKSPACE.json(或旧名,改名过渡期识别)\nconst isWorkspace = fs.existsSync('CBB-WORKSPACE.json') || fs.existsSync('workspace-config.json');\n\nfunction deny(bad) {\n console.error('[cbb-guard] 主干上拦截载荷提交(ADR 0002):');\n bad.forEach(f => console.error(' ' + f));\n console.error('需求载荷须在需求分支上提交:本地模式先 git checkout -b <需求名>;worktree 模式在 .worktrees/ 工作树内提交。');\n console.error('控制面白名单:CBB-WORKSPACE.json / .gitignore / AGENTS.md / openspec/config.yaml / openspec/schemas/** / .cbb/**');\n console.error('如确需在主干提交,须由用户显式 git commit --no-verify。');\n process.exit(1);\n}\n\nif (isWorkspace) {\n // 旧名仅放行删除条目:更名迁移提交(git mv)会产生旧名删除 + 新名新增两条\n const EXACT = new Set(['CBB-WORKSPACE.json', 'workspace-config.json', '.gitignore', 'AGENTS.md', 'openspec/config.yaml']);\n const PREFIXES = ['openspec/schemas/', '.cbb/'];\n const bad = files.filter(f => !EXACT.has(f) && !PREFIXES.some(p => f.indexOf(p) === 0));\n if (bad.length) deny(bad);\n} else {\n deny(files);\n}\nprocess.exit(0);\n";
|
|
24
24
|
|
|
25
25
|
function isCbbHookContent(content) {
|
|
26
26
|
return typeof content === 'string' && content.includes(HOOK_MARKER);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 退出码 2(agent 应升级为用户决策:继续 / 收尾 / 升级 / 丢弃,不在本脚本内处理)
|
|
10
10
|
* 2. 参与仓库 = workspace + 应用子集:
|
|
11
11
|
* - --apps a,b → 指定应用(本地模式推荐:从需求语境提取涉及应用)
|
|
12
|
-
* - --all-apps →
|
|
12
|
+
* - --all-apps → CBB-WORKSPACE.json 全量应用
|
|
13
13
|
* - 缺省 → 仅 workspace(后续重跑幂等补齐,与 worktree-init 增补语义一致)
|
|
14
14
|
* 3. 每个仓库:
|
|
15
15
|
* - 当前已在 <branch> → reused
|
|
@@ -108,10 +108,10 @@ if (argApps >= 0) {
|
|
|
108
108
|
appNames = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
109
109
|
} else if (argAll) {
|
|
110
110
|
try {
|
|
111
|
-
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, '
|
|
111
|
+
const config = JSON.parse(fs.readFileSync(path.join(wsRoot, 'CBB-WORKSPACE.json'), 'utf-8'));
|
|
112
112
|
appNames = (config.apps || []).map(a => a.name);
|
|
113
113
|
} catch (e) {
|
|
114
|
-
console.error('❌ 读取
|
|
114
|
+
console.error('❌ 读取 CBB-WORKSPACE.json 失败:' + e.message);
|
|
115
115
|
process.exit(2);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -120,7 +120,7 @@ const targets = [{ name: 'workspace', dir: wsRoot }];
|
|
|
120
120
|
for (const name of appNames) {
|
|
121
121
|
const appDir = path.join(wsRoot, '.codespace', name);
|
|
122
122
|
if (!fs.existsSync(path.join(appDir, '.git'))) {
|
|
123
|
-
console.error('❌ 应用仓库不存在:.codespace/' + name + '(请检查
|
|
123
|
+
console.error('❌ 应用仓库不存在:.codespace/' + name + '(请检查 CBB-WORKSPACE.json 或先跑 cbb setup)');
|
|
124
124
|
process.exit(2);
|
|
125
125
|
}
|
|
126
126
|
targets.push({ name, dir: appDir });
|
|
@@ -12,10 +12,10 @@ description: "关闭/清理/删除需求工作空间的 worktree 和分支。触
|
|
|
12
12
|
## 核心原则
|
|
13
13
|
|
|
14
14
|
- 本技能的所有原子操作封装为 `scripts/*.js` Node.js 脚本,**跨平台执行**
|
|
15
|
-
- 本技能是 `cbb-worktree-init` 的逆操作:共用 `
|
|
15
|
+
- 本技能是 `cbb-worktree-init` 的逆操作:共用 `CBB-WORKSPACE.json`、共用 `.worktrees/worktree-<需求名>/` 物理直聚命名、共用统一的分支名(不加前缀)
|
|
16
16
|
- App worktree **物理嵌套**在 workspace worktree 的 `.codespace/` 容器内(`.worktrees/worktree-<需求名>/.codespace/<应用名>/`,镜像空间根布局,ADR 0003),但 git 上属于各自仓库的独立 worktree
|
|
17
17
|
- 只操作 `.worktrees/worktree-<需求名>` 这一类精确路径,**绝不碰其它 worktree**(如 IDE/工具自动创建的 `~/.qoder/worktree/...` 会话 worktree)
|
|
18
|
-
- 通过检查 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` **是否实际存在**来确定关联应用——创建时用户可能只关联了 `
|
|
18
|
+
- 通过检查 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` **是否实际存在**来确定关联应用——创建时用户可能只关联了 `CBB-WORKSPACE.json` 中的部分应用,不能假设全部应用都关联
|
|
19
19
|
- **顺序硬约束:先移除所有 worktree,再删除分支。** 目标分支当前正检出于 worktree 中,先删分支会报 `error: Cannot delete branch '<分支>' checked out at '<worktree 路径>'`
|
|
20
20
|
- **破坏性操作必须先报告、经用户确认才执行**:未提交修改、未合并/未推送的提交,绝不静默 `--force` 或 `-D` 丢弃
|
|
21
21
|
- 软性检查失败时报告详情,询问用户是否继续
|
|
@@ -34,7 +34,7 @@ node "<skill_dir>/scripts/<name>.js" <参数>
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
> - `<skill_dir>` = 本 SKILL.md 所在目录的绝对路径(AI 从读取路径即可推出)
|
|
37
|
-
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `
|
|
37
|
+
> - **cwd 必须保持在工作空间根目录**:脚本按 cwd 定位 `CBB-WORKSPACE.json` 与 git 仓库
|
|
38
38
|
> - 不要用 `./scripts/...` 裸相对路径(AI 的 cwd 不是技能目录,会解析失败)
|
|
39
39
|
|
|
40
40
|
---
|
|
@@ -62,9 +62,9 @@ node "<skill_dir>/scripts/find-workspace-root.js"
|
|
|
62
62
|
- 退出码 0 + 末行(纯路径) → WS_ROOT 绝对路径
|
|
63
63
|
- 退出码 1 → 未找到工作空间根目录,**终止流程**
|
|
64
64
|
|
|
65
|
-
> ⓘ **为什么不用
|
|
65
|
+
> ⓘ **为什么不用 CBB-WORKSPACE.json 存在性判断**:init-worktree 会把 CBB-WORKSPACE.json 提交到主分支,`git worktree add` 会自动 checkout 副本到 worktree 目录中。若用 CBB-WORKSPACE.json 存在性判断,在 worktree 内执行时会错误地把 worktree 目录当作 WS_ROOT。使用 `git worktree list` 的第一行(git 契约上始终为主仓库绝对路径)则不受该问题影响。
|
|
66
66
|
|
|
67
|
-
> close-worktree 是 init-worktree 的逆操作,但本步骤的 WS_ROOT 定位逻辑与 init-worktree 不同:init-worktree 强制根目录执行(不需要处理 worktree 场景),close-worktree 支持 worktree 内执行(需要绕开
|
|
67
|
+
> close-worktree 是 init-worktree 的逆操作,但本步骤的 WS_ROOT 定位逻辑与 init-worktree 不同:init-worktree 强制根目录执行(不需要处理 worktree 场景),close-worktree 支持 worktree 内执行(需要绕开 CBB-WORKSPACE.json 副本陷阱)。
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -97,7 +97,7 @@ node "<skill_dir>/scripts/find-target-worktree.js "<ws_root>"
|
|
|
97
97
|
node "<skill_dir>/scripts/discover-apps.js "<ws_root>" "<branch>"
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
脚本自动从 `
|
|
100
|
+
脚本自动从 `CBB-WORKSPACE.json` 读取应用列表,扫描 `.worktrees/worktree-<branch>/.codespace/<应用名>/`(兼容 ≤beta.23 旧平铺 `<应用名>/`)实际存在的子目录。
|
|
101
101
|
|
|
102
102
|
**解读末行 `DISCOVER_RESULT`**:
|
|
103
103
|
- `workspace` 字段:workspace worktree 绝对路径(AI agent 记录供后续命令使用)
|
|
@@ -430,7 +430,7 @@ git -C "<ws_root>" worktree list
|
|
|
430
430
|
|
|
431
431
|
### 必须执行
|
|
432
432
|
|
|
433
|
-
- 读 `
|
|
433
|
+
- 读 `CBB-WORKSPACE.json` 取应用名列表;以 `.worktrees/worktree-<需求名>/.codespace/<应用名>/` 实际存在性确定关联清单
|
|
434
434
|
- 移除/删除前执行安全评估(未提交修改 / 是否合并 / 是否推远程)并报告
|
|
435
435
|
- 严格按 Step 4.2(移除 worktree)→ Step 4.4(删分支)顺序
|
|
436
436
|
- 移除 workspace worktree 前先把会话移出(`cd "<WS_ROOT>" &&` 或 `git -C` 绝对路径)
|
|
@@ -3,14 +3,14 @@ require('./silence-popup');
|
|
|
3
3
|
/**
|
|
4
4
|
* cbb-worktree-close 环境健康探活
|
|
5
5
|
*
|
|
6
|
-
* close-worktree 必须在工作空间根目录或 worktree 内执行,且需找到
|
|
7
|
-
* 与 init/extend 探活同构,但额外校验
|
|
6
|
+
* close-worktree 必须在工作空间根目录或 worktree 内执行,且需找到 CBB-WORKSPACE.json。
|
|
7
|
+
* 与 init/extend 探活同构,但额外校验 CBB-WORKSPACE.json 存在性。
|
|
8
8
|
*
|
|
9
9
|
* 退出码:
|
|
10
10
|
* 0 - 探活通过
|
|
11
11
|
* 1 - 致命错误(git 不可用)
|
|
12
12
|
* 2 - 当前不在 git 仓库
|
|
13
|
-
* 3 - 未找到
|
|
13
|
+
* 3 - 未找到 CBB-WORKSPACE.json
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { execSync } = require('child_process');
|
|
@@ -116,7 +116,7 @@ function deleteBranch(repoPath, name) {
|
|
|
116
116
|
return true;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
const configPath = path.join(wsRoot, '
|
|
119
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
120
120
|
let config = {};
|
|
121
121
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
122
122
|
|
|
@@ -5,7 +5,7 @@ require('./silence-popup');
|
|
|
5
5
|
*
|
|
6
6
|
* 用法:node discover-apps.js <ws_root> <branch>
|
|
7
7
|
*
|
|
8
|
-
* 从
|
|
8
|
+
* 从 CBB-WORKSPACE.json 读取应用列表,扫描 .worktrees/worktree-<branch>/ 实际存在的子目录。
|
|
9
9
|
*
|
|
10
10
|
* 输出末行:DISCOVER_RESULT { workspace: <wt_path>, apps: [{name, repo, side, wt_path}] }
|
|
11
11
|
*
|
|
@@ -26,9 +26,9 @@ if (!wsRoot || !branch) {
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const configPath = path.join(wsRoot, '
|
|
29
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
30
30
|
if (!fs.existsSync(configPath)) {
|
|
31
|
-
console.error('❌
|
|
31
|
+
console.error('❌ CBB-WORKSPACE.json 不存在:' + configPath);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ let config;
|
|
|
36
36
|
try {
|
|
37
37
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
38
38
|
} catch (e) {
|
|
39
|
-
console.error('❌
|
|
39
|
+
console.error('❌ CBB-WORKSPACE.json 解析失败:' + e.message);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -5,8 +5,8 @@ require('./silence-popup');
|
|
|
5
5
|
* 唯一真源:cbb/worktrees/_shared/scripts/find-workspace-root.js
|
|
6
6
|
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init / extend / close / push)。
|
|
7
7
|
*
|
|
8
|
-
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内
|
|
9
|
-
* 兜底:当前目录含
|
|
8
|
+
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内 CBB-WORKSPACE.json 副本干扰)
|
|
9
|
+
* 兜底:当前目录含 CBB-WORKSPACE.json
|
|
10
10
|
*
|
|
11
11
|
* 退出码:
|
|
12
12
|
* 0 - 定位成功,输出 WS_ROOT 绝对路径
|
|
@@ -44,19 +44,19 @@ if (worktreeList.ok) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// 兜底:当前目录含
|
|
47
|
+
// 兜底:当前目录含 CBB-WORKSPACE.json
|
|
48
48
|
if (!wsRoot) {
|
|
49
49
|
const cwd = process.cwd();
|
|
50
|
-
if (fs.existsSync(path.join(cwd, '
|
|
50
|
+
if (fs.existsSync(path.join(cwd, 'CBB-WORKSPACE.json'))) {
|
|
51
51
|
wsRoot = cwd;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// 校验
|
|
56
|
-
if (!wsRoot || !fs.existsSync(path.join(wsRoot, '
|
|
56
|
+
if (!wsRoot || !fs.existsSync(path.join(wsRoot, 'CBB-WORKSPACE.json'))) {
|
|
57
57
|
console.error('❌ 未找到工作空间根目录');
|
|
58
|
-
console.error(' 原因:git worktree list 失败且当前目录无
|
|
59
|
-
console.error(' 请在业务工作空间根目录(由 cbb setup 初始化、包含
|
|
58
|
+
console.error(' 原因:git worktree list 失败且当前目录无 CBB-WORKSPACE.json');
|
|
59
|
+
console.error(' 请在业务工作空间根目录(由 cbb setup 初始化、包含 CBB-WORKSPACE.json)下执行本技能');
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -83,7 +83,7 @@ function removeWt(repoPath, wtPath, name) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// 1. 应用 worktrees
|
|
86
|
-
const configPath = path.join(wsRoot, '
|
|
86
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
87
87
|
let config = {};
|
|
88
88
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
89
89
|
|
|
@@ -152,7 +152,7 @@ function checkRepo(name, repoPath, wtPath) {
|
|
|
152
152
|
return { name, wt: wtPath, fetch_ok: fetchOk, dirty_files: dirtyFiles, merged, main, remote_exists: remoteExists, remote: remoteName, unpushed_count: unpushedCount };
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
const configPath = path.join(wsRoot, '
|
|
155
|
+
const configPath = path.join(wsRoot, 'CBB-WORKSPACE.json');
|
|
156
156
|
let config = {};
|
|
157
157
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
158
158
|
|