@deepstorm/cli 0.9.2 → 0.9.3
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/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-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/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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# stash-and-switch.sh
|
|
3
|
+
# git stash → checkout main → pull → checkout -b → stash pop
|
|
4
|
+
#
|
|
5
|
+
# 用法:
|
|
6
|
+
# bash stash-and-switch.sh <new-branch-name>
|
|
7
|
+
#
|
|
8
|
+
# 说明:
|
|
9
|
+
# 自动处理 stash 保存和恢复当前工作区变更。
|
|
10
|
+
# 新分支基于 main 的最新提交。
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
if [ $# -lt 1 ]; then
|
|
15
|
+
echo "用法: bash $0 <new-branch-name>"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
NEW_BRANCH="$1"
|
|
20
|
+
STASHED=false
|
|
21
|
+
|
|
22
|
+
# stash 当前变更(如有)
|
|
23
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
24
|
+
echo "📦 暂存当前未提交变更..."
|
|
25
|
+
git stash push -m "stash-and-switch-auto-stash"
|
|
26
|
+
STASHED=true
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# 切换到 main 并更新
|
|
30
|
+
echo "🔀 切换到 main..."
|
|
31
|
+
git checkout main
|
|
32
|
+
git pull origin main 2>/dev/null || echo " ⚠️ pull 失败(可能无远程)"
|
|
33
|
+
|
|
34
|
+
# 创建新分支
|
|
35
|
+
echo "🌿 创建新分支: $NEW_BRANCH"
|
|
36
|
+
git checkout -b "$NEW_BRANCH"
|
|
37
|
+
|
|
38
|
+
# 恢复暂存的变更
|
|
39
|
+
if [ "$STASHED" = true ]; then
|
|
40
|
+
echo "📦 恢复暂存的变更..."
|
|
41
|
+
git stash pop
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
echo "✅ 已切换到新分支: $NEW_BRANCH"
|
|
@@ -10,25 +10,17 @@ deepstorm:
|
|
|
10
10
|
|
|
11
11
|
## 上下文约定
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
使用 `find-change-dir.mjs` 自动发现当前 change:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# 如仍无结果,列出所有 change 让用户选择
|
|
27
|
-
if [ ! -d "$CHANGE_DIR" ]; then
|
|
28
|
-
echo "未找到 openspec change,可用的目录:"
|
|
29
|
-
ls openspec/changes/
|
|
30
|
-
echo "请指定 change 名"
|
|
31
|
-
fi
|
|
16
|
+
node packages/reef/skills/reef-harden/scripts/find-change-dir.mjs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
输出 JSON。`noMatch: true` 时需让用户选择;有 `suggestion` 时使用推荐 change。
|
|
20
|
+
|
|
21
|
+
如需并列出 SDD 文档路径:
|
|
22
|
+
```bash
|
|
23
|
+
node packages/reef/skills/reef-harden/scripts/find-change-dir.mjs --files
|
|
32
24
|
```
|
|
33
25
|
|
|
34
26
|
## 快速开始
|
|
@@ -45,8 +37,8 @@ fi
|
|
|
45
37
|
### Step 1: 加载文档
|
|
46
38
|
|
|
47
39
|
```bash
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
CHANGE_JSON=$(node packages/reef/skills/reef-harden/scripts/find-change-dir.mjs)
|
|
41
|
+
# CHANGE_JSON 中包含 path 和 files[],files 为所有 SDD 文档路径
|
|
50
42
|
```
|
|
51
43
|
|
|
52
44
|
读取所有这些文档。目录结构通常为:
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* find-change-dir.mjs
|
|
5
|
+
* 自动发现当前 OpenSpec change 目录。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* node find-change-dir.mjs # 根据分支自动查找
|
|
9
|
+
* node find-change-dir.mjs --branch <name> # 指定分支
|
|
10
|
+
* node find-change-dir.mjs --files # 同时列出 SDD 文档
|
|
11
|
+
* node find-change-dir.mjs --dir <path> # 指定扫描目录
|
|
12
|
+
* node find-change-dir.mjs --help
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const CHANGES_GLOB = 'openspec/changes';
|
|
20
|
+
|
|
21
|
+
// ── Pure functions ────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function isArchivePath(changePath) {
|
|
24
|
+
return changePath.includes('/archive/');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 在 changes 列表中按 name 精确查找
|
|
29
|
+
* @param {Array} changes
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @returns {object|null}
|
|
32
|
+
*/
|
|
33
|
+
export function findChangeByName(changes, name) {
|
|
34
|
+
return changes.find(c =>
|
|
35
|
+
c.name === name && !isArchivePath(c.path)
|
|
36
|
+
) || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 返回最近修改的非归档 change
|
|
41
|
+
* @param {Array} changes
|
|
42
|
+
* @returns {object|null}
|
|
43
|
+
*/
|
|
44
|
+
export function findMostRecent(changes) {
|
|
45
|
+
const active = changes.filter(c => !c.archived);
|
|
46
|
+
if (active.length === 0) return null;
|
|
47
|
+
return active.sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 列出 changes 目录下的所有 change
|
|
52
|
+
* @param {string} changesDir
|
|
53
|
+
* @returns {Array<{name:string, path:string, archived:boolean, mtimeMs:number, files:string[]}>}
|
|
54
|
+
*/
|
|
55
|
+
export function listChanges(changesDir) {
|
|
56
|
+
if (!existsSync(changesDir)) return [];
|
|
57
|
+
const entries = readdirSync(changesDir, { withFileTypes: true });
|
|
58
|
+
const results = [];
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (!entry.isDirectory()) continue;
|
|
62
|
+
if (entry.name === 'archive') continue;
|
|
63
|
+
|
|
64
|
+
const dirPath = join(changesDir, entry.name);
|
|
65
|
+
const stats = statSync(dirPath);
|
|
66
|
+
|
|
67
|
+
// 收集 .md 文件
|
|
68
|
+
const files = collectMdFiles(dirPath);
|
|
69
|
+
|
|
70
|
+
results.push({
|
|
71
|
+
name: entry.name,
|
|
72
|
+
path: dirPath,
|
|
73
|
+
archived: false,
|
|
74
|
+
mtimeMs: stats.mtimeMs,
|
|
75
|
+
files,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function collectMdFiles(dir) {
|
|
83
|
+
try {
|
|
84
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
const files = [];
|
|
86
|
+
for (const e of entries) {
|
|
87
|
+
if (e.isFile() && e.name.endsWith('.md')) {
|
|
88
|
+
files.push(join(dir, e.name));
|
|
89
|
+
}
|
|
90
|
+
if (e.isDirectory()) {
|
|
91
|
+
const sub = collectMdFiles(join(dir, e.name));
|
|
92
|
+
files.push(...sub);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return files;
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── CLI entry ─────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
if (import.meta.filename === process.argv[1]) {
|
|
104
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
105
|
+
console.log(`
|
|
106
|
+
find-change-dir.mjs — 发现 OpenSpec change 目录
|
|
107
|
+
|
|
108
|
+
用法:
|
|
109
|
+
node find-change-dir.mjs 根据分支自动查找
|
|
110
|
+
node find-change-dir.mjs --branch <name> 指定分支
|
|
111
|
+
node find-change-dir.mjs --files 同时列出 SDD 文档
|
|
112
|
+
node find-change-dir.mjs --dir <path> 指定扫描目录
|
|
113
|
+
node find-change-dir.mjs --help
|
|
114
|
+
`);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const root = process.argv.indexOf('--dir') >= 0
|
|
119
|
+
? process.argv[process.argv.indexOf('--dir') + 1]
|
|
120
|
+
: process.env.INIT_CWD || process.cwd();
|
|
121
|
+
const changesDir = join(root, CHANGES_GLOB);
|
|
122
|
+
const allChanges = listChanges(changesDir);
|
|
123
|
+
|
|
124
|
+
const branchIdx = process.argv.indexOf('--branch');
|
|
125
|
+
let result;
|
|
126
|
+
|
|
127
|
+
if (branchIdx >= 0) {
|
|
128
|
+
const branch = process.argv[branchIdx + 1];
|
|
129
|
+
result = findChangeByName(allChanges, branch);
|
|
130
|
+
if (!result) {
|
|
131
|
+
result = {
|
|
132
|
+
noMatch: true,
|
|
133
|
+
branch,
|
|
134
|
+
suggestion: findMostRecent(allChanges),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const branch = execGitBranch();
|
|
139
|
+
result = branch
|
|
140
|
+
? (findChangeByName(allChanges, branch) ||
|
|
141
|
+
{ noMatch: true, branch,
|
|
142
|
+
suggestion: findMostRecent(allChanges) })
|
|
143
|
+
: { noMatch: true, allChanges };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function execGitBranch() {
|
|
150
|
+
try {
|
|
151
|
+
return execSync('git branch --show-current', {
|
|
152
|
+
encoding: 'utf8', timeout: 5000,
|
|
153
|
+
}).trim() || null;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -30,15 +30,10 @@ CHANGE_DIR="openspec/changes/$BRANCH"
|
|
|
30
30
|
先检查未提交变更。如有则提示「请先 commit 再创建 PR」,中止流程。
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
FORK_POINT=$(git merge-base main HEAD 2>/dev/null)
|
|
35
|
-
echo "Branch: $BRANCH"; git status -sb
|
|
36
|
-
echo "Commits:"; git log "$FORK_POINT"..HEAD --oneline
|
|
37
|
-
echo "Changes:"; git diff "$FORK_POINT"..HEAD --stat
|
|
38
|
-
ls -d openspec/changes/$BRANCH/*.md 2>/dev/null
|
|
33
|
+
node packages/reef/skills/reef-pr/scripts/create-pr.mjs --collect
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
输出 JSON 包含 `branch`、`hasUncommitted`、`diffStat`、`commitLog`、`proposalTitle`。如有 OpenSpec change,`proposalTitle` 会包含 proposal 标题。
|
|
42
37
|
|
|
43
38
|
### 2. 构建 PR 信息
|
|
44
39
|
|
|
@@ -71,20 +66,11 @@ OpenSpec: openspec/changes/{branch-name}/
|
|
|
71
66
|
|
|
72
67
|
### 4. 推送并创建 PR
|
|
73
68
|
|
|
74
|
-
先检查是否已有打开的 PR
|
|
69
|
+
先检查是否已有打开的 PR,用 `create-pr.mjs` 统一处理:
|
|
75
70
|
|
|
76
71
|
```bash
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
如有则询问「已有 PR,是否更新描述?」,用户确认后用 `gh pr update` 更新;否则正常创建:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
# 首次推送当前分支
|
|
84
|
-
git push -u origin $(git branch --show-current)
|
|
85
|
-
|
|
86
|
-
# 创建 PR(如有 reviewer/label/draft 则追加对应参数)
|
|
87
|
-
gh pr create \
|
|
72
|
+
# --title 和 --body 从步骤 2 构建的 PR 信息中取
|
|
73
|
+
node packages/reef/skills/reef-pr/scripts/create-pr.mjs --create \
|
|
88
74
|
--title "<标题>" \
|
|
89
75
|
--body "<正文>" \
|
|
90
76
|
[--reviewer "<用户>"] \
|
|
@@ -92,6 +78,8 @@ gh pr create \
|
|
|
92
78
|
[--draft]
|
|
93
79
|
```
|
|
94
80
|
|
|
81
|
+
如有已有 PR,脚本输出 `{"exists":true}` 并在 stderr 输出提示,此时可用 `gh pr update` 更新。
|
|
82
|
+
|
|
95
83
|
### 5. 输出结果
|
|
96
84
|
|
|
97
85
|
返回 PR 链接。
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create-pr.mjs — PR 上下文收集与创建
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* node create-pr.mjs --collect # 收集 PR 上下文
|
|
8
|
+
* node create-pr.mjs --create <args> # 创建 PR
|
|
9
|
+
* node create-pr.mjs --help
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
// ── Pure functions ────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export function checkUncommitted(statusShort) {
|
|
19
|
+
return statusShort.trim().length > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildPrBody(ctx) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
|
|
25
|
+
lines.push('## 概要');
|
|
26
|
+
if (ctx.proposalTitle) {
|
|
27
|
+
lines.push(ctx.proposalTitle);
|
|
28
|
+
} else if (ctx.commitLog && ctx.commitLog.length > 0) {
|
|
29
|
+
lines.push(ctx.commitLog[0].message);
|
|
30
|
+
} else {
|
|
31
|
+
lines.push(ctx.branch);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push('## 关联');
|
|
36
|
+
lines.push(`Branch: ${ctx.branch}`);
|
|
37
|
+
|
|
38
|
+
if (ctx.diffStat) {
|
|
39
|
+
const s = ctx.diffStat;
|
|
40
|
+
lines.push(`Changes: ${s.files} 文件, +${s.insertions}/-${s.deletions}`);
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push('## 变更清单');
|
|
43
|
+
s.changedFiles.forEach(f => lines.push(`- \`${f}\``));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (ctx.commitLog && ctx.commitLog.length > 0) {
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push('## Commits');
|
|
49
|
+
ctx.commitLog.forEach(c => lines.push(`- ${c.hash} ${c.message}`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function buildGhCreateArgs(opts) {
|
|
56
|
+
const args = ['gh pr create'];
|
|
57
|
+
args.push(`--title "${opts.title}"`);
|
|
58
|
+
args.push(`--body "${opts.body}"`);
|
|
59
|
+
if (opts.reviewer) args.push(`--reviewer ${opts.reviewer}`);
|
|
60
|
+
if (opts.label) args.push(`--label ${opts.label}`);
|
|
61
|
+
if (opts.draft) args.push('--draft');
|
|
62
|
+
return args.join(' ');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Git helpers ────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function exec(command) {
|
|
68
|
+
try {
|
|
69
|
+
return execSync(command, { encoding: 'utf8', timeout: 10000 }).trim();
|
|
70
|
+
} catch {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function collectContext() {
|
|
76
|
+
const branch = exec('git branch --show-current');
|
|
77
|
+
const forkPoint = exec(
|
|
78
|
+
'git merge-base main HEAD 2>/dev/null || echo ""'
|
|
79
|
+
);
|
|
80
|
+
const diffStat = exec(
|
|
81
|
+
`git diff "${forkPoint || 'main'}..HEAD" --stat`
|
|
82
|
+
);
|
|
83
|
+
const commitLog = forkPoint
|
|
84
|
+
? exec(`git log "${forkPoint}"..HEAD --oneline`)
|
|
85
|
+
: '';
|
|
86
|
+
const statusShort = exec('git status --short');
|
|
87
|
+
|
|
88
|
+
// 查找关联 proposal
|
|
89
|
+
let proposalTitle = '';
|
|
90
|
+
const root = exec(
|
|
91
|
+
'git rev-parse --show-toplevel 2>/dev/null || echo ""'
|
|
92
|
+
);
|
|
93
|
+
if (root && branch) {
|
|
94
|
+
const p = join(root, 'openspec', 'changes', branch, 'proposal.md');
|
|
95
|
+
if (existsSync(p)) {
|
|
96
|
+
const firstLine = readFileSync(p, 'utf8')
|
|
97
|
+
.split('\n')[0] || '';
|
|
98
|
+
proposalTitle = firstLine.replace(/^#+\s*/, '').trim();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
branch,
|
|
104
|
+
hasUncommitted: checkUncommitted(statusShort),
|
|
105
|
+
proposalTitle,
|
|
106
|
+
commitLog: commitLog
|
|
107
|
+
? commitLog.split('\n').filter(Boolean).map(l => {
|
|
108
|
+
const m = l.match(/^(\S+)\s+(.*)/);
|
|
109
|
+
return m ? { hash: m[1], message: m[2] } : null;
|
|
110
|
+
}).filter(Boolean)
|
|
111
|
+
: [],
|
|
112
|
+
diffStat: diffStat ? parseDiffStat(diffStat) : null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseDiffStat(output) {
|
|
117
|
+
const lines = output.trim().split('\n').filter(Boolean);
|
|
118
|
+
const changedFiles = [];
|
|
119
|
+
let files = 0, insertions = 0, deletions = 0;
|
|
120
|
+
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const sm = line.match(/(\d+)\s+files?\s+changed/);
|
|
123
|
+
if (sm) {
|
|
124
|
+
files = parseInt(sm[1], 10);
|
|
125
|
+
const im = line.match(/(\d+)\s+insertions?\(\+\)/);
|
|
126
|
+
if (im) insertions = parseInt(im[1], 10);
|
|
127
|
+
const dm = line.match(/(\d+)\s+deletions?\(-\)/);
|
|
128
|
+
if (dm) deletions = parseInt(dm[1], 10);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const fm = line.match(/^\s*(.+?)\s+\|/);
|
|
132
|
+
if (fm) changedFiles.push(fm[1].trim());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { files, insertions, deletions, changedFiles };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function checkExistingPR() {
|
|
139
|
+
const out = exec('gh pr view --json url 2>/dev/null || true');
|
|
140
|
+
return out ? { exists: true, url: out } : { exists: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── CLI entry ─────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
if (import.meta.filename === process.argv[1]) {
|
|
146
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
147
|
+
console.log(`
|
|
148
|
+
create-pr.mjs — PR 上下文收集与创建
|
|
149
|
+
|
|
150
|
+
用法:
|
|
151
|
+
node create-pr.mjs --collect 收集 PR 上下文(JSON)
|
|
152
|
+
node create-pr.mjs --create 创建 PR
|
|
153
|
+
node create-pr.mjs --help 显示帮助
|
|
154
|
+
|
|
155
|
+
--create 选项:
|
|
156
|
+
--title <text> PR 标题
|
|
157
|
+
--body <text> PR 正文
|
|
158
|
+
--reviewer <user> Reviewer
|
|
159
|
+
--label <label> 标签
|
|
160
|
+
--draft 创建 Draft PR
|
|
161
|
+
`);
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (process.argv.includes('--collect')) {
|
|
166
|
+
const ctx = collectContext();
|
|
167
|
+
process.stdout.write(JSON.stringify(ctx, null, 2) + '\n');
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (process.argv.includes('--create')) {
|
|
172
|
+
const titleArg = process.argv.indexOf('--title');
|
|
173
|
+
const bodyArg = process.argv.indexOf('--body');
|
|
174
|
+
const reviewerArg = process.argv.indexOf('--reviewer');
|
|
175
|
+
const labelArg = process.argv.indexOf('--label');
|
|
176
|
+
const isDraft = process.argv.includes('--draft');
|
|
177
|
+
|
|
178
|
+
if (titleArg < 0 || bodyArg < 0) {
|
|
179
|
+
console.error('--title 和 --body 必填');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const existing = checkExistingPR();
|
|
184
|
+
if (existing.exists) {
|
|
185
|
+
process.stdout.write(JSON.stringify(existing, null, 2) + '\n');
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const branch = exec('git branch --show-current');
|
|
190
|
+
const pushOut = exec(`git push -u origin ${branch} 2>&1`);
|
|
191
|
+
if (!pushOut) {
|
|
192
|
+
console.error('git push 失败');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const cmd = buildGhCreateArgs({
|
|
197
|
+
title: process.argv[titleArg + 1],
|
|
198
|
+
body: process.argv[bodyArg + 1],
|
|
199
|
+
branch,
|
|
200
|
+
reviewer: reviewerArg >= 0
|
|
201
|
+
? process.argv[reviewerArg + 1] : null,
|
|
202
|
+
label: labelArg >= 0 ? process.argv[labelArg + 1] : null,
|
|
203
|
+
draft: isDraft,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const out = exec(cmd);
|
|
207
|
+
process.stdout.write(JSON.stringify({
|
|
208
|
+
created: true,
|
|
209
|
+
url: out,
|
|
210
|
+
}, null, 2) + '\n');
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 默认:--collect
|
|
215
|
+
const ctx = collectContext();
|
|
216
|
+
process.stdout.write(JSON.stringify(ctx, null, 2) + '\n');
|
|
217
|
+
}
|
|
@@ -103,107 +103,21 @@ E2E_PATH=$(cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eProjectPath"[
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
-
### 步骤 2
|
|
106
|
+
### 步骤 2+3:创建项目目录与配置文件
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
#### 目录结构(当 TARGET_DIR 为 `"."` 时)
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
.
|
|
114
|
-
├── flows/ # 测试意图文档目录
|
|
115
|
-
│ ├── topology.yaml # 功能模块拓扑定义(待用户编辑)
|
|
116
|
-
│ └── reports/ # 执行报告持久化目录
|
|
117
|
-
├── scripts/
|
|
118
|
-
│ └── flow-selector.mjs # 层级选择工具(预置)
|
|
119
|
-
├── .env # 环境变量(gitignored,即模板)
|
|
120
|
-
├── package.json # 依赖声明
|
|
121
|
-
└── tsconfig.json # TypeScript 配置
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
> 如果 TARGET_DIR 为 `"e2e"`,以上所有文件在 `e2e/` 下生成。如果框架为 `playwright`,还会额外生成 `playwright.config.ts`。
|
|
125
|
-
|
|
126
|
-
#### 执行
|
|
108
|
+
使用 `init-project.mjs` 一键创建目录结构并生成所有配置文件:
|
|
127
109
|
|
|
128
110
|
```bash
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
echo "📂 将在 $TARGET_DIR/ 目录下生成 E2E 测试项目"
|
|
133
|
-
else
|
|
134
|
-
mkdir -p flows flows/reports scripts
|
|
135
|
-
fi
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
### 步骤 3:生成项目配置文件(框架感知)
|
|
141
|
-
|
|
142
|
-
根据步骤 0 读取的框架配置,在 `{TARGET_DIR}` 下生成对应的配置文件。
|
|
143
|
-
|
|
144
|
-
> 所有文件写入路径以 `{TARGET_DIR}` 为前缀。当 `TARGET_DIR` 为 `"."` 时,路径不变。
|
|
145
|
-
|
|
146
|
-
#### 3.1 package.json
|
|
147
|
-
|
|
148
|
-
写入基础 `package.json`。如果框架为 `playwright`,包含 Playwright 依赖:
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
{
|
|
152
|
-
"name": "sweep-e2e",
|
|
153
|
-
"version": "1.0.0",
|
|
154
|
-
"private": true,
|
|
155
|
-
"type": "module",
|
|
156
|
-
"scripts": {
|
|
157
|
-
"test": "playwright test",
|
|
158
|
-
"report": "playwright show-report"
|
|
159
|
-
},
|
|
160
|
-
"devDependencies": {
|
|
161
|
-
"@playwright/test": "^1.50.0",
|
|
162
|
-
"@inquirer/checkbox": "^4.0.0",
|
|
163
|
-
"@types/node": "^22.0.0"
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
> 当 E2E 框架未知或未配置时,`package.json` 中仅包含 `@inquirer/checkbox` 和 `@types/node` 依赖,**不包含** `@playwright/test`。
|
|
169
|
-
|
|
170
|
-
#### 3.2 框架配置文件
|
|
171
|
-
|
|
172
|
-
**当框架为 `playwright` 时**,写入 `{TARGET_DIR}/playwright.config.ts`,包含多环境 baseURL 设置:
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
import { defineConfig } from '@playwright/test';
|
|
176
|
-
|
|
177
|
-
export default defineConfig({
|
|
178
|
-
use: {
|
|
179
|
-
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
180
|
-
},
|
|
181
|
-
timeout: 30000,
|
|
182
|
-
retries: 0,
|
|
183
|
-
reporter: [['line'], ['html', { outputFolder: 'flows/reports' }]],
|
|
184
|
-
projects: [
|
|
185
|
-
{ name: 'chromium', use: { browserName: 'chromium' } },
|
|
186
|
-
],
|
|
187
|
-
});
|
|
111
|
+
node packages/sweep/skills/sweep-init/scripts/init-project.mjs \
|
|
112
|
+
--dir "$TARGET_DIR" \
|
|
113
|
+
--framework "$(node scripts/env-manager.mjs --framework | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).framework || ''")"
|
|
188
114
|
```
|
|
189
115
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```json
|
|
197
|
-
{
|
|
198
|
-
"compilerOptions": {
|
|
199
|
-
"target": "ES2022",
|
|
200
|
-
"module": "ESNext",
|
|
201
|
-
"moduleResolution": "bundler",
|
|
202
|
-
"strict": true,
|
|
203
|
-
"esModuleInterop": true
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
116
|
+
脚本会自动:
|
|
117
|
+
- 创建 `flows/`、`flows/reports/`、`scripts/` 目录
|
|
118
|
+
- 生成 `package.json`(含框架对应依赖)、`tsconfig.json`、`playwright.config.ts`(仅 playwright)
|
|
119
|
+
- 不覆盖已有的 `topology.yaml`
|
|
120
|
+
- 执行 `npm install`
|
|
207
121
|
|
|
208
122
|
---
|
|
209
123
|
|
|
@@ -312,38 +226,11 @@ cat .mcp.json 2>/dev/null | grep -c "deepstorm-playwright"
|
|
|
312
226
|
|
|
313
227
|
#### 8.1 写入 e2eProjectPath 到 settings.json
|
|
314
228
|
|
|
315
|
-
将 `sweep.e2eProjectPath` 写入 `.deepstorm/settings.json`。**不再创建 `.sweep-init` 标记文件。**
|
|
316
|
-
|
|
317
|
-
```bash
|
|
318
|
-
# 值由步骤 0A 确定:
|
|
319
|
-
# 独立项目 → "."
|
|
320
|
-
# 混放项目 → 用户选择的路径(如 "e2e")
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
写入后的 settings.json 结构示例:
|
|
324
|
-
|
|
325
|
-
```json
|
|
326
|
-
{
|
|
327
|
-
"sweep": {
|
|
328
|
-
"e2eFramework": "playwright",
|
|
329
|
-
"e2eProjectPath": "e2e",
|
|
330
|
-
"environments": { ... }
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
#### 8.2 安装依赖
|
|
336
|
-
|
|
337
229
|
```bash
|
|
338
|
-
|
|
339
|
-
if [ "$TARGET_DIR" != "." ]; then
|
|
340
|
-
cd "$TARGET_DIR" && npm install
|
|
341
|
-
else
|
|
342
|
-
npm install
|
|
343
|
-
fi
|
|
230
|
+
node scripts/env-manager.mjs --set-e2e-path "$TARGET_DIR"
|
|
344
231
|
```
|
|
345
232
|
|
|
346
|
-
#### 8.
|
|
233
|
+
#### 8.2 输出完成信息
|
|
347
234
|
|
|
348
235
|
```
|
|
349
236
|
✅ Sweep E2E 测试项目初始化完成!
|