@jspg-ai/coding-bb 0.0.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/README.md +41 -0
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +104 -0
- package/cbb/dev-standards/rules/cbb-coding-rule.md +50 -0
- package/cbb/dev-standards/rules/cbb-priority.md +56 -0
- package/cbb/dev-standards/skills/architecture-specs/SKILL.md +129 -0
- package/cbb/dev-standards/skills/coding-specs/SKILL.md +376 -0
- package/cbb/dev-standards/skills/coding-specs/references/concurrency.md +53 -0
- package/cbb/dev-standards/skills/coding-specs/references/config-center.md +16 -0
- package/cbb/dev-standards/skills/coding-specs/references/distributed.md +42 -0
- package/cbb/dev-standards/skills/coding-specs/references/es-coding.md +60 -0
- package/cbb/dev-standards/skills/coding-specs/references/scheduled-task.md +25 -0
- package/cbb/dev-standards/skills/coding-specs/references/security.md +11 -0
- package/cbb/dev-standards/skills/coding-specs/references/unit-testing.md +43 -0
- package/cbb/dev-standards/skills/es-design-specs/SKILL.md +104 -0
- package/cbb/dev-standards/skills/mysql-design-specs/SKILL.md +89 -0
- package/cbb/lib/install/claude-code.js +20 -0
- package/cbb/lib/install/cleanup.js +64 -0
- package/cbb/lib/install/init.js +1875 -0
- package/cbb/lib/install/qoder.js +20 -0
- package/cbb/lib/install/workspaces.js +232 -0
- package/cbb/lib/openspec/index.js +554 -0
- package/cbb/lib/superpowers/index.js +265 -0
- package/cbb/lib/utils/check-update.js +147 -0
- package/cbb/lib/utils/checkbox.js +383 -0
- package/cbb/lib/utils/gitignore.js +69 -0
- package/cbb/lib/utils/output.js +64 -0
- package/cbb/lib/utils/settings.js +119 -0
- package/cbb/lib/utils/version.js +135 -0
- package/cbb/lib/wiki/api-client.js +358 -0
- package/cbb/lib/wiki/cli.js +427 -0
- package/cbb/lib/wiki/convert.js +464 -0
- package/cbb/lib/wiki/index.js +218 -0
- package/cbb/lib/wiki/mermaid-guard.js +103 -0
- package/cbb/lib/wiki/split.js +131 -0
- package/cbb/tools/cbb-decompile-jar/SKILL.md +220 -0
- package/cbb/tools/cbb-decompile-jar/scripts/decompile.py +863 -0
- package/cbb/tools/cbb-design-to-wiki/SKILL.md +180 -0
- package/cbb/tools/cbb-mvn-guide/SKILL.md +281 -0
- package/cbb/tools/cbb-mvn-guide/scripts/mvn_jdk_manager.py +378 -0
- package/cbb/tools/cbb-mvn-guide/scripts/mvn_pom_jdk_reader.py +197 -0
- package/cbb/tools/cbb-plantuml-authoring/SKILL.md +448 -0
- package/cbb/tools/cbb-plantuml-authoring/references/drawing-templates.md +479 -0
- package/cbb/tools/cbb-wiki-ops/SKILL.md +134 -0
- package/cbb/tools/cbb-wiki-ops/scripts/check-auth.js +44 -0
- package/cbb/worktrees/_shared/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/_shared/scripts/push-core.js +136 -0
- package/cbb/worktrees/_shared/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/commands/close.md +64 -0
- package/cbb/worktrees/commands/extend.md +65 -0
- package/cbb/worktrees/commands/init.md +55 -0
- package/cbb/worktrees/commands/push.md +42 -0
- package/cbb/worktrees/skills/openspec-close-worktree/SKILL.md +449 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-env.js +76 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-unarchived.js +48 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/delete-branches.js +136 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/discover-apps.js +76 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/remove-worktrees.js +124 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/safety-check.js +174 -0
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +390 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +95 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +98 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +135 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +150 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +88 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +93 -0
- package/cbb/worktrees/skills/openspec-init-worktree/SKILL.md +539 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/auto-open.js +136 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env-deep.js +87 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env.js +90 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-repos.js +171 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js +103 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-worktrees.js +154 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +93 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/install-ai.js +177 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/parse-config.js +67 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-branches.js +101 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/silence-popup.js +23 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/sync-repos.js +95 -0
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/update-gitignore.js +134 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/SKILL.md +307 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/commit-worktrees.js +269 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-target-worktree.js +96 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-workspace-root.js +67 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-core.js +136 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-worktrees.js +159 -0
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +23 -0
- package/config/cbb.yaml +6 -0
- package/config/config.sample.json +16 -0
- package/config/openspec/config.yaml +28 -0
- package/config/openspec/schemas/spec-driven/schema.yaml +247 -0
- package/config/openspec/schemas/spec-driven/templates/design.md +475 -0
- package/config/openspec/schemas/spec-driven/templates/proposal.md +58 -0
- package/config/openspec/schemas/spec-driven/templates/spec.md +8 -0
- package/config/openspec/schemas/spec-driven/templates/tasks.md +9 -0
- package/config/workspaces.json +15 -0
- package/openspec/.version +6 -0
- package/openspec/commands/apply.md +182 -0
- package/openspec/commands/archive.md +223 -0
- package/openspec/commands/bulk-archive.md +334 -0
- package/openspec/commands/continue.md +112 -0
- package/openspec/commands/explore.md +206 -0
- package/openspec/commands/ff.md +111 -0
- package/openspec/commands/new.md +70 -0
- package/openspec/commands/onboard.md +555 -0
- package/openspec/commands/propose.md +157 -0
- package/openspec/commands/sync.md +256 -0
- package/openspec/commands/update.md +85 -0
- package/openspec/commands/verify.md +169 -0
- package/openspec/skills/openspec-apply-change/SKILL.md +187 -0
- package/openspec/skills/openspec-archive-change/SKILL.md +181 -0
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -0
- package/openspec/skills/openspec-continue-change/SKILL.md +117 -0
- package/openspec/skills/openspec-explore/SKILL.md +342 -0
- package/openspec/skills/openspec-ff-change/SKILL.md +116 -0
- package/openspec/skills/openspec-new-change/SKILL.md +76 -0
- package/openspec/skills/openspec-onboard/SKILL.md +560 -0
- package/openspec/skills/openspec-propose/SKILL.md +162 -0
- package/openspec/skills/openspec-sync-specs/SKILL.md +261 -0
- package/openspec/skills/openspec-update-change/SKILL.md +90 -0
- package/openspec/skills/openspec-verify-change/SKILL.md +174 -0
- package/package.json +46 -0
- package/superpowers/.version +6 -0
- package/superpowers/skills/brainstorming/SKILL.md +250 -0
- package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
- package/superpowers/skills/brainstorming/scripts/helper.js +167 -0
- package/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
- package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
- package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/superpowers/skills/brainstorming/visual-companion.md +299 -0
- package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/superpowers/skills/executing-plans/SKILL.md +64 -0
- package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -0
- package/superpowers/skills/receiving-code-review/SKILL.md +205 -0
- package/superpowers/skills/requesting-code-review/SKILL.md +95 -0
- package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -0
- package/superpowers/skills/subagent-driven-development/SKILL.md +568 -0
- package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -0
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -0
- package/superpowers/skills/subagent-driven-development/scripts/review-package +46 -0
- package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
- package/superpowers/skills/subagent-driven-development/scripts/task-brief +41 -0
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/superpowers/skills/systematic-debugging/SKILL.md +283 -0
- package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -0
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/superpowers/skills/systematic-debugging/test-academic.md +14 -0
- package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/superpowers/skills/test-driven-development/SKILL.md +320 -0
- package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -0
- package/superpowers/skills/using-git-worktrees/SKILL.md +167 -0
- package/superpowers/skills/using-superpowers/SKILL.md +63 -0
- package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
- package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -0
- package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -0
- package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -0
- package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
- package/superpowers/skills/verification-before-completion/SKILL.md +120 -0
- package/superpowers/skills/writing-plans/SKILL.md +171 -0
- package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/superpowers/skills/writing-skills/SKILL.md +679 -0
- package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
- package/superpowers/skills/writing-skills/render-graphs.js +169 -0
- package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-init-worktree 创建 Worktree(含自愈清理)
|
|
5
|
+
*
|
|
6
|
+
* 用法:node create-worktrees.js <ws_root> '<apps_json>' <branch>
|
|
7
|
+
*
|
|
8
|
+
* 行为:
|
|
9
|
+
* 1. 自愈清理:prune + 物理目录清理
|
|
10
|
+
* 2. workspace worktree 创建(.worktrees/worktree-<branch>)
|
|
11
|
+
* 3. 各 app worktree 创建(物理嵌套在 workspace worktree 内)
|
|
12
|
+
* 4. 失败时输出回滚提示
|
|
13
|
+
*
|
|
14
|
+
* 退出码:
|
|
15
|
+
* 0 - 全部成功
|
|
16
|
+
* 1 - 参数错误
|
|
17
|
+
* 2 - workspace worktree 创建失败
|
|
18
|
+
* 3 - 部分 app worktree 创建失败
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { execSync } = require('child_process');
|
|
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 || !branch) {
|
|
29
|
+
console.error('❌ 用法:node create-worktrees.js <ws_root> <branch>');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadAppsFromConfig() {
|
|
34
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
35
|
+
if (!fs.existsSync(configPath)) return null;
|
|
36
|
+
try {
|
|
37
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
38
|
+
return Array.isArray(config.apps) ? config.apps : null;
|
|
39
|
+
} catch (e) { return null; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const apps = loadAppsFromConfig();
|
|
43
|
+
if (!apps) {
|
|
44
|
+
console.error('❌ CONFIG.json 缺失或 apps 为空');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function run(cmd, cwd) {
|
|
49
|
+
try {
|
|
50
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).trim() };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 1. 自愈清理
|
|
57
|
+
console.log('=== 自愈清理 ===');
|
|
58
|
+
|
|
59
|
+
// 1.1 prune 所有仓库
|
|
60
|
+
const allRepos = [wsRoot, ...apps.map(a => path.join(wsRoot, '.code', a.name))];
|
|
61
|
+
for (const repo of allRepos) {
|
|
62
|
+
if (fs.existsSync(repo)) {
|
|
63
|
+
run('git worktree prune', repo);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.log('✅ 已 prune 所有仓库的 worktree 元数据');
|
|
67
|
+
|
|
68
|
+
// 1.2 清理 .worktrees/ 下无元数据的物理目录
|
|
69
|
+
const worktreesDir = path.join(wsRoot, '.worktrees');
|
|
70
|
+
if (fs.existsSync(worktreesDir)) {
|
|
71
|
+
const validWts = run('git worktree list --porcelain', wsRoot);
|
|
72
|
+
const validPaths = new Set();
|
|
73
|
+
if (validWts.ok) {
|
|
74
|
+
validWts.out.split('\n').forEach(line => {
|
|
75
|
+
if (line.startsWith('worktree ')) {
|
|
76
|
+
validPaths.add(line.substring(9).trim());
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const entries = fs.readdirSync(worktreesDir, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (!entry.isDirectory()) continue;
|
|
84
|
+
const dir = path.join(worktreesDir, entry.name);
|
|
85
|
+
let absDir;
|
|
86
|
+
try { absDir = fs.realpathSync(dir); } catch { continue; }
|
|
87
|
+
if (!validPaths.has(absDir)) {
|
|
88
|
+
try {
|
|
89
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
90
|
+
console.log('🧹 已清理残留目录:' + entry.name);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.log('⏳ 残留目录仍被占用,跳过:' + entry.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. workspace worktree 创建
|
|
99
|
+
console.log('=== 创建 Workspace Worktree ===');
|
|
100
|
+
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
101
|
+
if (fs.existsSync(wtDir)) {
|
|
102
|
+
console.log('⏭️ workspace worktree 已存在,复用:' + wtDir);
|
|
103
|
+
} else {
|
|
104
|
+
const created = run('git worktree add ".worktrees/worktree-' + branch + '" "' + branch + '"', wsRoot);
|
|
105
|
+
if (!created.ok) {
|
|
106
|
+
console.error('❌ workspace worktree 创建失败:' + created.err);
|
|
107
|
+
console.error(' 常见原因:分支已在其他 worktree 检出,或权限不足');
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
console.log('✅ workspace worktree 已创建:' + wtDir);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 3. apps worktree 创建(物理嵌套)
|
|
114
|
+
console.log('=== 创建 App Worktrees ===');
|
|
115
|
+
const results = [];
|
|
116
|
+
for (const app of apps) {
|
|
117
|
+
const appPath = path.join(wsRoot, '.code', app.name);
|
|
118
|
+
const nestedWt = path.join(wtDir, app.name);
|
|
119
|
+
|
|
120
|
+
if (fs.existsSync(nestedWt) && fs.existsSync(path.join(nestedWt, '.git'))) {
|
|
121
|
+
console.log('⏭️ ' + app.name + ': 嵌套 worktree 已存在,复用');
|
|
122
|
+
results.push({ name: app.name, status: 'reused' });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 防御性兜底:父目录不存在则创建
|
|
127
|
+
fs.mkdirSync(path.dirname(nestedWt), { recursive: true });
|
|
128
|
+
const created = run('git worktree add "' + nestedWt + '" "' + branch + '"', appPath);
|
|
129
|
+
if (created.ok) {
|
|
130
|
+
console.log('✅ ' + app.name + ': 嵌套 worktree 创建成功');
|
|
131
|
+
results.push({ name: app.name, status: 'created' });
|
|
132
|
+
} else {
|
|
133
|
+
console.error('❌ ' + app.name + ': worktree 创建失败:' + created.err);
|
|
134
|
+
results.push({ name: app.name, status: 'failed', error: created.err });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hasFailed = results.some(r => r.status === 'failed');
|
|
139
|
+
if (hasFailed) {
|
|
140
|
+
console.error('');
|
|
141
|
+
console.error('⚠️ 部分 app worktree 创建失败,需手动清理:');
|
|
142
|
+
console.error(' cd ' + wsRoot + ' && git worktree remove .worktrees/worktree-' + branch);
|
|
143
|
+
for (const r of results.filter(x => x.status === 'failed')) {
|
|
144
|
+
console.error(' git -C .code/' + r.name + ' worktree remove <worktree-path>');
|
|
145
|
+
}
|
|
146
|
+
console.error('---');
|
|
147
|
+
console.error('WORKTREE_RESULT', JSON.stringify(results));
|
|
148
|
+
process.exit(3);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('---');
|
|
152
|
+
console.log('WORKTREE_RESULT', JSON.stringify(results));
|
|
153
|
+
console.log('WT_PATH', path.resolve(wtDir));
|
|
154
|
+
process.exit(0);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require('./silence-popup');
|
|
2
|
+
/**
|
|
3
|
+
* openspec-shared 工作空间根目录定位
|
|
4
|
+
*
|
|
5
|
+
* 唯一真源:cbb/worktrees/_shared/scripts/find-workspace-root.js
|
|
6
|
+
* 由 scripts/sync-shared.js 展开到各 skill 的 scripts/ 下(init / extend / close / push)。
|
|
7
|
+
*
|
|
8
|
+
* 优先:git worktree list 第一行(主仓库根目录,不受 worktree 内 CONFIG.json 副本干扰)
|
|
9
|
+
* 兜底:当前目录含 CONFIG.json
|
|
10
|
+
*
|
|
11
|
+
* 退出码:
|
|
12
|
+
* 0 - 定位成功,输出 WS_ROOT 绝对路径
|
|
13
|
+
* 1 - 未找到工作空间根目录
|
|
14
|
+
*
|
|
15
|
+
* 输出格式:
|
|
16
|
+
* 第一行:人类可读报告
|
|
17
|
+
* 最后一行:纯路径(便于 AI agent 解析后用于 cd)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
function tryExec(cmd) {
|
|
25
|
+
try {
|
|
26
|
+
return { ok: true, out: execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).trim() };
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return { ok: false };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let wsRoot = '';
|
|
33
|
+
|
|
34
|
+
// 优先:git worktree list 第一行
|
|
35
|
+
const worktreeList = tryExec('git worktree list');
|
|
36
|
+
if (worktreeList.ok) {
|
|
37
|
+
const firstLine = worktreeList.out.split('\n')[0];
|
|
38
|
+
if (firstLine) {
|
|
39
|
+
// 格式:<path> <sha> [<branch>]
|
|
40
|
+
const candidate = firstLine.split(/\s+/)[0];
|
|
41
|
+
if (candidate && fs.existsSync(candidate)) {
|
|
42
|
+
wsRoot = candidate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 兜底:当前目录含 CONFIG.json
|
|
48
|
+
if (!wsRoot) {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
if (fs.existsSync(path.join(cwd, 'CONFIG.json'))) {
|
|
51
|
+
wsRoot = cwd;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 校验
|
|
56
|
+
if (!wsRoot || !fs.existsSync(path.join(wsRoot, 'CONFIG.json'))) {
|
|
57
|
+
console.error('❌ 未找到工作空间根目录');
|
|
58
|
+
console.error(' 原因:git worktree list 失败且当前目录无 CONFIG.json');
|
|
59
|
+
console.error(' 请在业务工作空间根目录(由 cbb setup 初始化、包含 CONFIG.json)下执行本技能');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('✅ 工作空间根目录:' + wsRoot);
|
|
64
|
+
console.log('---');
|
|
65
|
+
// 最后一行输出纯路径,AI agent 可直接 cd 使用
|
|
66
|
+
console.log(wsRoot);
|
|
67
|
+
process.exit(0);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* 从 CONFIG.json 读取应用列表,机械生成 AskUserQuestion options
|
|
5
|
+
*
|
|
6
|
+
* 核心原则:label 由代码硬生成,绝不依赖 AI 理解或翻译
|
|
7
|
+
* label = "【" + sideLabel + "】 - " + app.name
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* node generate-app-options.js → 生成全部应用的 options
|
|
11
|
+
* node generate-app-options.js --filter app1,app2 → 只生成指定 app 的 options
|
|
12
|
+
*
|
|
13
|
+
* 退出码:
|
|
14
|
+
* 0 - 成功,输出 APP_OPTIONS
|
|
15
|
+
* 1 - 解析失败
|
|
16
|
+
* 2 - apps 为空
|
|
17
|
+
*
|
|
18
|
+
* 输出格式(末行为机器可解析行):
|
|
19
|
+
* ✅ 已生成 N 个应用选项
|
|
20
|
+
* ---
|
|
21
|
+
* APP_OPTIONS [{"label":"...", "name":"..."}, ...]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// === 解析命令行参数 ===
|
|
28
|
+
const filterNames = [];
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
for (const arg of args) {
|
|
31
|
+
if (arg.startsWith('--filter=')) {
|
|
32
|
+
const raw = arg.slice('--filter='.length);
|
|
33
|
+
if (raw) {
|
|
34
|
+
filterNames.push(...raw.split(',').map(s => s.trim()).filter(Boolean));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// === 读取 CONFIG.json ===
|
|
40
|
+
const configPath = path.join(process.cwd(), 'CONFIG.json');
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(configPath)) {
|
|
43
|
+
console.error('❌ CONFIG.json 不存在');
|
|
44
|
+
console.error('---');
|
|
45
|
+
console.error('CONFIG_MISSING');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let config;
|
|
50
|
+
try {
|
|
51
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('❌ CONFIG.json 解析失败:', e.message);
|
|
54
|
+
console.error('---');
|
|
55
|
+
console.error('CONFIG_INVALID');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const apps = config.apps || [];
|
|
60
|
+
if (apps.length === 0) {
|
|
61
|
+
console.error('❌ CONFIG.json 中 apps 为空');
|
|
62
|
+
console.error('---');
|
|
63
|
+
console.error('CONFIG_EMPTY');
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// === 过滤(如果指定了 --filter) ===
|
|
68
|
+
let targetApps = apps;
|
|
69
|
+
if (filterNames.length > 0) {
|
|
70
|
+
targetApps = apps.filter(app => filterNames.includes(app.name));
|
|
71
|
+
if (targetApps.length === 0) {
|
|
72
|
+
console.error('❌ --filter 指定的应用均不在 CONFIG.json 中:' + filterNames.join(', '));
|
|
73
|
+
console.error('---');
|
|
74
|
+
console.error('FILTER_NO_MATCH');
|
|
75
|
+
process.exit(2);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// === 机械生成 label(核心逻辑,不可修改) ===
|
|
80
|
+
// 公式:label = "【" + sideLabel + "】 - " + app.name
|
|
81
|
+
// app.name 逐字符原样复制,不翻译、不加工
|
|
82
|
+
const options = [];
|
|
83
|
+
for (const app of targetApps) {
|
|
84
|
+
const sideLabel = (app.side === 'front') ? '前端' : '后端';
|
|
85
|
+
const label = '【' + sideLabel + '】 - ' + app.name;
|
|
86
|
+
options.push({ label: label, name: app.name });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// === 输出 ===
|
|
90
|
+
console.log('✅ 已生成 ' + options.length + ' 个应用选项(label 由脚本硬编码,绝不翻译)');
|
|
91
|
+
console.log('---');
|
|
92
|
+
console.log('APP_OPTIONS ' + JSON.stringify(options));
|
|
93
|
+
process.exit(0);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-init-worktree 在 worktree 中安装 AI 配置(必要步骤)
|
|
5
|
+
*
|
|
6
|
+
* 用法:node install-ai.js <wt_path>
|
|
7
|
+
*
|
|
8
|
+
* 行为:
|
|
9
|
+
* 1. 检测 cbb 是否全局安装,未安装则自动安装
|
|
10
|
+
* 2. 直接用 npm 全局 bin 路径调用 cbb(避免 PowerShell PATH 缓存)
|
|
11
|
+
* 3. 在 worktree 内执行 cbb init --yes
|
|
12
|
+
* 4. 失败则 exit 2,阻塞 init-worktree 流程(与 push-branches.js 语义一致)
|
|
13
|
+
*
|
|
14
|
+
* 退出码:
|
|
15
|
+
* 0 - 成功
|
|
16
|
+
* 1 - 参数错误(脚本 bug 或调用错误)
|
|
17
|
+
* 2 - cbb init 执行失败(**阻塞 init-worktree**,用户必须手动恢复)
|
|
18
|
+
*/
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
const wtPath = process.argv[2];
|
|
24
|
+
|
|
25
|
+
if (!wtPath) {
|
|
26
|
+
console.error('Usage: node install-ai.js <wt_path>');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(wtPath)) {
|
|
31
|
+
console.error('ERROR: worktree path not exist: ' + wtPath);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function run(cmd, cwd, env) {
|
|
36
|
+
try {
|
|
37
|
+
return { ok: true, out: execSync(cmd, { cwd: cwd || process.cwd(), encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], env: env || process.env, input: '\n' }).trim() };
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 查找 cbb 可执行文件:先 PATH,再 npm 全局 bin
|
|
44
|
+
function findCbb() {
|
|
45
|
+
// 1. 直接调用 cbb --version
|
|
46
|
+
let r = run('cbb --version');
|
|
47
|
+
if (r.ok) return { cmd: 'cbb', version: r.out };
|
|
48
|
+
|
|
49
|
+
// 2. 查 npm 全局 bin 目录
|
|
50
|
+
const npmRoot = run('npm root -g');
|
|
51
|
+
if (npmRoot.ok) {
|
|
52
|
+
// Windows: <npm_root>\..\cbb.cmd (bin 在 npm_root 的父目录)
|
|
53
|
+
// Unix: <npm_root>/../bin/cbb
|
|
54
|
+
const candidates = [];
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
candidates.push(path.join(npmRoot.out, 'cbb.cmd'));
|
|
57
|
+
candidates.push(path.join(npmRoot.out, '..', 'cbb.cmd'));
|
|
58
|
+
candidates.push(path.join(npmRoot.out, '..', 'bin', 'cbb.cmd'));
|
|
59
|
+
// npm 全局 bin 通常是 %APPDATA%\npm\cbb.cmd
|
|
60
|
+
const appdata = process.env.APPDATA || process.env.USERPROFILE;
|
|
61
|
+
if (appdata) candidates.push(path.join(appdata, 'npm', 'cbb.cmd'));
|
|
62
|
+
} else {
|
|
63
|
+
candidates.push(path.join(npmRoot.out, '..', 'bin', 'cbb'));
|
|
64
|
+
candidates.push(path.join(npmRoot.out, '..', 'cbb'));
|
|
65
|
+
}
|
|
66
|
+
for (const c of candidates) {
|
|
67
|
+
if (fs.existsSync(c)) {
|
|
68
|
+
// 尝试执行
|
|
69
|
+
r = run('"' + c + '" --version');
|
|
70
|
+
if (r.ok) return { cmd: '"' + c + '"', version: r.out };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 1. 检测/安装 cbb
|
|
78
|
+
let cbbInfo = findCbb();
|
|
79
|
+
if (!cbbInfo) {
|
|
80
|
+
console.log('WARN: cbb not globally installed, try auto-install...');
|
|
81
|
+
const install = run('npm install -g @jspg-ai/coding-bb');
|
|
82
|
+
if (!install.ok) {
|
|
83
|
+
console.error('ERROR: cbb auto-install failed: ' + install.err);
|
|
84
|
+
console.error('');
|
|
85
|
+
console.error('━━━ init-worktree 已中断 ━━━');
|
|
86
|
+
console.error('请手动安装 cbb 后重跑 init-worktree:');
|
|
87
|
+
console.error(' 1. npm install -g @jspg-ai/coding-bb');
|
|
88
|
+
console.error(' 2. cd "' + wtPath + '" && cbb init --yes');
|
|
89
|
+
console.error(' 3. 在 ai-workspace 根目录重跑 init-worktree');
|
|
90
|
+
console.log('---');
|
|
91
|
+
console.log('AI_INIT_FAILED');
|
|
92
|
+
process.exit(2);
|
|
93
|
+
}
|
|
94
|
+
// 重新查找(npm install 后 PATH 可能仍未刷新)
|
|
95
|
+
cbbInfo = findCbb();
|
|
96
|
+
if (!cbbInfo) {
|
|
97
|
+
// 最后尝试直接用 npm 全局 bin
|
|
98
|
+
const npmRoot = run('npm root -g');
|
|
99
|
+
if (npmRoot.ok) {
|
|
100
|
+
const candidates = process.platform === 'win32'
|
|
101
|
+
? [path.join(npmRoot.out, '..', 'cbb.cmd'), path.join(npmRoot.out, '..', 'bin', 'cbb.cmd')]
|
|
102
|
+
: [path.join(npmRoot.out, '..', 'bin', 'cbb')];
|
|
103
|
+
for (const c of candidates) {
|
|
104
|
+
if (fs.existsSync(c)) {
|
|
105
|
+
cbbInfo = { cmd: '"' + c + '"', version: 'located at ' + c };
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!cbbInfo) {
|
|
112
|
+
console.error('ERROR: cbb not found after install. Likely npm PATH cache issue.');
|
|
113
|
+
console.error('');
|
|
114
|
+
console.error('━━━ init-worktree 已中断 ━━━');
|
|
115
|
+
console.error('可能原因:npm 全局 bin 目录不在当前 shell 的 PATH 中。');
|
|
116
|
+
console.error('手动恢复步骤:');
|
|
117
|
+
console.error(' 1. 重启终端让 PATH 生效,或手动定位 cbb:');
|
|
118
|
+
console.error(' npm root -g # 拿到全局 node_modules 路径');
|
|
119
|
+
console.error(' <path>/../cbb.cmd # Windows / <path>/../bin/cbb # Unix');
|
|
120
|
+
console.error(' 2. cd "' + wtPath + '" && cbb init --yes');
|
|
121
|
+
console.error(' 3. 在 ai-workspace 根目录重跑 init-worktree');
|
|
122
|
+
console.log('---');
|
|
123
|
+
console.log('AI_INIT_FAILED');
|
|
124
|
+
process.exit(2);
|
|
125
|
+
}
|
|
126
|
+
console.log('OK: cbb located: ' + cbbInfo.version);
|
|
127
|
+
} else {
|
|
128
|
+
console.log('OK: cbb found: ' + cbbInfo.version);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. 在 worktree 中执行 cbb init --yes
|
|
132
|
+
console.log('Installing AI config in worktree: ' + wtPath);
|
|
133
|
+
const initCmd = cbbInfo.cmd + ' init --yes';
|
|
134
|
+
const init = run(initCmd, wtPath);
|
|
135
|
+
if (init.ok) {
|
|
136
|
+
console.log('OK: AI config installed');
|
|
137
|
+
// 输出 init 的最后几行(init 通常输出大量日志)
|
|
138
|
+
const lines = init.out.split(/\r?\n/);
|
|
139
|
+
const tail = lines.slice(-5).join('\n');
|
|
140
|
+
console.log(tail);
|
|
141
|
+
console.log('---');
|
|
142
|
+
console.log('AI_INIT_OK');
|
|
143
|
+
process.exit(0);
|
|
144
|
+
} else {
|
|
145
|
+
console.error('ERROR: AI config install failed: ' + init.err);
|
|
146
|
+
console.error('');
|
|
147
|
+
console.error('━━━ init-worktree 已中断 ━━━');
|
|
148
|
+
console.error('原因:cbb init 是必要步骤。AI 配置缺失将导致后续 openspec-* ');
|
|
149
|
+
console.error(' (propose/apply/verify/ff 等)技能全部不可用。');
|
|
150
|
+
console.error('');
|
|
151
|
+
console.error('━━━ 手动恢复步骤(请按顺序执行)━━━');
|
|
152
|
+
console.error('');
|
|
153
|
+
console.error('1. 验证环境');
|
|
154
|
+
console.error(' $ cbb --version');
|
|
155
|
+
console.error(' $ node --version # 需要 >= 16.7.0');
|
|
156
|
+
console.error('');
|
|
157
|
+
console.error('2. 若 cbb 未安装,先安装');
|
|
158
|
+
console.error(' $ npm install -g @jspg-ai/coding-bb');
|
|
159
|
+
console.error('');
|
|
160
|
+
console.error('3. 进入 worktree 并初始化 AI 配置');
|
|
161
|
+
console.error(' $ cd "' + wtPath + '"');
|
|
162
|
+
console.error(' $ cbb init --yes');
|
|
163
|
+
console.error('');
|
|
164
|
+
console.error('4. 重新执行 init-worktree');
|
|
165
|
+
console.error(' 在 ai-workspace 根目录重跑,会跳过已初始化的部分');
|
|
166
|
+
console.error(' (用 .cbb/.managed-by-cbb 判重)');
|
|
167
|
+
console.error('');
|
|
168
|
+
console.error('━━━ 常见失败原因对照 ━━━');
|
|
169
|
+
console.error(' • 0xc0000142 DLL 初始化失败 → 退出 Qoder 后重试,或到系统终端执行');
|
|
170
|
+
console.error(' • npm ERR! network → 检查代理:npm config get registry');
|
|
171
|
+
console.error(' • EACCES permission denied → 关闭杀毒软件或用管理员终端');
|
|
172
|
+
console.error(' • npm login 401 → token 过期,重新 npm login');
|
|
173
|
+
console.log('---');
|
|
174
|
+
console.log('AI_INIT_FAILED');
|
|
175
|
+
process.exit(2);
|
|
176
|
+
}
|
|
177
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-init-worktree 读取并校验 CONFIG.json
|
|
5
|
+
*
|
|
6
|
+
* 退出码:
|
|
7
|
+
* 0 - 成功,输出应用列表 JSON
|
|
8
|
+
* 1 - 解析失败(JSON 格式错误、文件不存在等)
|
|
9
|
+
* 2 - apps 为空数组
|
|
10
|
+
*
|
|
11
|
+
* 输出格式:
|
|
12
|
+
* CONFIG_OK {json}
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const configPath = path.join(process.cwd(), 'CONFIG.json');
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(configPath)) {
|
|
21
|
+
console.error('❌ CONFIG.json 不存在');
|
|
22
|
+
console.error(' 请先在 cbb setup 阶段创建,或手动配置');
|
|
23
|
+
console.error('---');
|
|
24
|
+
console.error('CONFIG_MISSING');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let config;
|
|
29
|
+
try {
|
|
30
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('❌ CONFIG.json 解析失败:', e.message);
|
|
33
|
+
console.error(' 请检查 JSON 格式');
|
|
34
|
+
console.error('---');
|
|
35
|
+
console.error('CONFIG_INVALID');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const apps = config.apps || [];
|
|
40
|
+
if (apps.length === 0) {
|
|
41
|
+
console.error('❌ CONFIG.json 中 apps 为空');
|
|
42
|
+
console.error(' 请编辑 CONFIG.json 配置关联应用');
|
|
43
|
+
console.error('---');
|
|
44
|
+
console.error('CONFIG_EMPTY');
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 校验每个 app 必填字段
|
|
49
|
+
for (const app of apps) {
|
|
50
|
+
if (!app.name) {
|
|
51
|
+
console.error('❌ 应用缺少 name 字段');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
if (!app.repo) {
|
|
55
|
+
console.error('❌ 应用 ' + app.name + ' 缺少 repo 字段');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// side 缺失默认 back
|
|
59
|
+
if (!app.side) {
|
|
60
|
+
app.side = 'back';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('✅ CONFIG.json 有效(' + apps.length + ' 个应用)');
|
|
65
|
+
console.log('---');
|
|
66
|
+
console.log('CONFIG_OK', JSON.stringify(apps));
|
|
67
|
+
process.exit(0);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require('./silence-popup');
|
|
2
|
+
/**
|
|
3
|
+
* openspec-init-worktree 推送并建立 upstream
|
|
4
|
+
*
|
|
5
|
+
* 薄壳脚本:核心 push 逻辑委托给同目录的 push-core.js(共享脚本,源仓库真源在 _shared/scripts/)。
|
|
6
|
+
* 本脚本只负责 CLI 入口编排(编排 init-worktree 关注的 workspace + .code/<app>/ 列表)。
|
|
7
|
+
*
|
|
8
|
+
* Usage: node push-branches.js <ws_root> <branch>
|
|
9
|
+
*
|
|
10
|
+
* 退出码:
|
|
11
|
+
* 0 - 全部推送成功
|
|
12
|
+
* 1 - 参数错误(CONFIG 缺失或 argv 不足)
|
|
13
|
+
* 2 - 部分/全部失败(AI agent 必须阻塞流程)
|
|
14
|
+
*
|
|
15
|
+
* 新场景(开发完成后跨 worktree 推送)请使用 openspec-push-worktrees/scripts/push-worktrees.js。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { pushSingle, classifyError, collectRemoteMetadata } = require('./push-core');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
function loadAppsFromConfig(wsRoot) {
|
|
23
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
24
|
+
if (!fs.existsSync(configPath)) return null;
|
|
25
|
+
try {
|
|
26
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
27
|
+
return Array.isArray(config.apps) ? config.apps : null;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 编排:对 workspace + 所有 app 仓库逐个 push
|
|
35
|
+
* @param {{ wsRoot: string, branch: string, apps?: Array, execSyncImpl?: Function }} opts
|
|
36
|
+
* @returns {Array}
|
|
37
|
+
*/
|
|
38
|
+
function executePush(opts) {
|
|
39
|
+
const wsRoot = opts.wsRoot;
|
|
40
|
+
const branch = opts.branch;
|
|
41
|
+
const apps = opts.apps != null ? opts.apps : loadAppsFromConfig(wsRoot);
|
|
42
|
+
if (apps == null) {
|
|
43
|
+
throw new Error('CONFIG.json missing or apps empty');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const results = [];
|
|
47
|
+
// workspace
|
|
48
|
+
results.push(pushSingle(wsRoot, 'workspace', branch, { execSyncImpl: opts.execSyncImpl }));
|
|
49
|
+
// apps
|
|
50
|
+
for (const app of apps) {
|
|
51
|
+
const appPath = path.join(wsRoot, '.code', app.name);
|
|
52
|
+
results.push(pushSingle(appPath, app.name, branch, { execSyncImpl: opts.execSyncImpl }));
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 导出供单测(require 上下文不会触发 argv 检查)
|
|
58
|
+
// 兼容历史:test/push-branches.test.js 仍可 require 本文件获得 classifyError/pushSingle/collectRemoteMetadata
|
|
59
|
+
module.exports = { classifyError, pushSingle, collectRemoteMetadata, executePush };
|
|
60
|
+
|
|
61
|
+
function main() {
|
|
62
|
+
const wsRoot = process.argv[2];
|
|
63
|
+
const branch = process.argv[3];
|
|
64
|
+
if (!wsRoot || !branch) {
|
|
65
|
+
console.error('❌ 用法:node push-branches.js <ws_root> <branch>');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const apps = loadAppsFromConfig(wsRoot);
|
|
70
|
+
if (apps == null) {
|
|
71
|
+
console.error('❌ CONFIG.json 缺失或 apps 为空');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log('=== 推送并建立 upstream ===');
|
|
76
|
+
console.log('当前分支: ' + branch);
|
|
77
|
+
|
|
78
|
+
const results = executePush({ wsRoot, branch, apps });
|
|
79
|
+
|
|
80
|
+
// 人类可读报告
|
|
81
|
+
for (const r of results) {
|
|
82
|
+
if (r.status === 'pushed') {
|
|
83
|
+
console.log('✅ ' + r.name + ': 已推送 ' + r.upstream);
|
|
84
|
+
} else if (r.status === 'remote-exists') {
|
|
85
|
+
console.log('⚠️ ' + r.name + ': 远程分支已存在(需用户决策)');
|
|
86
|
+
} else {
|
|
87
|
+
console.error('❌ ' + r.name + ': 推送失败 - ' + (r.message || r.error));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hasFailure = results.some(r => r.status === 'failed' || r.status === 'remote-exists');
|
|
92
|
+
|
|
93
|
+
console.log('---');
|
|
94
|
+
console.log('PUSH_RESULT', JSON.stringify(results));
|
|
95
|
+
process.exit(hasFailure ? 2 : 0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// CLI 入口(仅直接调用本脚本时执行,require 时跳过)
|
|
99
|
+
if (require.main === module) {
|
|
100
|
+
main();
|
|
101
|
+
}
|