@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.
Files changed (183) hide show
  1. package/README.md +41 -0
  2. package/cbb/dev-standards/rules/cbb-ai-behavior.md +104 -0
  3. package/cbb/dev-standards/rules/cbb-coding-rule.md +50 -0
  4. package/cbb/dev-standards/rules/cbb-priority.md +56 -0
  5. package/cbb/dev-standards/skills/architecture-specs/SKILL.md +129 -0
  6. package/cbb/dev-standards/skills/coding-specs/SKILL.md +376 -0
  7. package/cbb/dev-standards/skills/coding-specs/references/concurrency.md +53 -0
  8. package/cbb/dev-standards/skills/coding-specs/references/config-center.md +16 -0
  9. package/cbb/dev-standards/skills/coding-specs/references/distributed.md +42 -0
  10. package/cbb/dev-standards/skills/coding-specs/references/es-coding.md +60 -0
  11. package/cbb/dev-standards/skills/coding-specs/references/scheduled-task.md +25 -0
  12. package/cbb/dev-standards/skills/coding-specs/references/security.md +11 -0
  13. package/cbb/dev-standards/skills/coding-specs/references/unit-testing.md +43 -0
  14. package/cbb/dev-standards/skills/es-design-specs/SKILL.md +104 -0
  15. package/cbb/dev-standards/skills/mysql-design-specs/SKILL.md +89 -0
  16. package/cbb/lib/install/claude-code.js +20 -0
  17. package/cbb/lib/install/cleanup.js +64 -0
  18. package/cbb/lib/install/init.js +1875 -0
  19. package/cbb/lib/install/qoder.js +20 -0
  20. package/cbb/lib/install/workspaces.js +232 -0
  21. package/cbb/lib/openspec/index.js +554 -0
  22. package/cbb/lib/superpowers/index.js +265 -0
  23. package/cbb/lib/utils/check-update.js +147 -0
  24. package/cbb/lib/utils/checkbox.js +383 -0
  25. package/cbb/lib/utils/gitignore.js +69 -0
  26. package/cbb/lib/utils/output.js +64 -0
  27. package/cbb/lib/utils/settings.js +119 -0
  28. package/cbb/lib/utils/version.js +135 -0
  29. package/cbb/lib/wiki/api-client.js +358 -0
  30. package/cbb/lib/wiki/cli.js +427 -0
  31. package/cbb/lib/wiki/convert.js +464 -0
  32. package/cbb/lib/wiki/index.js +218 -0
  33. package/cbb/lib/wiki/mermaid-guard.js +103 -0
  34. package/cbb/lib/wiki/split.js +131 -0
  35. package/cbb/tools/cbb-decompile-jar/SKILL.md +220 -0
  36. package/cbb/tools/cbb-decompile-jar/scripts/decompile.py +863 -0
  37. package/cbb/tools/cbb-design-to-wiki/SKILL.md +180 -0
  38. package/cbb/tools/cbb-mvn-guide/SKILL.md +281 -0
  39. package/cbb/tools/cbb-mvn-guide/scripts/mvn_jdk_manager.py +378 -0
  40. package/cbb/tools/cbb-mvn-guide/scripts/mvn_pom_jdk_reader.py +197 -0
  41. package/cbb/tools/cbb-plantuml-authoring/SKILL.md +448 -0
  42. package/cbb/tools/cbb-plantuml-authoring/references/drawing-templates.md +479 -0
  43. package/cbb/tools/cbb-wiki-ops/SKILL.md +134 -0
  44. package/cbb/tools/cbb-wiki-ops/scripts/check-auth.js +44 -0
  45. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +96 -0
  46. package/cbb/worktrees/_shared/scripts/find-workspace-root.js +67 -0
  47. package/cbb/worktrees/_shared/scripts/push-core.js +136 -0
  48. package/cbb/worktrees/_shared/scripts/silence-popup.js +23 -0
  49. package/cbb/worktrees/commands/close.md +64 -0
  50. package/cbb/worktrees/commands/extend.md +65 -0
  51. package/cbb/worktrees/commands/init.md +55 -0
  52. package/cbb/worktrees/commands/push.md +42 -0
  53. package/cbb/worktrees/skills/openspec-close-worktree/SKILL.md +449 -0
  54. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-env.js +76 -0
  55. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-unarchived.js +48 -0
  56. package/cbb/worktrees/skills/openspec-close-worktree/scripts/delete-branches.js +136 -0
  57. package/cbb/worktrees/skills/openspec-close-worktree/scripts/discover-apps.js +76 -0
  58. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +96 -0
  59. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-workspace-root.js +67 -0
  60. package/cbb/worktrees/skills/openspec-close-worktree/scripts/remove-worktrees.js +124 -0
  61. package/cbb/worktrees/skills/openspec-close-worktree/scripts/safety-check.js +174 -0
  62. package/cbb/worktrees/skills/openspec-close-worktree/scripts/silence-popup.js +23 -0
  63. package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +390 -0
  64. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +95 -0
  65. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +98 -0
  66. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +135 -0
  67. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-target-worktree.js +96 -0
  68. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-workspace-root.js +67 -0
  69. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +150 -0
  70. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +88 -0
  71. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/push-core.js +136 -0
  72. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/silence-popup.js +23 -0
  73. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +93 -0
  74. package/cbb/worktrees/skills/openspec-init-worktree/SKILL.md +539 -0
  75. package/cbb/worktrees/skills/openspec-init-worktree/scripts/auto-open.js +136 -0
  76. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env-deep.js +87 -0
  77. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env.js +90 -0
  78. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-repos.js +171 -0
  79. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js +103 -0
  80. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-worktrees.js +154 -0
  81. package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +67 -0
  82. package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +93 -0
  83. package/cbb/worktrees/skills/openspec-init-worktree/scripts/install-ai.js +177 -0
  84. package/cbb/worktrees/skills/openspec-init-worktree/scripts/parse-config.js +67 -0
  85. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-branches.js +101 -0
  86. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +136 -0
  87. package/cbb/worktrees/skills/openspec-init-worktree/scripts/silence-popup.js +23 -0
  88. package/cbb/worktrees/skills/openspec-init-worktree/scripts/sync-repos.js +95 -0
  89. package/cbb/worktrees/skills/openspec-init-worktree/scripts/update-gitignore.js +134 -0
  90. package/cbb/worktrees/skills/openspec-push-worktrees/SKILL.md +307 -0
  91. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/commit-worktrees.js +269 -0
  92. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-target-worktree.js +96 -0
  93. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-workspace-root.js +67 -0
  94. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-core.js +136 -0
  95. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-worktrees.js +159 -0
  96. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +23 -0
  97. package/config/cbb.yaml +6 -0
  98. package/config/config.sample.json +16 -0
  99. package/config/openspec/config.yaml +28 -0
  100. package/config/openspec/schemas/spec-driven/schema.yaml +247 -0
  101. package/config/openspec/schemas/spec-driven/templates/design.md +475 -0
  102. package/config/openspec/schemas/spec-driven/templates/proposal.md +58 -0
  103. package/config/openspec/schemas/spec-driven/templates/spec.md +8 -0
  104. package/config/openspec/schemas/spec-driven/templates/tasks.md +9 -0
  105. package/config/workspaces.json +15 -0
  106. package/openspec/.version +6 -0
  107. package/openspec/commands/apply.md +182 -0
  108. package/openspec/commands/archive.md +223 -0
  109. package/openspec/commands/bulk-archive.md +334 -0
  110. package/openspec/commands/continue.md +112 -0
  111. package/openspec/commands/explore.md +206 -0
  112. package/openspec/commands/ff.md +111 -0
  113. package/openspec/commands/new.md +70 -0
  114. package/openspec/commands/onboard.md +555 -0
  115. package/openspec/commands/propose.md +157 -0
  116. package/openspec/commands/sync.md +256 -0
  117. package/openspec/commands/update.md +85 -0
  118. package/openspec/commands/verify.md +169 -0
  119. package/openspec/skills/openspec-apply-change/SKILL.md +187 -0
  120. package/openspec/skills/openspec-archive-change/SKILL.md +181 -0
  121. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -0
  122. package/openspec/skills/openspec-continue-change/SKILL.md +117 -0
  123. package/openspec/skills/openspec-explore/SKILL.md +342 -0
  124. package/openspec/skills/openspec-ff-change/SKILL.md +116 -0
  125. package/openspec/skills/openspec-new-change/SKILL.md +76 -0
  126. package/openspec/skills/openspec-onboard/SKILL.md +560 -0
  127. package/openspec/skills/openspec-propose/SKILL.md +162 -0
  128. package/openspec/skills/openspec-sync-specs/SKILL.md +261 -0
  129. package/openspec/skills/openspec-update-change/SKILL.md +90 -0
  130. package/openspec/skills/openspec-verify-change/SKILL.md +174 -0
  131. package/package.json +46 -0
  132. package/superpowers/.version +6 -0
  133. package/superpowers/skills/brainstorming/SKILL.md +250 -0
  134. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
  135. package/superpowers/skills/brainstorming/scripts/helper.js +167 -0
  136. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
  137. package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
  138. package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
  139. package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  140. package/superpowers/skills/brainstorming/visual-companion.md +299 -0
  141. package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -0
  142. package/superpowers/skills/executing-plans/SKILL.md +64 -0
  143. package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -0
  144. package/superpowers/skills/receiving-code-review/SKILL.md +205 -0
  145. package/superpowers/skills/requesting-code-review/SKILL.md +95 -0
  146. package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -0
  147. package/superpowers/skills/subagent-driven-development/SKILL.md +568 -0
  148. package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -0
  149. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -0
  150. package/superpowers/skills/subagent-driven-development/scripts/review-package +46 -0
  151. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
  152. package/superpowers/skills/subagent-driven-development/scripts/task-brief +41 -0
  153. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
  154. package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
  155. package/superpowers/skills/systematic-debugging/SKILL.md +283 -0
  156. package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  157. package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
  158. package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
  159. package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -0
  160. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
  161. package/superpowers/skills/systematic-debugging/test-academic.md +14 -0
  162. package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
  163. package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
  164. package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
  165. package/superpowers/skills/test-driven-development/SKILL.md +320 -0
  166. package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -0
  167. package/superpowers/skills/using-git-worktrees/SKILL.md +167 -0
  168. package/superpowers/skills/using-superpowers/SKILL.md +63 -0
  169. package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
  170. package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -0
  171. package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -0
  172. package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -0
  173. package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
  174. package/superpowers/skills/verification-before-completion/SKILL.md +120 -0
  175. package/superpowers/skills/writing-plans/SKILL.md +171 -0
  176. package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  177. package/superpowers/skills/writing-skills/SKILL.md +679 -0
  178. package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
  179. package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  180. package/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
  181. package/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
  182. package/superpowers/skills/writing-skills/render-graphs.js +169 -0
  183. package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Qoder 适配器
3
+ *
4
+ * 将编码规范分发到 .qoder/rules/ 目录
5
+ */
6
+
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ module.exports = {
11
+ name: 'qoder',
12
+ displayName: 'Qoder',
13
+ rulesDir: '.qoder/rules',
14
+ skillsDir: '.qoder/skills',
15
+ commandsDir: '.qoder/commands/opsx',
16
+ // 用户级 settings,写入全局生效,不污染项目目录
17
+ settingsFile: path.join(os.homedir(), '.qoder', 'settings.json'),
18
+ settingsFileIsUserLevel: true,
19
+ hookEvents: ['UserPromptSubmit'],
20
+ };
@@ -0,0 +1,232 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 业务空间配置加载与匹配工具。
5
+ *
6
+ * 数据源:包内随发的 config/workspaces.json(含 _comment 字段会被忽略)。
7
+ * 提供:
8
+ * - 加载 + 校验
9
+ * - 归一化 remote URL(兼容 SSH / HTTPS 两种写法)
10
+ * - 检测当前目录是否已拉取指定 workspace 的仓库
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ const DEFAULT_CONFIG = 'config/workspaces.json';
18
+ const SAMPLE_CONFIG = 'config/config.sample.json';
19
+
20
+ /**
21
+ * 归一化 git remote URL:提取 "namespace/repo" 形式(去掉 .git 后缀)。
22
+ * 支持 SSH / HTTPS / HTTP / 已经是 path 的形式。
23
+ * 仅接受非空字符串,其它输入(null / undefined / 非字符串 / 空串)一律返回 '',
24
+ * 由调用方决定如何处理。
25
+ */
26
+ function normalizeRepoUrl(url) {
27
+ if (typeof url !== 'string') return '';
28
+ const u = url.trim();
29
+ if (!u) return '';
30
+ const stripped = u.replace(/\.git$/i, '');
31
+ // SSH: git@host:path/to/repo
32
+ const ssh = stripped.match(/^git@[^:\s]+:(.+)$/);
33
+ if (ssh) return ssh[1];
34
+ // HTTPS/HTTP: scheme://host/path/to/repo
35
+ const http = stripped.match(/^https?:\/\/[^/]+\/(.+)$/i);
36
+ if (http) return http[1];
37
+ // 已是 path 形式(兼容裸 "group/repo")
38
+ return stripped.replace(/^\/+/, '');
39
+ }
40
+
41
+ /**
42
+ * 判断 git remote URL 是否匹配配置的 repo。
43
+ * 采用归一化后严格相等比较(同一仓库的 SSH/HTTPS 写法归一后相等)。
44
+ */
45
+ function matchesConfiguredRepo(remoteUrl, configRepo) {
46
+ if (!remoteUrl || !configRepo) return false;
47
+ return normalizeRepoUrl(remoteUrl) === normalizeRepoUrl(configRepo);
48
+ }
49
+
50
+ /**
51
+ * 加载并校验业务空间配置。
52
+ * @param {string} [configPath] 相对于包根目录的配置文件路径,默认 config/workspaces.json
53
+ * @returns {{name: string, repo: string, businessLine: string}[]}
54
+ * @throws 配置缺失 / 格式错误时抛出,错误信息面向最终用户
55
+ */
56
+ function loadWorkspaces(configPath = DEFAULT_CONFIG) {
57
+ const absPath = path.isAbsolute(configPath)
58
+ ? configPath
59
+ : path.resolve(__dirname, '..', '..', '..', configPath);
60
+
61
+ if (!fs.existsSync(absPath)) {
62
+ throw new Error(
63
+ `未找到业务空间配置文件:${configPath}\n` +
64
+ `请在项目维护方提供的本包内维护该文件后再试。`
65
+ );
66
+ }
67
+
68
+ let raw;
69
+ try {
70
+ raw = JSON.parse(fs.readFileSync(absPath, 'utf-8'));
71
+ } catch (e) {
72
+ throw new Error(`业务空间配置 ${configPath} 不是合法 JSON:${e.message}`);
73
+ }
74
+
75
+ if (!raw || typeof raw !== 'object' || !Array.isArray(raw.workspaces)) {
76
+ throw new Error(`业务空间配置 ${configPath} 缺少 workspaces 数组`);
77
+ }
78
+ if (raw.workspaces.length === 0) {
79
+ throw new Error(`业务空间配置 ${configPath} 的 workspaces 数组为空,请至少配置一项`);
80
+ }
81
+
82
+ return raw.workspaces.map((w, i) => {
83
+ const itemLabel = `第 ${i + 1} 项`;
84
+ if (!w || typeof w !== 'object') {
85
+ throw new Error(`业务空间配置 ${itemLabel} 不是对象`);
86
+ }
87
+ if (typeof w.name !== 'string' || !w.name.trim()) {
88
+ throw new Error(`业务空间配置 ${itemLabel} 缺少 name(业务空间名)`);
89
+ }
90
+ if (typeof w.repo !== 'string' || !w.repo.trim()) {
91
+ throw new Error(`业务空间 "${w.name}" 缺少 repo(GitLab 仓库地址)`);
92
+ }
93
+ if (typeof w.businessLine !== 'string' || !w.businessLine.trim()) {
94
+ throw new Error(`业务空间 "${w.name}" 缺少 businessLine(业务线,如:直采、增值、KA)`);
95
+ }
96
+ return {
97
+ name: w.name.trim(),
98
+ repo: w.repo.trim(),
99
+ businessLine: w.businessLine.trim(),
100
+ };
101
+ });
102
+ }
103
+
104
+ /**
105
+ * 校验业务工作空间 CONFIG.json 的格式是否合法。
106
+ *
107
+ * 校验项:
108
+ * - 文件存在且为合法 JSON
109
+ * - 含 apps 数组且非空
110
+ * - 每个 app 对象必须含 name / repo / side 三个字段
111
+ * - name、repo 为非空字符串;side 必须为 'front' 或 'back'
112
+ *
113
+ * @param {string} configPath CONFIG.json 的绝对路径
114
+ * @returns {{ valid: boolean, message?: string, config?: object }}
115
+ */
116
+ function validateWorkspaceConfig(configPath) {
117
+ if (!fs.existsSync(configPath)) {
118
+ return { valid: false, message: `CONFIG.json 不存在:${configPath}` };
119
+ }
120
+
121
+ let raw;
122
+ try {
123
+ raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
124
+ } catch (e) {
125
+ return { valid: false, message: `CONFIG.json 不是合法 JSON:${e.message}` };
126
+ }
127
+
128
+ if (!raw || typeof raw !== 'object') {
129
+ return { valid: false, message: 'CONFIG.json 内容应为 JSON 对象' };
130
+ }
131
+
132
+ if (!Array.isArray(raw.apps)) {
133
+ return { valid: false, message: 'CONFIG.json 缺少 apps 数组' };
134
+ }
135
+
136
+ if (raw.apps.length === 0) {
137
+ return { valid: false, message: 'CONFIG.json 中 apps 数组为空,请至少配置一个关联应用' };
138
+ }
139
+
140
+ const VALID_SIDES = ['front', 'back'];
141
+ for (let i = 0; i < raw.apps.length; i++) {
142
+ const app = raw.apps[i];
143
+ const label = `第 ${i + 1} 项`;
144
+
145
+ if (!app || typeof app !== 'object') {
146
+ return { valid: false, message: `CONFIG.json ${label}不是对象` };
147
+ }
148
+ if (typeof app.name !== 'string' || !app.name.trim()) {
149
+ return { valid: false, message: `CONFIG.json ${label}缺少 name(应用名)` };
150
+ }
151
+ if (typeof app.repo !== 'string' || !app.repo.trim()) {
152
+ return { valid: false, message: `CONFIG.json "${app.name}" 缺少 repo(Git 仓库地址)` };
153
+ }
154
+ if (typeof app.side !== 'string' || !app.side.trim()) {
155
+ return { valid: false, message: `CONFIG.json "${app.name}" 缺少 side(仓库类型,可选值:front / back)` };
156
+ }
157
+ if (!VALID_SIDES.includes(app.side.trim())) {
158
+ return { valid: false, message: `CONFIG.json "${app.name}" 的 side 字段值无效:"${app.side}"(可选值:front / back)` };
159
+ }
160
+ }
161
+
162
+ return { valid: true, config: raw };
163
+ }
164
+
165
+ /**
166
+ * 在指定目录执行 `git remote -v`,解析出 [{ name, url }] 列表。
167
+ * 任何异常静默返回空数组,便于上层用 state 机判定。
168
+ */
169
+ function readGitRemotes(cwd) {
170
+ try {
171
+ const out = execSync('git remote -v', {
172
+ cwd,
173
+ stdio: ['ignore', 'pipe', 'ignore'],
174
+ encoding: 'utf-8',
175
+ timeout: 10000,
176
+ });
177
+ const remotes = [];
178
+ out.split(/\r?\n/).forEach(line => {
179
+ // 仅保留 fetch 行,避免 push/fetch 各占一行导致重复
180
+ const m = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
181
+ if (m) remotes.push({ name: m[1], url: m[2] });
182
+ });
183
+ return remotes;
184
+ } catch (_) {
185
+ return [];
186
+ }
187
+ }
188
+
189
+ /**
190
+ * 检测目标目录相对于 configRepo 的"是否已拉取"状态。
191
+ * @returns {{ state: 'not-git' | 'matched' | 'mismatched', remotes: {name:string,url:string}[] }}
192
+ * - not-git:目录不是 git 仓库
193
+ * - matched:是 git 仓库且任一 remote 匹配 configRepo
194
+ * - mismatched:是 git 仓库但没有任何 remote 匹配 configRepo
195
+ */
196
+ function detectRepo(cwd, configRepo) {
197
+ const gitDir = path.join(cwd, '.git');
198
+ if (!fs.existsSync(gitDir)) {
199
+ return { state: 'not-git', remotes: [] };
200
+ }
201
+ const remotes = readGitRemotes(cwd);
202
+ const matched = remotes.some(r => matchesConfiguredRepo(r.url, configRepo));
203
+ return matched
204
+ ? { state: 'matched', remotes }
205
+ : { state: 'mismatched', remotes };
206
+ }
207
+
208
+ /**
209
+ * 从 git remote URL 中提取仓库目录名(最后一段 path)。
210
+ * - git@host:group/repo.git → 'repo'
211
+ * - https://host/group/repo.git → 'repo'
212
+ * - group/sub/repo → 'repo'
213
+ * 非法输入返回空串。
214
+ */
215
+ function extractRepoBasename(url) {
216
+ const normalized = normalizeRepoUrl(url);
217
+ if (!normalized) return '';
218
+ const parts = normalized.split('/').filter(Boolean);
219
+ return parts.length > 0 ? parts[parts.length - 1] : '';
220
+ }
221
+
222
+ module.exports = {
223
+ DEFAULT_CONFIG,
224
+ SAMPLE_CONFIG,
225
+ normalizeRepoUrl,
226
+ matchesConfiguredRepo,
227
+ loadWorkspaces,
228
+ validateWorkspaceConfig,
229
+ readGitRemotes,
230
+ detectRepo,
231
+ extractRepoBasename,
232
+ };