@jspg-ai/coding-bb 0.0.3-beta.21 → 0.0.3-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/cbb/dev-standards/rules/cbb-ai-behavior.md +21 -14
  2. package/cbb/lib/install/init.js +36 -3
  3. package/cbb/lib/install/workspaces.js +6 -2
  4. package/cbb/lib/utils/githook.js +224 -0
  5. package/cbb/lib/utils/gitignore.js +5 -1
  6. package/cbb/worktrees/_shared/scripts/check-local-state.js +114 -0
  7. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +148 -96
  8. package/cbb/worktrees/_shared/scripts/install-git-hook.js +70 -0
  9. package/cbb/worktrees/_shared/scripts/junction-ai-config.js +127 -0
  10. package/cbb/worktrees/_shared/scripts/start-local.js +180 -0
  11. package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +158 -0
  12. package/cbb/worktrees/skills/cbb-local-mode/scripts/check-local-state.js +114 -0
  13. package/cbb/worktrees/skills/cbb-local-mode/scripts/install-git-hook.js +70 -0
  14. package/cbb/worktrees/skills/cbb-local-mode/scripts/silence-popup.js +23 -0
  15. package/cbb/worktrees/skills/cbb-local-mode/scripts/start-local.js +180 -0
  16. package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +9 -6
  17. package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-unarchived.js +75 -48
  18. package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-target-worktree.js +148 -96
  19. package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +85 -91
  20. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-branches.js +31 -6
  21. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +23 -3
  22. package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-git-hook.js +70 -0
  23. package/cbb/worktrees/skills/cbb-worktree-init/scripts/junction-ai-config.js +127 -0
  24. package/cbb/worktrees/skills/cbb-worktree-init/scripts/sync-repos.js +5 -1
  25. package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +17 -5
  26. package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +5 -5
  27. package/cbb/worktrees/skills/cbb-worktree-push/scripts/commit-worktrees.js +48 -12
  28. package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-target-worktree.js +148 -96
  29. package/cbb/worktrees/skills/cbb-worktree-push/scripts/push-worktrees.js +45 -20
  30. package/config/workspace-agents.sample.md +50 -45
  31. package/package.json +2 -2
@@ -1,48 +1,75 @@
1
- #!/usr/bin/env node
2
- require('./silence-popup');
3
- /**
4
- * cbb-worktree-close 未归档 change 检查
5
- *
6
- * 用法:node check-unarchived.js <ws_root>
7
- *
8
- * 扫描 openspec/changes/ 目录,筛选出非 archive/ 子目录的 change 名称列表。
9
- * 本检查为信息收集性质,不阻断流程。
10
- *
11
- * 退出码:
12
- * 0 - 检查完成(无论是否有未归档 change)
13
- * 1 - 参数错误或 openspec/changes/ 目录不存在
14
- */
15
-
16
- const fs = require('fs');
17
- const path = require('path');
18
-
19
- const wsRoot = process.argv[2];
20
-
21
- if (!wsRoot) {
22
- console.error('Usage: node check-unarchived.js <ws_root>');
23
- process.exit(1);
24
- }
25
-
26
- const changesDir = path.join(wsRoot, 'openspec', 'changes');
27
-
28
- if (!fs.existsSync(changesDir)) {
29
- console.log('UNARCHIVED_RESULT {"changes":[]}');
30
- process.exit(0);
31
- }
32
-
33
- let entries;
34
- try {
35
- entries = fs.readdirSync(changesDir, { withFileTypes: true });
36
- } catch (e) {
37
- console.error('Failed to read openspec/changes/:', e.message);
38
- console.log('UNARCHIVED_RESULT {"changes":[]}');
39
- process.exit(0);
40
- }
41
-
42
- const unarchived = entries
43
- .filter(e => e.isDirectory() && e.name !== 'archive')
44
- .map(e => e.name)
45
- .sort();
46
-
47
- console.log('UNARCHIVED_RESULT', JSON.stringify({ changes: unarchived }));
48
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ require('./silence-popup');
3
+ /**
4
+ * cbb-worktree-close 未归档 change 检查
5
+ *
6
+ * 用法:node check-unarchived.js <ws_root> [branch]
7
+ *
8
+ * 双扫(ADR 0002):
9
+ * - <ws_root>/openspec/changes/ → 空间根当前检出(本地模式产物 / 空间根遗留)
10
+ * - <ws_root>/.worktrees/worktree-<branch>/openspec/changes/ → worktree 模式产物
11
+ * (branch 未传时跳过 worktree 扫描,保持旧调用兼容)
12
+ *
13
+ * 输出:changes 为两处合集(去重);detail 标注每个 change 的来源
14
+ * ('root' / 'worktree',同名两处共存时为 'both')。
15
+ * 本检查为信息收集性质,不阻断流程。
16
+ *
17
+ * 退出码:
18
+ * 0 - 检查完成(无论是否有未归档 change)
19
+ * 1 - 参数错误
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const wsRoot = process.argv[2];
26
+ const branch = process.argv[3];
27
+
28
+ if (!wsRoot) {
29
+ console.error('Usage: node check-unarchived.js <ws_root> [branch]');
30
+ process.exit(1);
31
+ }
32
+
33
+ /**
34
+ * 扫描单个 changes 目录,返回未归档 change 名数组(目录不存在返回 [])。
35
+ */
36
+ function scanDir(changesDir) {
37
+ if (!fs.existsSync(changesDir)) return [];
38
+ let entries;
39
+ try {
40
+ entries = fs.readdirSync(changesDir, { withFileTypes: true });
41
+ } catch (e) {
42
+ console.error('Failed to read ' + changesDir + ':', e.message);
43
+ return [];
44
+ }
45
+ return entries
46
+ .filter(e => e.isDirectory() && e.name !== 'archive')
47
+ .map(e => e.name);
48
+ }
49
+
50
+ const rootChanges = scanDir(path.join(wsRoot, 'openspec', 'changes'));
51
+
52
+ const worktreeDir = branch
53
+ ? path.join(wsRoot, '.worktrees', 'worktree-' + branch, 'openspec', 'changes')
54
+ : null;
55
+ const wtChanges = worktreeDir ? scanDir(worktreeDir) : [];
56
+
57
+ const detail = {};
58
+ for (const name of rootChanges) detail[name] = 'root';
59
+ for (const name of wtChanges) {
60
+ detail[name] = detail[name] === 'root' ? 'both' : 'worktree';
61
+ }
62
+
63
+ const changes = Object.keys(detail).sort();
64
+
65
+ if (rootChanges.length === 0 && wtChanges.length === 0) {
66
+ console.log('ℹ️ 未发现未归档 change(根与 worktree' + (worktreeDir ? '' : '(未指定 branch,跳过)') + '均无)');
67
+ } else {
68
+ console.log('ℹ️ 未归档 change:' + (changes.join(', ') || '无'));
69
+ for (const name of changes) {
70
+ console.log(' - ' + name + '(来源:' + detail[name] + ')');
71
+ }
72
+ }
73
+
74
+ console.log('UNARCHIVED_RESULT', JSON.stringify({ changes, detail }));
75
+ process.exit(0);
@@ -1,96 +1,148 @@
1
- require('./silence-popup');
2
- /**
3
- * openspec-shared 目标需求名识别
4
- *
5
- * 唯一真源:cbb/worktrees/_shared/scripts/find-target-worktree.js
6
- * 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(extend / close / push)。
7
- *
8
- * 用法:node find-target-worktree.js <ws_root>
9
- *
10
- * 优先级:
11
- * 1. cwd 在 .worktrees/worktree-xxx/ 内 → 自动从路径反推
12
- * 2. .worktrees/ 下只有一个 worktree → 自动选用
13
- * 3. 多个 worktree → 按 mtime 倒序,第一个为推荐
14
- *
15
- * 输出:
16
- * 末行输出需求名(用于 AI agent 解析)
17
- * 多个时按 [1] xxx ⭐ / [2] yyy 列出
18
- *
19
- * 退出码:
20
- * 0 - 唯一识别(自动或唯一)
21
- * 1 - 多个 worktree,需用户输入
22
- * 2 - 无 worktree
23
- */
24
-
25
- const { execSync } = require('child_process');
26
- const fs = require('fs');
27
- const path = require('path');
28
-
29
- const wsRoot = process.argv[2];
30
-
31
- if (!wsRoot) {
32
- console.error('❌ 用法:node find-target-worktree.js <ws_root>');
33
- process.exit(1);
34
- }
35
-
36
- // 优先级 1:cwd 反推
37
- const cwd = process.cwd();
38
- const cwdMatch = cwd.match(/\.worktrees[\\\/]worktree-([^\\\/]+)/);
39
- if (cwdMatch) {
40
- const branch = cwdMatch[1];
41
- console.log('✅ 从 cwd 反推需求名:' + branch);
42
- console.log('---');
43
- console.log(branch);
44
- process.exit(0);
45
- }
46
-
47
- const worktreesDir = path.join(wsRoot, '.worktrees');
48
- if (!fs.existsSync(worktreesDir)) {
49
- console.error('❌ .worktrees 目录不存在:' + worktreesDir);
50
- console.error(' 请先执行 cbb-worktree-init 创建 worktree');
51
- console.log('---');
52
- console.log('NO_WORKTREE');
53
- process.exit(2);
54
- }
55
-
56
- // 列出所有 worktree-xxx 目录
57
- const entries = fs.readdirSync(worktreesDir, { withFileTypes: true })
58
- .filter(e => e.isDirectory() && e.name.startsWith('worktree-'))
59
- .map(e => ({
60
- name: e.name.substring('worktree-'.length),
61
- fullName: e.name,
62
- path: path.join(worktreesDir, e.name),
63
- mtime: fs.statSync(path.join(worktreesDir, e.name)).mtime.getTime(),
64
- }));
65
-
66
- if (entries.length === 0) {
67
- console.error('❌ 未找到任何 worktree');
68
- console.error(' 请先执行 cbb-worktree-init 创建 worktree');
69
- console.log('---');
70
- console.log('NO_WORKTREE');
71
- process.exit(2);
72
- }
73
-
74
- // 优先级 2:唯一
75
- if (entries.length === 1) {
76
- console.log('✅ 自动选用唯一 worktree:' + entries[0].name);
77
- console.log('---');
78
- console.log(entries[0].name);
79
- process.exit(0);
80
- }
81
-
82
- // 优先级 3:多个 → 按 mtime 倒序
83
- entries.sort((a, b) => b.mtime - a.mtime);
84
-
85
- console.log('⚠️ 检测到 ' + entries.length + ' 个 worktree(按最近修改排序):');
86
- entries.forEach((e, i) => {
87
- if (i === 0) {
88
- console.log(' [' + (i + 1) + '] ' + e.name + ' ⭐ (推荐:最近修改)');
89
- } else {
90
- console.log(' [' + (i + 1) + '] ' + e.name);
91
- }
92
- });
93
- console.log('请在对话中输入序号(1/2/3)或完整分支名:');
94
- console.log('---');
95
- console.log('MULTIPLE_WORKTREEES');
96
- process.exit(1);
1
+ require('./silence-popup');
2
+ /**
3
+ * openspec-shared 目标需求名识别
4
+ *
5
+ * 唯一真源:cbb/worktrees/_shared/scripts/find-target-worktree.js
6
+ * 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(extend / close / push)。
7
+ *
8
+ * 用法:node find-target-worktree.js <ws_root>
9
+ *
10
+ * 优先级(ADR 0002 双布局):
11
+ * 1. cwd 在 .worktrees/worktree-xxx/ 内 → 自动从路径反推
12
+ * 2. 收集候选:worktree 目录(.worktrees/worktree-xxx)
13
+ * + 本地布局(根 / .codespace/<app>/ 检出在非主干分支,本地模式进行中)
14
+ * 同一需求名两种布局共存时合并为一项(标 both)
15
+ * 3. 唯一候选 → 自动选用;多个 → 按 mtime 倒序列出(本地布局用分支检出时间)
16
+ *
17
+ * 输出:
18
+ * 末行输出需求名(用于 AI agent 解析)
19
+ * 多个时按 [1] xxx ⭐ / [2] yyy 列出
20
+ *
21
+ * 退出码:
22
+ * 0 - 唯一识别(自动或唯一)
23
+ * 1 - 多个候选,需用户输入
24
+ * 2 - 无候选(无 worktree 且无本地布局检出)
25
+ */
26
+
27
+ const { execSync } = require('child_process');
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+
31
+ const wsRoot = process.argv[2];
32
+
33
+ if (!wsRoot) {
34
+ console.error('❌ 用法:node find-target-worktree.js <ws_root>');
35
+ process.exit(1);
36
+ }
37
+
38
+ function run(cmd, cwd) {
39
+ try {
40
+ return execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
41
+ } catch (e) { return null; }
42
+ }
43
+
44
+ function currentBranch(cwd) {
45
+ return run('git branch --show-current', cwd) || '';
46
+ }
47
+
48
+ /** 主干名(探测失败返回 null → 该仓库任何非空检出都视为候选) */
49
+ function detectMain(cwd) {
50
+ const remotes = run('git remote', cwd);
51
+ if (remotes) {
52
+ const r = remotes.split(/\r?\n/)[0];
53
+ const sym = run('git symbolic-ref "refs/remotes/' + r + '/HEAD"', cwd);
54
+ if (sym) return sym.replace('refs/remotes/' + r + '/', '');
55
+ }
56
+ if (run('git rev-parse --verify --quiet refs/heads/main', cwd)) return 'main';
57
+ if (run('git rev-parse --verify --quiet refs/heads/master', cwd)) return 'master';
58
+ return null;
59
+ }
60
+
61
+ // 优先级 1:cwd 反推
62
+ const cwd = process.cwd();
63
+ const cwdMatch = cwd.match(/\.worktrees[\\\/]worktree-([^\\\/]+)/);
64
+ if (cwdMatch) {
65
+ const branch = cwdMatch[1];
66
+ console.log('✅ 从 cwd 反推需求名:' + branch);
67
+ console.log('---');
68
+ console.log(branch);
69
+ process.exit(0);
70
+ }
71
+
72
+ // 优先级 2a:worktree 目录候选
73
+ const candidates = new Map(); // name → { layouts: Set, mtime }
74
+
75
+ const worktreesDir = path.join(wsRoot, '.worktrees');
76
+ if (fs.existsSync(worktreesDir)) {
77
+ const entries = fs.readdirSync(worktreesDir, { withFileTypes: true })
78
+ .filter(e => e.isDirectory() && e.name.startsWith('worktree-'));
79
+ for (const e of entries) {
80
+ const name = e.name.substring('worktree-'.length);
81
+ const full = path.join(worktreesDir, e.name);
82
+ const item = candidates.get(name) || { layouts: new Set(), mtime: 0 };
83
+ item.layouts.add('worktree');
84
+ item.mtime = Math.max(item.mtime, fs.statSync(full).mtime.getTime());
85
+ candidates.set(name, item);
86
+ }
87
+ }
88
+
89
+ // 优先级 2b:本地布局候选(根 + .codespace/<app>/ 检出在非主干分支)
90
+ function addLocalCandidate(name, cwdPath) {
91
+ if (!name) return;
92
+ const item = candidates.get(name) || { layouts: new Set(), mtime: 0 };
93
+ item.layouts.add('local');
94
+ try {
95
+ item.mtime = Math.max(item.mtime, fs.statSync(path.join(cwdPath, '.git')).mtime.getTime());
96
+ } catch (_) { /* .git 状态时间不可读则保持 */ }
97
+ candidates.set(name, item);
98
+ }
99
+
100
+ const rootMain = detectMain(wsRoot);
101
+ const rootBranch = currentBranch(wsRoot);
102
+ if (rootBranch && (!rootMain || rootBranch !== rootMain)) {
103
+ addLocalCandidate(rootBranch, wsRoot);
104
+ }
105
+
106
+ const codeDir = path.join(wsRoot, '.codespace');
107
+ if (fs.existsSync(codeDir)) {
108
+ for (const app of fs.readdirSync(codeDir).sort()) {
109
+ const appDir = path.join(codeDir, app);
110
+ if (!fs.existsSync(path.join(appDir, '.git'))) continue;
111
+ const appMain = detectMain(appDir);
112
+ const appBranch = currentBranch(appDir);
113
+ if (appBranch && (!appMain || appBranch !== appMain)) {
114
+ addLocalCandidate(appBranch, appDir);
115
+ }
116
+ }
117
+ }
118
+
119
+ if (candidates.size === 0) {
120
+ console.error('❌ 未找到任何进行中的需求(无 worktree,且无仓库检出在非主干分支)');
121
+ console.error(' worktree 模式请先执行 cbb-worktree-init;本地模式请先执行 cbb-local-mode');
122
+ console.log('---');
123
+ console.log('NO_WORKTREE');
124
+ process.exit(2);
125
+ }
126
+
127
+ // 唯一候选 → 自动选用
128
+ if (candidates.size === 1) {
129
+ const [name] = [...candidates.keys()];
130
+ const layouts = [...candidates.get(name).layouts].join('+');
131
+ console.log('✅ 自动选用唯一进行中需求:' + name + '(' + layouts + ')');
132
+ console.log('---');
133
+ console.log(name);
134
+ process.exit(0);
135
+ }
136
+
137
+ // 多候选 → 按 mtime 倒序列出
138
+ const sorted = [...candidates.entries()].sort((a, b) => b[1].mtime - a[1].mtime);
139
+ console.log('⚠️ 检测到 ' + sorted.length + ' 个进行中的需求(按最近活动排序):');
140
+ sorted.forEach(([name, item], i) => {
141
+ const layouts = [...item.layouts].join('+');
142
+ const tag = i === 0 ? ' ⭐ (推荐:最近活动)' : '';
143
+ console.log(' [' + (i + 1) + '] ' + name + '(' + layouts + ')' + tag);
144
+ });
145
+ console.log('请在对话中输入序号(1/2/3)或完整分支名:');
146
+ console.log('---');
147
+ console.log('MULTIPLE_WORKTREEES');
148
+ process.exit(1);