@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,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-close-worktree 删除分支 (按用户选择)
|
|
5
|
+
*/
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const wsRoot = process.argv[2];
|
|
11
|
+
const branch = process.argv[3];
|
|
12
|
+
const mode = process.argv[4];
|
|
13
|
+
|
|
14
|
+
if (!wsRoot || !branch || !mode) {
|
|
15
|
+
console.error('Usage: node delete-branches.js <ws_root> <branch> <mode>');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (mode === 'keep') {
|
|
20
|
+
console.log('Keep branch, skip delete');
|
|
21
|
+
console.log('---');
|
|
22
|
+
console.log('BRANCH_DELETE_RESULT', JSON.stringify({ mode: 'keep', deleted: [] }));
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (mode !== 'local' && mode !== 'local_remote') {
|
|
27
|
+
console.error('mode must be keep / local / local_remote');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function run(cmd, cwd, env) {
|
|
32
|
+
try {
|
|
33
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...(env || {}) } }).trim() };
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function detectMain(repoPath) {
|
|
40
|
+
const remote = run('git remote', repoPath);
|
|
41
|
+
if (!remote.ok || !remote.out) return 'main';
|
|
42
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
43
|
+
const symbolic = run('git symbolic-ref "refs/remotes/' + r + '/HEAD"', repoPath);
|
|
44
|
+
if (symbolic.ok && symbolic.out) {
|
|
45
|
+
return symbolic.out.replace('refs/remotes/' + r + '/', '');
|
|
46
|
+
}
|
|
47
|
+
return 'main';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mergedTo(repoPath, from, to) {
|
|
51
|
+
// 0=merged, 1=unmerged, 2=to-ref-missing
|
|
52
|
+
if (!run('git rev-parse --verify --quiet "' + from + '"', repoPath).ok) return 1;
|
|
53
|
+
if (!run('git rev-parse --verify --quiet "' + to + '"', repoPath).ok) return 2;
|
|
54
|
+
if (run('git merge-base --is-ancestor "' + from + '" "' + to + '" && echo Y', repoPath).out === 'Y') return 0;
|
|
55
|
+
const cherry = run('git cherry "' + to + '" "' + from + '" | grep -q "^+" && echo N || echo Y', repoPath);
|
|
56
|
+
if (cherry.out === 'N') return 1;
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mergedToMain(repoPath, branchName, main) {
|
|
61
|
+
if (mergedTo(repoPath, branchName, main) === 0) return true;
|
|
62
|
+
if (mergedTo(repoPath, branchName, 'origin/' + main) === 0) return true;
|
|
63
|
+
// release-* transit
|
|
64
|
+
const releaseList = run('git for-each-ref --format="%(refname:short)" "refs/heads/release-*" "refs/remotes/origin/release-*"', repoPath);
|
|
65
|
+
if (releaseList.ok && releaseList.out) {
|
|
66
|
+
const releases = releaseList.out.split(/\r?\n/).filter(Boolean).sort();
|
|
67
|
+
for (const rel of releases) {
|
|
68
|
+
if (mergedTo(repoPath, branchName, rel) === 0) {
|
|
69
|
+
if (mergedTo(repoPath, rel, main) === 0) return true;
|
|
70
|
+
if (mergedTo(repoPath, rel, 'origin/' + main) === 0) return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function deleteBranch(repoPath, name) {
|
|
78
|
+
const checkLocal = run('git branch --list "' + branch + '"', repoPath);
|
|
79
|
+
if (checkLocal.ok && checkLocal.out) {
|
|
80
|
+
const main = detectMain(repoPath);
|
|
81
|
+
const merged = main ? mergedToMain(repoPath, branch, main) : false;
|
|
82
|
+
let flag = merged ? '-d' : '-D';
|
|
83
|
+
const del = run('git branch ' + flag + ' "' + branch + '"', repoPath);
|
|
84
|
+
if (del.ok) {
|
|
85
|
+
console.log('OK ' + name + ' local branch deleted (' + flag + ', ' + (merged ? 'merged' : 'unmerged') + ')');
|
|
86
|
+
} else {
|
|
87
|
+
if (flag === '-d') {
|
|
88
|
+
const fallback = run('git branch -D "' + branch + '"', repoPath);
|
|
89
|
+
if (fallback.ok) {
|
|
90
|
+
console.log('OK ' + name + ' local branch deleted (squash merge, fallback -D)');
|
|
91
|
+
} else {
|
|
92
|
+
console.error('FAIL ' + name + ' local branch delete failed');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
console.error('FAIL ' + name + ' local branch delete failed');
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
console.log('Skip ' + name + ': no local branch ' + branch);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (mode === 'local_remote') {
|
|
105
|
+
const remote = run('git remote', repoPath);
|
|
106
|
+
if (remote.ok && remote.out) {
|
|
107
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
108
|
+
const pushDel = run('git push "' + r + '" --delete "' + branch + '"', repoPath, { GIT_TERMINAL_PROMPT: '0' });
|
|
109
|
+
if (pushDel.ok) {
|
|
110
|
+
console.log('OK ' + name + ' remote branch deleted');
|
|
111
|
+
} else {
|
|
112
|
+
console.log('Skip ' + name + ': remote has no ' + branch);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
120
|
+
let config = {};
|
|
121
|
+
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
122
|
+
|
|
123
|
+
const apps = config.apps || [];
|
|
124
|
+
|
|
125
|
+
console.log('=== Delete Workspace Branch ===');
|
|
126
|
+
deleteBranch(wsRoot, 'workspace');
|
|
127
|
+
|
|
128
|
+
console.log('=== Delete App Branches ===');
|
|
129
|
+
for (const app of apps) {
|
|
130
|
+
const appPath = path.join(wsRoot, '.code', app.name);
|
|
131
|
+
deleteBranch(appPath, app.name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log('---');
|
|
135
|
+
console.log('BRANCH_DELETE_RESULT', JSON.stringify({ mode, ok: true }));
|
|
136
|
+
process.exit(0);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-close-worktree 发现关联应用 worktree
|
|
5
|
+
*
|
|
6
|
+
* 用法:node discover-apps.js <ws_root> <branch>
|
|
7
|
+
*
|
|
8
|
+
* 从 CONFIG.json 读取应用列表,扫描 .worktrees/worktree-<branch>/ 实际存在的子目录。
|
|
9
|
+
*
|
|
10
|
+
* 输出末行:DISCOVER_RESULT { workspace: <wt_path>, apps: [{name, repo, side, wt_path}] }
|
|
11
|
+
*
|
|
12
|
+
* 退出码:
|
|
13
|
+
* 0 - 找到至少一个 worktree
|
|
14
|
+
* 1 - 参数错误
|
|
15
|
+
* 2 - 未找到任何 worktree
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const wsRoot = process.argv[2];
|
|
22
|
+
const branch = process.argv[3];
|
|
23
|
+
|
|
24
|
+
if (!wsRoot || !branch) {
|
|
25
|
+
console.error('❌ 用法:node discover-apps.js <ws_root> <branch>');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
console.error('❌ CONFIG.json 不存在:' + configPath);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let config;
|
|
36
|
+
try {
|
|
37
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('❌ CONFIG.json 解析失败:' + e.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const apps = config.apps || [];
|
|
44
|
+
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(wtDir)) {
|
|
47
|
+
console.error('❌ 目标 workspace worktree 不存在:' + wtDir);
|
|
48
|
+
console.log('---');
|
|
49
|
+
console.log('DISCOVER_RESULT', JSON.stringify({ workspace: null, apps: [] }));
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const found = [];
|
|
54
|
+
for (const app of apps) {
|
|
55
|
+
const appWt = path.join(wtDir, app.name);
|
|
56
|
+
if (fs.existsSync(appWt) && fs.existsSync(path.join(appWt, '.git'))) {
|
|
57
|
+
console.log('✅ ' + app.name + ':存在嵌套 worktree(' + appWt + ')');
|
|
58
|
+
found.push({
|
|
59
|
+
name: app.name,
|
|
60
|
+
repo: app.repo,
|
|
61
|
+
side: app.side || 'back',
|
|
62
|
+
wt_path: appWt,
|
|
63
|
+
app_path: path.join(wsRoot, '.code', app.name),
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
console.log('⏭️ ' + app.name + ':无关联 worktree,跳过');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('---');
|
|
71
|
+
console.log('DISCOVER_RESULT', JSON.stringify({
|
|
72
|
+
workspace: wtDir,
|
|
73
|
+
workspace_app: wsRoot,
|
|
74
|
+
apps: found,
|
|
75
|
+
}));
|
|
76
|
+
process.exit(0);
|
|
@@ -0,0 +1,96 @@
|
|
|
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(' 请先执行 openspec-init-worktree 创建 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(' 请先执行 openspec-init-worktree 创建 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);
|
|
@@ -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,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-close-worktree 移除 Worktree(三层降级)
|
|
5
|
+
*
|
|
6
|
+
* 用法:node remove-worktrees.js <ws_root> <branch> <force>
|
|
7
|
+
* force: "true" 启用 --force,"false" 不启用
|
|
8
|
+
*
|
|
9
|
+
* 行为:
|
|
10
|
+
* 1. 先移除各应用 worktree
|
|
11
|
+
* 2. 再移除 workspace worktree
|
|
12
|
+
* 3. 三层降级:git worktree remove → prune+rm → 仅 prune
|
|
13
|
+
*
|
|
14
|
+
* 退出码:
|
|
15
|
+
* 0 - 全部成功
|
|
16
|
+
* 1 - 参数错误
|
|
17
|
+
* 2 - 部分失败
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
const wsRoot = process.argv[2];
|
|
25
|
+
const branch = process.argv[3];
|
|
26
|
+
const forceStr = process.argv[4];
|
|
27
|
+
|
|
28
|
+
if (!wsRoot || !branch || !forceStr) {
|
|
29
|
+
console.error('❌ 用法:node remove-worktrees.js <ws_root> <branch> <force>');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const force = forceStr === 'true';
|
|
34
|
+
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
35
|
+
|
|
36
|
+
function run(cmd, cwd) {
|
|
37
|
+
try {
|
|
38
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] }).trim() };
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function removePhysicalDir(dir) {
|
|
45
|
+
// Windows 用 rmdir /s /q,Unix 用 rm -rf
|
|
46
|
+
if (process.platform === 'win32') {
|
|
47
|
+
return run('rmdir /s /q "' + dir + '"');
|
|
48
|
+
}
|
|
49
|
+
return run('rm -rf "' + dir + '"');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lockedDirs = [];
|
|
53
|
+
const results = [];
|
|
54
|
+
|
|
55
|
+
function removeWt(repoPath, wtPath, name) {
|
|
56
|
+
if (!fs.existsSync(wtPath)) {
|
|
57
|
+
console.log('⏭️ ' + name + ':worktree 不存在,跳过');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const forceFlag = force ? ' --force' : '';
|
|
62
|
+
// 第 1 层:git worktree remove
|
|
63
|
+
const r1 = run('git worktree remove' + forceFlag + ' "' + wtPath + '"', repoPath);
|
|
64
|
+
if (r1.ok) {
|
|
65
|
+
console.log('✅ ' + name + ' worktree 已移除');
|
|
66
|
+
results.push({ name, status: 'removed' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 第 2 层:prune + 物理目录清理(解耦)
|
|
71
|
+
run('git worktree prune', repoPath);
|
|
72
|
+
const r2 = removePhysicalDir(wtPath);
|
|
73
|
+
if (r2.ok) {
|
|
74
|
+
console.log('✅ ' + name + ' worktree 已移除(元数据 prune + 物理目录清理)');
|
|
75
|
+
results.push({ name, status: 'prune-rm' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 第 3 层:目录被锁
|
|
80
|
+
lockedDirs.push(wtPath);
|
|
81
|
+
console.log('⏳ ' + name + ' 元数据已清理,物理目录被锁定(将在下次 init 时自动清理)');
|
|
82
|
+
results.push({ name, status: 'locked' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 1. 应用 worktrees
|
|
86
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
87
|
+
let config = {};
|
|
88
|
+
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
89
|
+
|
|
90
|
+
const apps = config.apps || [];
|
|
91
|
+
console.log('=== 移除应用 Worktrees ===');
|
|
92
|
+
for (const app of apps) {
|
|
93
|
+
const appWt = path.join(wtDir, app.name);
|
|
94
|
+
const appPath = path.join(wsRoot, '.code', app.name);
|
|
95
|
+
removeWt(appPath, appWt, app.name);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. workspace worktree
|
|
99
|
+
console.log('=== 移除 Workspace Worktree ===');
|
|
100
|
+
removeWt(wsRoot, wtDir, 'workspace');
|
|
101
|
+
|
|
102
|
+
// 3. 兜底:清理所有仓库的陈旧 worktree 元数据
|
|
103
|
+
for (const app of apps) {
|
|
104
|
+
run('git worktree prune', path.join(wsRoot, '.code', app.name));
|
|
105
|
+
}
|
|
106
|
+
run('git worktree prune', wsRoot);
|
|
107
|
+
|
|
108
|
+
// 报告
|
|
109
|
+
if (lockedDirs.length > 0) {
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log('⏳ 以下目录被外部进程占用(IDE 等),git 元数据已清理,物理目录仍保留:');
|
|
112
|
+
lockedDirs.forEach(d => console.log(' - ' + d));
|
|
113
|
+
console.log(' → 请关闭占用这些目录的程序(如 IDE)后手动删除;或下次 init-worktree 时自动清理');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('---');
|
|
117
|
+
console.log('REMOVE_RESULT', JSON.stringify({
|
|
118
|
+
results,
|
|
119
|
+
locked: lockedDirs,
|
|
120
|
+
has_locked: lockedDirs.length > 0,
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
// 退出码:有 locked 视为部分成功(不报错,因为元数据已清理)
|
|
124
|
+
process.exit(0);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('./silence-popup');
|
|
3
|
+
/**
|
|
4
|
+
* openspec-close-worktree 安全检查 (合并判定 + 未提交修改 + 远程推送状态)
|
|
5
|
+
*/
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const wsRoot = process.argv[2];
|
|
11
|
+
const branch = process.argv[3];
|
|
12
|
+
|
|
13
|
+
if (!wsRoot || !branch) {
|
|
14
|
+
console.error('Usage: node safety-check.js <ws_root> <branch>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function run(cmd, cwd, env) {
|
|
19
|
+
try {
|
|
20
|
+
return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...(env || {}) } }).trim() };
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return { ok: false, err: (e.stderr || e.message || String(e)).trim() };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function detectMain(repoPath) {
|
|
27
|
+
const remote = run('git remote', repoPath);
|
|
28
|
+
if (!remote.ok || !remote.out) return '';
|
|
29
|
+
const r = remote.out.split(/\r?\n/)[0];
|
|
30
|
+
const symbolic = run('git symbolic-ref "refs/remotes/' + r + '/HEAD"', repoPath);
|
|
31
|
+
if (symbolic.ok && symbolic.out) {
|
|
32
|
+
return symbolic.out.replace('refs/remotes/' + r + '/', '');
|
|
33
|
+
}
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mergedTo(repoPath, from, to) {
|
|
38
|
+
if (!run('git rev-parse --verify --quiet "' + from + '"', repoPath).ok) return 1;
|
|
39
|
+
if (!run('git rev-parse --verify --quiet "' + to + '"', repoPath).ok) return 2;
|
|
40
|
+
if (run('git merge-base --is-ancestor "' + from + '" "' + to + '" && echo Y', repoPath).out === 'Y') return 0;
|
|
41
|
+
const cherry = run('git cherry "' + to + '" "' + from + '" | grep -q "^+" && echo N || echo Y', repoPath);
|
|
42
|
+
if (cherry.out === 'N') return 1;
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mergedToMain(repoPath, branchName, main) {
|
|
47
|
+
if (mergedTo(repoPath, branchName, main) === 0) return 'main';
|
|
48
|
+
if (mergedTo(repoPath, branchName, 'origin/' + main) === 0) return 'origin/' + main;
|
|
49
|
+
const releaseList = run('git for-each-ref --format="%(refname:short)" "refs/heads/release-*" "refs/remotes/origin/release-*"', repoPath);
|
|
50
|
+
if (releaseList.ok && releaseList.out) {
|
|
51
|
+
const releases = releaseList.out.split(/\r?\n/).filter(Boolean).sort();
|
|
52
|
+
for (const rel of releases) {
|
|
53
|
+
if (mergedTo(repoPath, branchName, rel) === 0) {
|
|
54
|
+
if (mergedTo(repoPath, rel, main) === 0) return 'main-via-' + rel;
|
|
55
|
+
if (mergedTo(repoPath, rel, 'origin/' + main) === 0) return 'origin/' + main + '-via-' + rel;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function checkRepo(name, repoPath, wtPath) {
|
|
63
|
+
console.log('=== ' + name + ' ===');
|
|
64
|
+
if (!fs.existsSync(wtPath)) {
|
|
65
|
+
console.log('(no worktree, skip)');
|
|
66
|
+
console.log('');
|
|
67
|
+
return { name, wt: null, skipped: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3.0 fetch
|
|
71
|
+
const remote = run('git remote', repoPath);
|
|
72
|
+
const remoteName = remote.ok ? (remote.out.split(/\r?\n/)[0] || '') : '';
|
|
73
|
+
let fetchOk = false;
|
|
74
|
+
if (remoteName) {
|
|
75
|
+
const fetch = run('git fetch --quiet "' + remoteName + '"', repoPath, { GIT_TERMINAL_PROMPT: '0' });
|
|
76
|
+
if (fetch.ok) {
|
|
77
|
+
fetchOk = true;
|
|
78
|
+
} else {
|
|
79
|
+
console.log('WARN fetch failed (offline/permission), using possibly stale refs');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3.1 uncommitted
|
|
84
|
+
const status = run('git status --porcelain', wtPath);
|
|
85
|
+
const dirtyFiles = status.ok && status.out ? status.out.split(/\r?\n/).filter(Boolean) : [];
|
|
86
|
+
if (dirtyFiles.length > 0) {
|
|
87
|
+
console.log('FAIL uncommitted changes:');
|
|
88
|
+
dirtyFiles.slice(0, 10).forEach(f => console.log(' ' + f));
|
|
89
|
+
} else {
|
|
90
|
+
console.log('OK no uncommitted changes');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3.2 merge
|
|
94
|
+
const main = detectMain(repoPath);
|
|
95
|
+
let merged = null;
|
|
96
|
+
if (main) {
|
|
97
|
+
merged = mergedToMain(repoPath, branch, main);
|
|
98
|
+
if (merged) {
|
|
99
|
+
console.log('OK merged into ' + merged);
|
|
100
|
+
} else {
|
|
101
|
+
console.log('WARN not merged into ' + main);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
console.log('WARN cannot detect main branch');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3.3 remote
|
|
108
|
+
let remoteExists = null;
|
|
109
|
+
if (fetchOk && remoteName) {
|
|
110
|
+
const check = run('git rev-parse --verify --quiet "refs/remotes/' + remoteName + '/' + branch + '"', repoPath);
|
|
111
|
+
if (check.ok) {
|
|
112
|
+
remoteExists = true;
|
|
113
|
+
console.log('OK remote has branch');
|
|
114
|
+
} else {
|
|
115
|
+
remoteExists = false;
|
|
116
|
+
console.log('WARN remote has no branch (local only)');
|
|
117
|
+
}
|
|
118
|
+
} else if (remoteName) {
|
|
119
|
+
const ls = run('git ls-remote --heads "' + remoteName + '" "' + branch + '"', repoPath);
|
|
120
|
+
if (ls.ok && ls.out) {
|
|
121
|
+
remoteExists = true;
|
|
122
|
+
console.log('OK remote has branch');
|
|
123
|
+
} else {
|
|
124
|
+
remoteExists = false;
|
|
125
|
+
console.log('WARN remote has no branch (local only)');
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.log('WARN no remote');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2.4 unpushed commits (ahead of upstream)
|
|
132
|
+
// 仅在已建立 upstream(fetch_ok + remote_name 存在 + branch 在远程存在)时检测。
|
|
133
|
+
// 无 upstream 时 ahead 必然为 0(@{u} 解析失败),跳过避免误导。
|
|
134
|
+
let unpushedCount = 0;
|
|
135
|
+
if (fetchOk && remoteName && remoteExists === true) {
|
|
136
|
+
const ahead = run('git rev-list --count "@{u}..HEAD"', wtPath, { GIT_TERMINAL_PROMPT: '0' });
|
|
137
|
+
if (ahead.ok && ahead.out !== '') {
|
|
138
|
+
unpushedCount = parseInt(ahead.out, 10) || 0;
|
|
139
|
+
if (unpushedCount > 0) {
|
|
140
|
+
console.log('WARN ' + unpushedCount + ' unpushed commit(s) ahead of ' + remoteName + '/' + branch);
|
|
141
|
+
} else {
|
|
142
|
+
console.log('OK no unpushed commits');
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// @{u} 不存在(本地分支没建立 upstream)
|
|
146
|
+
unpushedCount = -1;
|
|
147
|
+
console.log('WARN no upstream tracking branch (run openspec-push-worktrees to set up)');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
console.log('');
|
|
151
|
+
|
|
152
|
+
return { name, wt: wtPath, fetch_ok: fetchOk, dirty_files: dirtyFiles, merged, main, remote_exists: remoteExists, remote: remoteName, unpushed_count: unpushedCount };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const configPath = path.join(wsRoot, 'CONFIG.json');
|
|
156
|
+
let config = {};
|
|
157
|
+
try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) {}
|
|
158
|
+
|
|
159
|
+
const apps = config.apps || [];
|
|
160
|
+
const wtDir = path.join(wsRoot, '.worktrees', 'worktree-' + branch);
|
|
161
|
+
|
|
162
|
+
const results = [];
|
|
163
|
+
|
|
164
|
+
results.push(checkRepo('workspace', wsRoot, wtDir));
|
|
165
|
+
|
|
166
|
+
for (const app of apps) {
|
|
167
|
+
const appWt = path.join(wtDir, app.name);
|
|
168
|
+
const appPath = path.join(wsRoot, '.code', app.name);
|
|
169
|
+
results.push(checkRepo(app.name, appPath, appWt));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log('---');
|
|
173
|
+
console.log('SAFETY_RESULT', JSON.stringify(results));
|
|
174
|
+
process.exit(0);
|