@deepstorm/cli 0.9.2 → 0.10.0
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/dist/cli.js +508 -24
- package/dist/registry.json +168 -0
- package/dist/skills/reef-commit/SKILL.md +27 -100
- package/dist/skills/reef-commit/scripts/branch-check.mjs +109 -0
- package/dist/skills/reef-commit/scripts/check-openspec-status.mjs +92 -0
- package/dist/skills/reef-commit/scripts/collect-git-context.mjs +186 -0
- package/dist/skills/reef-commit/scripts/run-tests.sh +91 -0
- package/dist/skills/reef-commit/scripts/stash-and-switch.sh +44 -0
- package/dist/skills/reef-gen-backend/variants/nodejs/steps.md +53 -0
- package/dist/skills/reef-harden/SKILL.md +11 -19
- package/dist/skills/reef-harden/scripts/find-change-dir.mjs +157 -0
- package/dist/skills/reef-pr/SKILL.md +7 -19
- package/dist/skills/reef-pr/scripts/create-pr.mjs +217 -0
- package/dist/skills/reef-style-backend/fragments/nodejs/eslint-config.json +30 -0
- package/dist/skills/reef-style-backend/fragments/nodejs/nestjs-structure.md +58 -0
- package/dist/skills/reef-style-backend/fragments/nodejs/prettier-config.json +19 -0
- package/dist/skills/reef-style-backend/variants/nodejs/examples/module-example.md +166 -0
- package/dist/skills/reef-style-backend/variants/nodejs/examples/prisma-example.md +115 -0
- package/dist/skills/reef-style-backend/variants/nodejs/quick-reference.md +116 -0
- package/dist/skills/sweep-init/SKILL.md +12 -125
- package/dist/skills/sweep-init/scripts/init-project.mjs +166 -0
- package/dist/skills/sweep-run/SKILL.md +11 -35
- package/dist/skills/sweep-run/scripts/env-manager.mjs +71 -2
- package/dist/skills/sweep-run/scripts/generate-report.mjs +116 -0
- package/package.json +2 -2
package/dist/registry.json
CHANGED
|
@@ -355,6 +355,9 @@
|
|
|
355
355
|
"sourcePath": "src/main/java/ src/test/java/",
|
|
356
356
|
"styleRef": "→ 参考 [Java 后端速查](quick-reference.md)"
|
|
357
357
|
},
|
|
358
|
+
"availableAiFrameworks": [
|
|
359
|
+
"spring-ai"
|
|
360
|
+
],
|
|
358
361
|
"affectedTemplates": [
|
|
359
362
|
"skills/reef-style-backend/SKILL.md.tmpl",
|
|
360
363
|
"agents/reef-review-backend.md.tmpl",
|
|
@@ -380,6 +383,7 @@
|
|
|
380
383
|
"styleRef": "→ 参考 [Python FastAPI 后瑞速查](quick-reference.md)",
|
|
381
384
|
"isPython": "true"
|
|
382
385
|
},
|
|
386
|
+
"availableAiFrameworks": [],
|
|
383
387
|
"affectedTemplates": [
|
|
384
388
|
"skills/reef-style-backend/SKILL.md.tmpl",
|
|
385
389
|
"agents/reef-review-backend.md.tmpl",
|
|
@@ -393,6 +397,33 @@
|
|
|
393
397
|
"python/security-redlines"
|
|
394
398
|
]
|
|
395
399
|
},
|
|
400
|
+
{
|
|
401
|
+
"value": "nodejs",
|
|
402
|
+
"label": "Node.js / NestJS",
|
|
403
|
+
"template": {
|
|
404
|
+
"label": "NestJS + TypeScript + Prisma",
|
|
405
|
+
"buildTool": "pnpm:*",
|
|
406
|
+
"formatCmd": "npx eslint",
|
|
407
|
+
"fileExt": "*.ts",
|
|
408
|
+
"sourcePath": "src/ test/",
|
|
409
|
+
"styleRef": "→ 参考 [Node.js NestJS 后端速查](quick-reference.md)"
|
|
410
|
+
},
|
|
411
|
+
"availableAiFrameworks": [
|
|
412
|
+
"claude-agent-sdk"
|
|
413
|
+
],
|
|
414
|
+
"affectedTemplates": [
|
|
415
|
+
"skills/reef-style-backend/SKILL.md.tmpl",
|
|
416
|
+
"agents/reef-review-backend.md.tmpl",
|
|
417
|
+
"skills/reef-review/SKILL.md.tmpl",
|
|
418
|
+
"skills/reef-gen-backend/SKILL.md.tmpl"
|
|
419
|
+
],
|
|
420
|
+
"fragments": [
|
|
421
|
+
"nodejs/api-spec",
|
|
422
|
+
"nodejs/dependency-management",
|
|
423
|
+
"nodejs/exception-handling",
|
|
424
|
+
"nodejs/security-redlines"
|
|
425
|
+
]
|
|
426
|
+
},
|
|
396
427
|
{
|
|
397
428
|
"value": "none",
|
|
398
429
|
"label": "不使用后端",
|
|
@@ -790,6 +821,143 @@
|
|
|
790
821
|
}
|
|
791
822
|
]
|
|
792
823
|
},
|
|
824
|
+
{
|
|
825
|
+
"key": "reef.backend.nodejs.details",
|
|
826
|
+
"label": "Node.js 框架选型",
|
|
827
|
+
"description": "选择需要使用的 Node.js 框架和技术",
|
|
828
|
+
"type": "group",
|
|
829
|
+
"dependsOn": {
|
|
830
|
+
"key": "reef.backend.language",
|
|
831
|
+
"value": "nodejs"
|
|
832
|
+
},
|
|
833
|
+
"questions": [
|
|
834
|
+
{
|
|
835
|
+
"key": "reef.backend.nodejs.framework",
|
|
836
|
+
"label": "Web 框架",
|
|
837
|
+
"type": "select",
|
|
838
|
+
"options": [
|
|
839
|
+
{
|
|
840
|
+
"value": "nestjs",
|
|
841
|
+
"label": "NestJS",
|
|
842
|
+
"template": {
|
|
843
|
+
"styleRef": "→ 参考 [NestJS 后端速查](quick-reference.md)"
|
|
844
|
+
},
|
|
845
|
+
"affectedTemplates": [
|
|
846
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
847
|
+
],
|
|
848
|
+
"fragments": [
|
|
849
|
+
"nodejs/framework/nestjs"
|
|
850
|
+
]
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
"value": "none",
|
|
854
|
+
"label": "不使用",
|
|
855
|
+
"template": {},
|
|
856
|
+
"affectedTemplates": [
|
|
857
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
858
|
+
]
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
"key": "reef.backend.nodejs.orm",
|
|
864
|
+
"label": "ORM",
|
|
865
|
+
"type": "select",
|
|
866
|
+
"options": [
|
|
867
|
+
{
|
|
868
|
+
"value": "prisma",
|
|
869
|
+
"label": "Prisma",
|
|
870
|
+
"template": {
|
|
871
|
+
"styleRef": "→ 参考 [Prisma 规范](prisma.md)"
|
|
872
|
+
},
|
|
873
|
+
"affectedTemplates": [
|
|
874
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
875
|
+
],
|
|
876
|
+
"fragments": [
|
|
877
|
+
"nodejs/orm/prisma"
|
|
878
|
+
]
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
"value": "none",
|
|
882
|
+
"label": "不使用",
|
|
883
|
+
"template": {},
|
|
884
|
+
"affectedTemplates": [
|
|
885
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
886
|
+
]
|
|
887
|
+
}
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
"key": "reef.backend.nodejs.ai",
|
|
892
|
+
"label": "AI 集成",
|
|
893
|
+
"type": "select",
|
|
894
|
+
"options": [
|
|
895
|
+
{
|
|
896
|
+
"value": "claude-agent-sdk",
|
|
897
|
+
"label": "Claude Agent SDK",
|
|
898
|
+
"template": {
|
|
899
|
+
"styleRef": "→ 参考 [Claude Agent SDK 规范](claude-agent-sdk.md)"
|
|
900
|
+
},
|
|
901
|
+
"affectedTemplates": [
|
|
902
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
903
|
+
],
|
|
904
|
+
"fragments": [
|
|
905
|
+
"nodejs/ai/claude-agent-sdk"
|
|
906
|
+
]
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
"value": "none",
|
|
910
|
+
"label": "不使用",
|
|
911
|
+
"template": {},
|
|
912
|
+
"affectedTemplates": [
|
|
913
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
914
|
+
]
|
|
915
|
+
}
|
|
916
|
+
]
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
"key": "reef.backend.nodejs.test",
|
|
920
|
+
"label": "单元测试",
|
|
921
|
+
"type": "select",
|
|
922
|
+
"options": [
|
|
923
|
+
{
|
|
924
|
+
"value": "jest",
|
|
925
|
+
"label": "Jest",
|
|
926
|
+
"template": {
|
|
927
|
+
"styleRef": "→ 参考 [Jest 规范](jest-testing.md)"
|
|
928
|
+
},
|
|
929
|
+
"affectedTemplates": [
|
|
930
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
931
|
+
],
|
|
932
|
+
"fragments": [
|
|
933
|
+
"nodejs/test/jest"
|
|
934
|
+
]
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
"value": "vitest",
|
|
938
|
+
"label": "Vitest",
|
|
939
|
+
"template": {
|
|
940
|
+
"styleRef": "→ 参考 [Vitest 规范](vitest-testing.md)"
|
|
941
|
+
},
|
|
942
|
+
"affectedTemplates": [
|
|
943
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
944
|
+
],
|
|
945
|
+
"fragments": [
|
|
946
|
+
"nodejs/test/vitest"
|
|
947
|
+
]
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
"value": "none",
|
|
951
|
+
"label": "不使用",
|
|
952
|
+
"template": {},
|
|
953
|
+
"affectedTemplates": [
|
|
954
|
+
"skills/reef-style-backend/SKILL.md.tmpl"
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
]
|
|
958
|
+
}
|
|
959
|
+
]
|
|
960
|
+
},
|
|
793
961
|
{
|
|
794
962
|
"key": "reef.formatter",
|
|
795
963
|
"label": "格式化增强配置",
|
|
@@ -41,75 +41,32 @@ git status --short
|
|
|
41
41
|
|
|
42
42
|
### 2. 分支名与任务相关性检查
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
通过 `branch-check.mjs` 检查当前分支是否合法:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
48
|
-
echo "当前分支: $BRANCH"
|
|
49
|
-
|
|
50
|
-
# 检测是否需要创建新分支
|
|
51
|
-
MUST_NEW_BRANCH=false
|
|
52
|
-
|
|
53
|
-
# 条件一:在 main 或 master 上必须创建新分支
|
|
54
|
-
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
55
|
-
echo "⚠️ 当前在 $BRANCH 分支上,不允许直接提交,必须创建新分支"
|
|
56
|
-
MUST_NEW_BRANCH=true
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# 条件二:检查分支名是否包含 temp、wip、test、tmp、dev 等临时名称
|
|
60
|
-
TEMP_PATTERN='^(temp|wip|test|tmp|dev)(/.*)?$'
|
|
61
|
-
if echo "$BRANCH" | grep -qE "$TEMP_PATTERN"; then
|
|
62
|
-
echo "⚠️ 当前分支名 ($BRANCH) 看起来是临时分支,建议创建有意义的新分支"
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
# 收集 OpenSpec 任务上下文
|
|
66
|
-
for dir in openspec/changes/*/; do
|
|
67
|
-
if [ -f "$dir/proposal.md" ]; then
|
|
68
|
-
TASK_NAME=$(basename "$dir")
|
|
69
|
-
echo "发现 OpenSpec 任务: $TASK_NAME"
|
|
70
|
-
fi
|
|
71
|
-
done
|
|
47
|
+
node packages/reef/skills/reef-commit/scripts/branch-check.mjs
|
|
72
48
|
```
|
|
73
49
|
|
|
74
|
-
|
|
50
|
+
输出 JSON:
|
|
51
|
+
```json
|
|
52
|
+
{"isValid":true,"warning":false,"action":"continue"}
|
|
53
|
+
```
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- 用户不同意 → 继续当前分支
|
|
82
|
-
3. **如果分支名合理**(如与 OpenSpec 任务同名,或包含功能描述的 kebab-case):
|
|
83
|
-
- 直接继续
|
|
55
|
+
**判断规则:**
|
|
56
|
+
- `isValid: false, action: "create-branch"`(当前在 main/master)→ **直接进入步骤 3**
|
|
57
|
+
- `warning: true, action: "suggest-rename"`(分支名像临时分支)→ 询问用户是否改名
|
|
58
|
+
- `matchedTask` 有值 → 说明已匹配到 OpenSpec 任务
|
|
59
|
+
- 其他 → 直接继续
|
|
84
60
|
|
|
85
61
|
### 3. 创建新分支
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
**分支名生成规则(优先级从高到低):**
|
|
90
|
-
1. **OpenSpec 任务名**:如果检测到 `openspec/changes/<task>/proposal.md`,使用 `<task>` 作为分支名
|
|
63
|
+
按以下逻辑确定分支名(优先级从高到低):
|
|
64
|
+
1. **OpenSpec 任务名**:由 `branch-check.mjs` 输出的 `matchedTask` 确定
|
|
91
65
|
2. **用户输入**:询问用户想要的分支名
|
|
92
|
-
3. **AI 推导**:根据变更内容总结生成 kebab-case
|
|
66
|
+
3. **AI 推导**:根据变更内容总结生成 kebab-case 分支名
|
|
93
67
|
|
|
94
68
|
```bash
|
|
95
|
-
|
|
96
|
-
STASHED=false
|
|
97
|
-
if [ -n "$(git status --porcelain)" ]; then
|
|
98
|
-
git stash push -m "reef-commit-auto-stash"
|
|
99
|
-
STASHED=true
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# 创建并切换到新分支(基于 main)
|
|
103
|
-
git checkout main
|
|
104
|
-
git pull origin main 2>/dev/null || true
|
|
105
|
-
git checkout -b <new-branch-name>
|
|
106
|
-
|
|
107
|
-
# 恢复暂存的变更
|
|
108
|
-
if [ "$STASHED" = true ]; then
|
|
109
|
-
git stash pop
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
echo "✅ 已切换到新分支: <new-branch-name>"
|
|
69
|
+
bash packages/reef/skills/reef-commit/scripts/stash-and-switch.sh <new-branch-name>
|
|
113
70
|
```
|
|
114
71
|
|
|
115
72
|
> **注意**:创建新分支后,后续步骤(审查文件、范围检查、测试等)在新分支上继续执行。
|
|
@@ -129,9 +86,6 @@ git diff --stat
|
|
|
129
86
|
检查当前分支是否涉及多个业务领域。如果跨领域,给出拆分建议并中止提交。
|
|
130
87
|
|
|
131
88
|
```bash
|
|
132
|
-
BRANCH=$(git branch --show-current)
|
|
133
|
-
FORK_POINT=$(git merge-base main HEAD 2>/dev/null || echo "")
|
|
134
|
-
echo "检查分支范围(基准: main)..."
|
|
135
89
|
SCOPE_HOOK="packages/reef/hooks/reef-scope-gate.sh"
|
|
136
90
|
if [ -f "$SCOPE_HOOK" ]; then
|
|
137
91
|
bash "$SCOPE_HOOK" || {
|
|
@@ -146,64 +100,37 @@ fi
|
|
|
146
100
|
### 6. 运行单元测试
|
|
147
101
|
|
|
148
102
|
```bash
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
如有变更则全量运行测试:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
./gradlew test
|
|
156
|
-
(cd src/main/web && pnpm test -- --run)
|
|
103
|
+
bash packages/reef/skills/reef-commit/scripts/run-tests.sh --json
|
|
157
104
|
```
|
|
158
105
|
|
|
159
|
-
-
|
|
160
|
-
-
|
|
106
|
+
- 全部通过(`allPassed: true`)→ 继续
|
|
107
|
+
- 任一测试失败(`allPassed: false`)→ 提示用户修复后再提交
|
|
161
108
|
|
|
162
109
|
### 6.5 OpenSpec 验证与归档检查
|
|
163
110
|
|
|
164
111
|
> 提交前检查关联的 OpenSpec change 是否已完成验证和归档。如果 `openspec/` 目录不存在或无活跃 change 则跳过本步骤。
|
|
165
112
|
|
|
166
|
-
|
|
113
|
+
**判断流程:**
|
|
167
114
|
|
|
168
115
|
1. **查找关联的 OpenSpec change:**
|
|
169
116
|
```bash
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
for dir in openspec/changes/*/; do
|
|
174
|
-
CHANGE_NAME=$(basename "$dir")
|
|
175
|
-
if [ -f "$dir/.openspec.yaml" ] && [ "$CHANGE_NAME" != "archive" ]; then
|
|
176
|
-
echo "发现活跃 OpenSpec change: $CHANGE_NAME"
|
|
177
|
-
cat "$dir/.openspec.yaml"
|
|
178
|
-
fi
|
|
179
|
-
done
|
|
117
|
+
node packages/reef/skills/reef-commit/scripts/check-openspec-status.mjs \
|
|
118
|
+
--branch "$(git branch --show-current)"
|
|
180
119
|
```
|
|
181
120
|
|
|
182
|
-
2. **匹配规则:**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
4. **运行验证:** 确认 `tasks.md` 全部 checkbox 已完成 → 通过 Skill 工具自动调用 `/opsx:verify`。有 CRITICAL 问题则中止;仅 WARNING/SUGGESTION 则通过。
|
|
187
|
-
|
|
121
|
+
2. **匹配规则:** 脚本输出中 `name` 匹配当前分支名,`tasksAllDone` 检查任务完成状态。
|
|
122
|
+
3. **检查归档状态:** 无输出(`noMatch: true`)→ 跳过。`hasTasksMd: true` + `tasksAllDone: true` → 已全部完成 → 调用 `/opsx:verify` 和 `/opsx:archive`。
|
|
123
|
+
4. **运行验证:** 确认后通过 Skill 工具自动调用 `/opsx:verify`。有 CRITICAL 问题则中止;仅 WARNING/SUGGESTION 则通过。
|
|
188
124
|
5. **运行归档:** 验证通过后 → 通过 Skill 工具自动调用 `/opsx:archive`。执行失败则提示用户手动处理。
|
|
189
125
|
|
|
190
|
-
6. **确认已就绪:** 校验状态并向用户报告。
|
|
191
|
-
|
|
192
|
-
> **提示:** verify/archive 执行后产生额外文件变更的,后续步骤会重新检测并纳入提交。
|
|
193
|
-
|
|
194
126
|
### 7. 收集上下文
|
|
195
127
|
|
|
196
128
|
```bash
|
|
197
|
-
|
|
198
|
-
FORK_POINT=$(git merge-base main HEAD 2>/dev/null)
|
|
199
|
-
echo "Branch: $BRANCH"; git diff "$FORK_POINT"..HEAD --stat
|
|
200
|
-
ls -d openspec/changes/*/ 2>/dev/null | grep -v archive
|
|
201
|
-
[ -f "openspec/changes/$BRANCH/proposal.md" ] && head -5 "openspec/changes/$BRANCH/proposal.md"
|
|
202
|
-
# 从 proposal/commit 提取 Issue 引用
|
|
203
|
-
grep -iE '(issue|jira|lc-|proj-)' "openspec/changes/$BRANCH/proposal.md" 2>/dev/null | head -3
|
|
204
|
-
git log "$FORK_POINT"..HEAD --format="%B" 2>/dev/null | grep -ioP '[A-Z]+-\d+' | head -1
|
|
129
|
+
node packages/reef/skills/reef-commit/scripts/collect-git-context.mjs
|
|
205
130
|
```
|
|
206
131
|
|
|
132
|
+
输出 JSON 包含 `branch`、`diffStat`、`commitLog`、`openspecChanges` 等字段。提取 `openspecChanges` 中 `matched: true` 项的 `proposal.md` 标题作为提交信息素材。
|
|
133
|
+
|
|
207
134
|
### 8. 生成提交信息
|
|
208
135
|
|
|
209
136
|
**标题:** 优先 OpenSpec proposal 标题 → 分支名(kebab-case 转中文)→ 变更总结。依赖更新用 `更新 {依赖} 至 {版本}`,问题修复用 `解决{问题描述}的问题`。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* branch-check.mjs
|
|
5
|
+
* 检查当前分支是否合法。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* node branch-check.mjs
|
|
9
|
+
* node branch-check.mjs --branch feat/add-auth
|
|
10
|
+
* node branch-check.mjs --help
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
// ── Exported constants (for test access) ──────────────────────────
|
|
18
|
+
|
|
19
|
+
/** 临时分支名正则 */
|
|
20
|
+
export const TEMP_PATTERN = /^(temp|wip|test|tmp|dev)(\/.*)?$/;
|
|
21
|
+
|
|
22
|
+
// ── Pure functions ────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export function isMainOrMaster(branch) {
|
|
25
|
+
return branch === 'main' || branch === 'master';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 检查分支是否合法。
|
|
30
|
+
* @param {string} branch
|
|
31
|
+
* @param {Array<{name:string}>} openspecTasks
|
|
32
|
+
* @returns {{isValid:boolean, reason?:string, action:string,
|
|
33
|
+
* warning:boolean, matchedTask?:string}}
|
|
34
|
+
*/
|
|
35
|
+
export function checkBranch(branch, openspecTasks = []) {
|
|
36
|
+
if (isMainOrMaster(branch)) {
|
|
37
|
+
return {
|
|
38
|
+
isValid: false,
|
|
39
|
+
reason: `不允许在 ${branch} 分支上直接提交`,
|
|
40
|
+
action: 'create-branch',
|
|
41
|
+
warning: false,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isTemp = TEMP_PATTERN.test(branch);
|
|
46
|
+
const action = isTemp ? 'suggest-rename' : 'continue';
|
|
47
|
+
const matchedTask = openspecTasks.find(t =>
|
|
48
|
+
branch === t.name || branch.includes(t.name)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isValid: true,
|
|
53
|
+
warning: isTemp,
|
|
54
|
+
action,
|
|
55
|
+
...(isTemp ? { reason: '分支名像临时分支,建议改名' } : {}),
|
|
56
|
+
...(matchedTask ? { matchedTask: matchedTask.name } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Shell helpers ─────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
function exec(command) {
|
|
63
|
+
try {
|
|
64
|
+
return execSync(command, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
65
|
+
} catch {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findOpenspecTasks() {
|
|
71
|
+
const root = exec(
|
|
72
|
+
'git rev-parse --show-toplevel 2>/dev/null || echo ""'
|
|
73
|
+
);
|
|
74
|
+
if (!root) return [];
|
|
75
|
+
const changesDir = join(root, 'openspec', 'changes');
|
|
76
|
+
if (!existsSync(changesDir)) return [];
|
|
77
|
+
|
|
78
|
+
const entries = readdirSync(changesDir, { withFileTypes: true });
|
|
79
|
+
return entries
|
|
80
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
81
|
+
.map(e => ({
|
|
82
|
+
name: e.name,
|
|
83
|
+
hasProposal: existsSync(join(changesDir, e.name, 'proposal.md')),
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── CLI entry ─────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
if (import.meta.filename === process.argv[1]) {
|
|
90
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
91
|
+
console.log(`
|
|
92
|
+
branch-check.mjs — 检查分支合法性
|
|
93
|
+
|
|
94
|
+
用法:
|
|
95
|
+
node branch-check.mjs 自动检测
|
|
96
|
+
node branch-check.mjs --branch <name> 指定分支
|
|
97
|
+
node branch-check.mjs --help 显示帮助
|
|
98
|
+
`);
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const idx = process.argv.indexOf('--branch');
|
|
103
|
+
const branch = idx >= 0
|
|
104
|
+
? process.argv[idx + 1]
|
|
105
|
+
: exec('git branch --show-current');
|
|
106
|
+
const tasks = findOpenspecTasks();
|
|
107
|
+
const result = checkBranch(branch, tasks);
|
|
108
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
109
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* check-openspec-status.mjs
|
|
5
|
+
* 扫描 OpenSpec change 的状态。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* node check-openspec-status.mjs
|
|
9
|
+
* node check-openspec-status.mjs --branch add-auth
|
|
10
|
+
* node check-openspec-status.mjs --help
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
const TASKS_CHECKBOX_RE = /^- \[([ xX])\]/gm;
|
|
17
|
+
|
|
18
|
+
// ── Pure functions ────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export function isArchived(changePath) {
|
|
21
|
+
return changePath.includes('/archive/');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseTasksStatus(content) {
|
|
25
|
+
const matches = [...content.matchAll(TASKS_CHECKBOX_RE)];
|
|
26
|
+
const total = matches.length;
|
|
27
|
+
const complete = matches.filter(m => m[1] !== ' ').length;
|
|
28
|
+
return {
|
|
29
|
+
total,
|
|
30
|
+
complete,
|
|
31
|
+
allDone: total === 0 ? true : total === complete,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function scanChanges(changesDir) {
|
|
36
|
+
if (!existsSync(changesDir)) return [];
|
|
37
|
+
const entries = readdirSync(changesDir, { withFileTypes: true });
|
|
38
|
+
return entries
|
|
39
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
40
|
+
.map(e => {
|
|
41
|
+
const dirPath = join(changesDir, e.name);
|
|
42
|
+
const tasksPath = join(dirPath, 'tasks.md');
|
|
43
|
+
const hasTasksMd = existsSync(tasksPath);
|
|
44
|
+
let tasksStatus = null;
|
|
45
|
+
if (hasTasksMd) {
|
|
46
|
+
tasksStatus = parseTasksStatus(
|
|
47
|
+
readFileSync(tasksPath, 'utf8')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: e.name,
|
|
52
|
+
archived: false,
|
|
53
|
+
hasTasksMd,
|
|
54
|
+
tasksAllDone: tasksStatus ? tasksStatus.allDone : null,
|
|
55
|
+
tasksTotal: tasksStatus ? tasksStatus.total : 0,
|
|
56
|
+
tasksComplete: tasksStatus ? tasksStatus.complete : 0,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── CLI entry ─────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
if (import.meta.filename === process.argv[1]) {
|
|
64
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
65
|
+
console.log(`
|
|
66
|
+
check-openspec-status.mjs — 扫描 OpenSpec change 状态
|
|
67
|
+
|
|
68
|
+
用法:
|
|
69
|
+
node check-openspec-status.mjs
|
|
70
|
+
node check-openspec-status.mjs --branch <name>
|
|
71
|
+
node check-openspec-status.mjs --help
|
|
72
|
+
`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const root = process.env.INIT_CWD || process.cwd();
|
|
77
|
+
const changesDir = join(root, 'openspec', 'changes');
|
|
78
|
+
const results = scanChanges(changesDir);
|
|
79
|
+
|
|
80
|
+
const branchIdx = process.argv.indexOf('--branch');
|
|
81
|
+
if (branchIdx >= 0) {
|
|
82
|
+
const branch = process.argv[branchIdx + 1];
|
|
83
|
+
const matched = results.filter(r =>
|
|
84
|
+
r.name === branch || branch.includes(r.name)
|
|
85
|
+
);
|
|
86
|
+
process.stdout.write(JSON.stringify(
|
|
87
|
+
matched.length > 0 ? matched : { noMatch: true, branch }, null, 2
|
|
88
|
+
) + '\n');
|
|
89
|
+
} else {
|
|
90
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
91
|
+
}
|
|
92
|
+
}
|