@deepstorm/cli 0.2.3 → 0.3.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 CHANGED
@@ -6757,6 +6757,10 @@ function cleanInstalled(targetDir) {
6757
6757
  }
6758
6758
  }
6759
6759
  }
6760
+ const hooksJson = path2.join(targetDir, ".claude", "hooks.json");
6761
+ if (fs6.existsSync(hooksJson)) {
6762
+ fs6.rmSync(hooksJson, { force: true });
6763
+ }
6760
6764
  const hooksDir = path2.join(targetDir, ".claude", "hooks");
6761
6765
  if (fs6.existsSync(hooksDir)) {
6762
6766
  fs6.rmSync(hooksDir, { recursive: true, force: true });
@@ -6996,19 +7000,18 @@ async function runSetup(reader, targetDir, cliDir2, options, registry2) {
6996
7000
  enabledMcpJsonServers = Object.keys(servers);
6997
7001
  console.log(`\u2714 \u5DF2\u5B89\u88C5 ${selectedMcpTools.length} \u4E2A MCP \u670D\u52A1`);
6998
7002
  }
6999
- installMcpSkills(tools, reader, cliDir2, targetDir, installedSkillIds);
7003
+ installMcpSkills(tools, reader, cliDir2, targetDir, installedSkillIds, selectedMcpTools);
7000
7004
  for (const tool of tools) {
7001
7005
  const ids = renderToolAssets(tool, reader, cliDir2, targetDir, templateVariables, config, registry2, selectedMcpTools);
7002
7006
  installedSkillIds.push(...ids);
7003
7007
  }
7004
7008
  if (shouldInstallGlobalHooks(tools, reader)) {
7005
7009
  const hooksSrcDir = path6.join(cliDir2, "hooks");
7006
- const targetHooksDir = path6.join(targetDir, ".claude", "hooks");
7007
- const destHooksJson = path6.join(targetHooksDir, "hooks.json");
7010
+ const destHooksJson = path6.join(targetDir, ".claude", "hooks.json");
7008
7011
  for (const tool of tools) {
7009
7012
  const toolHooksJsonPath = path6.join(hooksSrcDir, `${tool}-hooks.json`);
7010
7013
  if (fs10.existsSync(toolHooksJsonPath)) {
7011
- ensureDir(targetHooksDir);
7014
+ ensureDir(path6.join(targetDir, ".claude"));
7012
7015
  const incoming = JSON.parse(fs10.readFileSync(toolHooksJsonPath, "utf-8"));
7013
7016
  mergeHooks(destHooksJson, incoming);
7014
7017
  }
@@ -7092,7 +7095,7 @@ function shouldSkipAgent(agentFile, config) {
7092
7095
  const value = config[configKey];
7093
7096
  return !value || value === "none";
7094
7097
  }
7095
- function renderToolAssets(tool, reader, cliDir2, targetDir, templateVariables, config, registry2, installedMcpTools = [], targetSubDir = ".claude") {
7098
+ function renderToolAssets(tool, reader, cliDir2, targetDir, templateVariables, config, registry2, installedMcpTools = [], targetSubDir = ".claude", skillIdFilter) {
7096
7099
  const installedSkillIds = [];
7097
7100
  const targetSkillsDir = targetSubDir ? path6.join(targetDir, targetSubDir, "skills") : path6.join(targetDir, "skills");
7098
7101
  const toolSkills = reader.getToolSkills(tool);
@@ -7100,6 +7103,7 @@ function renderToolAssets(tool, reader, cliDir2, targetDir, templateVariables, c
7100
7103
  for (const skill of toolSkills) {
7101
7104
  const skillId = getSkillId(skill, registry2);
7102
7105
  if (!skillId) continue;
7106
+ if (skillIdFilter && !skillIdFilter.has(skillId)) continue;
7103
7107
  if (shouldSkipSkill(skill, config)) {
7104
7108
  console.log(` \u23ED ${skillId}\uFF1A\u4F9D\u8D56 ${skill.configKey} \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7`);
7105
7109
  continue;
@@ -7180,14 +7184,21 @@ function renderToolAssets(tool, reader, cliDir2, targetDir, templateVariables, c
7180
7184
  }
7181
7185
  return installedSkillIds;
7182
7186
  }
7183
- function installMcpSkills(tools, reader, cliDir2, targetDir, installedSkillIds) {
7187
+ function installMcpSkills(tools, reader, cliDir2, targetDir, installedSkillIds, selectedMcpTools = []) {
7184
7188
  const mcpSkillsDir = path6.join(cliDir2, "mcp-skills");
7185
7189
  const targetSkillsDir = path6.join(targetDir, ".claude", "skills");
7186
7190
  if (!fs10.existsSync(mcpSkillsDir)) return;
7191
+ if (selectedMcpTools.length === 0) {
7192
+ console.log("\u23ED \u672A\u9009\u62E9 MCP \u670D\u52A1\uFF0C\u8DF3\u8FC7 MCP \u6280\u80FD\u5B89\u88C5");
7193
+ return;
7194
+ }
7195
+ const selectedMcpSet = new Set(selectedMcpTools);
7187
7196
  const neededSkills = /* @__PURE__ */ new Set();
7188
7197
  for (const tool of tools) {
7189
7198
  const toolMcpSkills = reader.getToolMcpSkills(tool);
7190
7199
  for (const skillId of toolMcpSkills) {
7200
+ const service = skillId.replace(/^deepstorm-mcp-/, "").replace(/-(read|write)$/, "");
7201
+ if (!selectedMcpSet.has(service)) continue;
7191
7202
  neededSkills.add(skillId);
7192
7203
  }
7193
7204
  }
@@ -8330,12 +8341,11 @@ function loadRegistryFromCliDir(cliDir2) {
8330
8341
  }
8331
8342
  function mergeToolHooksJson(toolNames, cliDir2, targetDir) {
8332
8343
  const hooksSrcDir = path18.join(cliDir2, "hooks");
8333
- const targetHooksDir = path18.join(targetDir, ".claude", "hooks");
8334
- const destHooksJson = path18.join(targetHooksDir, "hooks.json");
8344
+ const destHooksJson = path18.join(targetDir, ".claude", "hooks.json");
8335
8345
  for (const tool of toolNames) {
8336
8346
  const toolHooksJsonPath = path18.join(hooksSrcDir, `${tool}-hooks.json`);
8337
8347
  if (fs20.existsSync(toolHooksJsonPath)) {
8338
- ensureDir(targetHooksDir);
8348
+ ensureDir(path18.join(targetDir, ".claude"));
8339
8349
  const incoming = JSON.parse(fs20.readFileSync(toolHooksJsonPath, "utf-8"));
8340
8350
  mergeHooks(destHooksJson, incoming);
8341
8351
  }
@@ -8355,7 +8365,19 @@ function syncToolAssets(cliDir2, targetDir, installedSkillIds) {
8355
8365
  }
8356
8366
  const { config, installedMcpTools } = readSettingsConfig(targetDir);
8357
8367
  const templateVariables = buildTemplateVariables(registry2, config, installedMcpTools);
8358
- const backedUpFiles = backupModifiedAssets(targetDir, installedSkillIds, toolNames, registry2);
8368
+ const mcpSkillSet = /* @__PURE__ */ new Set();
8369
+ if (installedMcpTools.length > 0) {
8370
+ const selectedMcpSet = new Set(installedMcpTools);
8371
+ for (const tool of toolNames) {
8372
+ for (const skillId of reader.getToolMcpSkills(tool)) {
8373
+ const service = skillId.replace(/^deepstorm-mcp-/, "").replace(/-(read|write)$/, "");
8374
+ if (selectedMcpSet.has(service)) mcpSkillSet.add(skillId);
8375
+ }
8376
+ }
8377
+ }
8378
+ const mcpSkillIds = [...mcpSkillSet];
8379
+ const allSkillIdsForBackup = [.../* @__PURE__ */ new Set([...installedSkillIds, ...mcpSkillIds])];
8380
+ const backedUpFiles = backupModifiedAssets(targetDir, allSkillIdsForBackup, toolNames, registry2);
8359
8381
  if (backedUpFiles.length > 0) {
8360
8382
  console.log("\n\u26A0 \u68C0\u6D4B\u5230\u7528\u6237\u4FEE\u6539\u7684\u6587\u4EF6\uFF1A");
8361
8383
  for (const info of backedUpFiles) {
@@ -8396,15 +8418,20 @@ function syncToolAssets(cliDir2, targetDir, installedSkillIds) {
8396
8418
  }
8397
8419
  }
8398
8420
  const syncedSkills = actualSkillIds.length > 0 ? actualSkillIds : [...installedSkillIds];
8421
+ const syncedMcpSkillIds = [];
8422
+ if (installedMcpTools.length > 0) {
8423
+ installMcpSkills(toolNames, reader, cliDir2, targetDir, syncedMcpSkillIds, installedMcpTools);
8424
+ }
8399
8425
  mergeToolHooksJson(toolNames, cliDir2, targetDir);
8400
- storeNewChecksums(targetDir, installedSkillIds, toolNames, registry2);
8426
+ const allSkillIds = [...installedSkillIds, ...syncedMcpSkillIds];
8427
+ storeNewChecksums(targetDir, allSkillIds, toolNames, registry2);
8401
8428
  if (hasWarning) {
8402
8429
  console.log("\n\u26A0 \u90E8\u5206\u5DE5\u5177\u540C\u6B65\u51FA\u9519\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u8BD5");
8403
8430
  }
8404
8431
  const uniqueAgents = [...new Set(syncedAgents)];
8405
8432
  const uniqueHooks = [...new Set(syncedHooks)];
8406
8433
  return {
8407
- syncedSkills,
8434
+ syncedSkills: [...syncedSkills, ...syncedMcpSkillIds],
8408
8435
  syncedAgents: uniqueAgents,
8409
8436
  syncedHooks: uniqueHooks,
8410
8437
  backedUpFiles
@@ -8426,12 +8453,33 @@ function upgradeTemplates(cliDir2, targetDir, skillIds) {
8426
8453
 
8427
8454
  // src/commands/update.ts
8428
8455
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@deepstorm/cli/latest";
8456
+ function getRegistryUrl() {
8457
+ return process.env.DEEPSTORM_REGISTRY_URL || NPM_REGISTRY_URL;
8458
+ }
8459
+ function readLocalRegistryFile(url) {
8460
+ let filePath = url;
8461
+ if (filePath.startsWith("file://")) {
8462
+ filePath = filePath.slice(7);
8463
+ }
8464
+ try {
8465
+ if (fs21.existsSync(filePath)) {
8466
+ return JSON.parse(fs21.readFileSync(filePath, "utf-8"));
8467
+ }
8468
+ } catch {
8469
+ }
8470
+ return null;
8471
+ }
8429
8472
  async function checkNpmVersion(fetchFn = globalThis.fetch) {
8430
8473
  const current = getCliVersion();
8474
+ const url = getRegistryUrl();
8431
8475
  try {
8476
+ const localData = readLocalRegistryFile(url);
8477
+ if (localData) {
8478
+ return parseVersionResponse(localData, current);
8479
+ }
8432
8480
  const controller = new AbortController();
8433
8481
  const timeout = setTimeout(() => controller.abort(), 5e3);
8434
- const response = await fetchFn(NPM_REGISTRY_URL, {
8482
+ const response = await fetchFn(url, {
8435
8483
  signal: controller.signal
8436
8484
  });
8437
8485
  clearTimeout(timeout);
@@ -8439,47 +8487,52 @@ async function checkNpmVersion(fetchFn = globalThis.fetch) {
8439
8487
  return {
8440
8488
  current,
8441
8489
  latest: null,
8442
- error: `npm registry \u8FD4\u56DE\u72B6\u6001\u7801 ${response.status}`
8490
+ error: `registry \u8FD4\u56DE\u72B6\u6001\u7801 ${response.status}`
8443
8491
  };
8444
8492
  }
8445
8493
  const data = await response.json();
8446
- const latest = data.version;
8447
- if (!latest) {
8448
- return {
8449
- current,
8450
- latest: null,
8451
- error: "\u65E0\u6CD5\u89E3\u6790 registry \u54CD\u5E94\u4E2D\u7684\u7248\u672C\u53F7"
8452
- };
8453
- }
8454
- const isUpToDate = current === latest;
8455
- const hasUpdate = !isUpToDate && latest !== null;
8494
+ return parseVersionResponse(data, current);
8495
+ } catch (err) {
8496
+ const message = err instanceof Error ? err.message : String(err);
8497
+ const hint = message.includes("abort") ? "\u8FDE\u63A5\u8D85\u65F6" : message.includes("fetch failed") || message.includes("ECONNREFUSED") || message.includes("ENOTFOUND") ? `\u65E0\u6CD5\u8FDE\u63A5 registry\uFF08${url}\uFF09\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC` : message;
8456
8498
  return {
8457
8499
  current,
8458
- latest,
8459
- isUpToDate,
8460
- hasUpdate
8500
+ latest: null,
8501
+ error: hint
8461
8502
  };
8462
- } catch (err) {
8463
- const message = err instanceof Error ? err.message : String(err);
8503
+ }
8504
+ }
8505
+ function parseVersionResponse(data, current) {
8506
+ const latest = data.version;
8507
+ if (!latest) {
8464
8508
  return {
8465
8509
  current,
8466
8510
  latest: null,
8467
- error: message
8511
+ error: "\u65E0\u6CD5\u89E3\u6790 registry \u54CD\u5E94\u4E2D\u7684\u7248\u672C\u53F7"
8468
8512
  };
8469
8513
  }
8514
+ const isUpToDate = current === latest;
8515
+ const hasUpdate = !isUpToDate && latest !== null;
8516
+ return {
8517
+ current,
8518
+ latest,
8519
+ isUpToDate,
8520
+ hasUpdate
8521
+ };
8470
8522
  }
8471
8523
  async function updateCLI(fetchFn) {
8472
8524
  const result = await checkNpmVersion(fetchFn);
8473
8525
  if (result.error) {
8474
8526
  console.log(`\u26A0 \u65E0\u6CD5\u68C0\u67E5\u66F4\u65B0\uFF1A${result.error}`);
8475
- return;
8527
+ return result;
8476
8528
  }
8477
8529
  console.log(`\u2714 \u5F53\u524D\u7248\u672C: v${result.current}`);
8478
8530
  console.log(`\u2714 \u6700\u65B0\u7248\u672C: v${result.latest}`);
8479
8531
  if (result.hasUpdate) {
8480
8532
  console.log("\u2192 \u6B63\u5728\u81EA\u52A8\u66F4\u65B0...");
8533
+ const updateCmd = process.env.DEEPSTORM_UPDATE_CMD || "npm install -g @deepstorm/cli@latest";
8481
8534
  try {
8482
- (0, import_node_child_process.execSync)("npm install -g @deepstorm/cli@latest", { stdio: "inherit" });
8535
+ (0, import_node_child_process.execSync)(updateCmd, { stdio: "inherit" });
8483
8536
  console.log(`
8484
8537
  \u2714 \u5DF2\u66F4\u65B0\u81F3 v${result.latest}`);
8485
8538
  } catch {
@@ -8489,6 +8542,7 @@ async function updateCLI(fetchFn) {
8489
8542
  } else {
8490
8543
  console.log("\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C");
8491
8544
  }
8545
+ return result;
8492
8546
  }
8493
8547
  function getInstalledSkillIds(targetDir) {
8494
8548
  const settingsPath = path19.join(targetDir, ".claude", "settings.json");
@@ -8503,14 +8557,13 @@ function getInstalledSkillIds(targetDir) {
8503
8557
  function registerUpdateCommand(program2) {
8504
8558
  const cliDir2 = __dirname;
8505
8559
  program2.command("update").description("\u68C0\u67E5 CLI \u66F4\u65B0\u5E76\u540C\u6B65\u5DF2\u5B89\u88C5 skill \u7684\u5B98\u65B9\u6700\u65B0\u6A21\u677F").action(async () => {
8506
- await updateCLI();
8507
- console.log("");
8508
8560
  const installedIds = getInstalledSkillIds(process.cwd());
8509
8561
  if (installedIds.length === 0) {
8510
8562
  console.log("\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u7684 skill\uFF0C\u8DF3\u8FC7\u540C\u6B65");
8511
8563
  } else {
8512
8564
  upgradeTemplates(cliDir2, process.cwd(), installedIds);
8513
8565
  }
8566
+ await updateCLI();
8514
8567
  });
8515
8568
  }
8516
8569
 
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "bash ./reef-intent-detect.sh"
8
+ "command": "bash .claude/hooks/reef-intent-detect.sh"
9
9
  }
10
10
  ]
11
11
  }
@@ -16,7 +16,7 @@
16
16
  "hooks": [
17
17
  {
18
18
  "type": "command",
19
- "command": "bash ./reef-block-dangerous.sh"
19
+ "command": "bash .claude/hooks/reef-block-dangerous.sh"
20
20
  }
21
21
  ]
22
22
  },
@@ -25,7 +25,7 @@
25
25
  "hooks": [
26
26
  {
27
27
  "type": "command",
28
- "command": "bash ./reef-protect-files.sh"
28
+ "command": "bash .claude/hooks/reef-protect-files.sh"
29
29
  },
30
30
  {
31
31
  "type": "prompt",
@@ -41,7 +41,7 @@
41
41
  "hooks": [
42
42
  {
43
43
  "type": "command",
44
- "command": "bash ./reef-auto-format.sh"
44
+ "command": "bash .claude/hooks/reef-auto-format.sh"
45
45
  }
46
46
  ]
47
47
  },
@@ -50,7 +50,7 @@
50
50
  "hooks": [
51
51
  {
52
52
  "type": "command",
53
- "command": "bash ./reef-run-tests.sh",
53
+ "command": "bash .claude/hooks/reef-run-tests.sh",
54
54
  "if": "Edit(*.ts)|Write(*.ts)",
55
55
  "async": true,
56
56
  "statusMessage": "Running tests..."
@@ -63,7 +63,7 @@
63
63
  "hooks": [
64
64
  {
65
65
  "type": "command",
66
- "command": "bash ./reef-run-tests.sh"
66
+ "command": "bash .claude/hooks/reef-run-tests.sh"
67
67
  }
68
68
  ]
69
69
  }
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "bash ./sweep-mcp-hook.sh"
8
+ "command": "bash .claude/hooks/sweep-mcp-hook.sh"
9
9
  }
10
10
  ]
11
11
  }
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "bash ./tide-session-preload.sh"
8
+ "command": "bash .claude/hooks/tide-session-preload.sh"
9
9
  }
10
10
  ]
11
11
  }
@@ -39,7 +39,82 @@ git status --short
39
39
 
40
40
  若无任何变更则提示用户并退出。
41
41
 
42
- ### 2. 审查待提交文件
42
+ ### 2. 分支名与任务相关性检查
43
+
44
+ 在审查待提交文件之前,先检查当前分支是否与待提交的任务相关。如果当前在 `main`/`master` 分支上则**必须**创建新分支;如果分支名与任务内容明显不匹配(如命名随意、与 OpenSpec 任务不符),建议创建新分支。
45
+
46
+ ```bash
47
+ BRANCH=$(git branch --show-current)
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
72
+ ```
73
+
74
+ **判断规则(LLM 自行推理执行):**
75
+
76
+ 1. **如果 MUST_NEW_BRANCH=true**(当前在 main/master):
77
+ - 直接进入步骤 3「创建新分支」,无需询问用户
78
+ 2. **如果分支名明显不相关**(如 `temp-xxx`、`test-foo`、或随意命名与当前变更毫无关联):
79
+ - 向用户说明:「当前分支 $BRANCH 与待提交的变更内容似乎不匹配,是否创建一个新分支?」
80
+ - 用户同意 → 进入步骤 3「创建新分支」
81
+ - 用户不同意 → 继续当前分支
82
+ 3. **如果分支名合理**(如与 OpenSpec 任务同名,或包含功能描述的 kebab-case):
83
+ - 直接继续
84
+
85
+ ### 3. 创建新分支
86
+
87
+ 当需要创建新分支时,按以下逻辑确定分支名:
88
+
89
+ **分支名生成规则(优先级从高到低):**
90
+ 1. **OpenSpec 任务名**:如果检测到 `openspec/changes/<task>/proposal.md`,使用 `<task>` 作为分支名
91
+ 2. **用户输入**:询问用户想要的分支名
92
+ 3. **AI 推导**:根据变更内容总结生成 kebab-case 分支名(例如 `feat/add-user-auth`、`fix/login-timeout`)
93
+
94
+ ```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>"
113
+ ```
114
+
115
+ > **注意**:创建新分支后,后续步骤(审查文件、范围检查、测试等)在新分支上继续执行。
116
+
117
+ ### 4. 审查待提交文件
43
118
 
44
119
  展示变更清单,检查敏感文件:
45
120
 
@@ -49,7 +124,7 @@ git diff --stat
49
124
 
50
125
  如包含 `.env`、凭据、证书或大型二进制文件,让用户确认或排除后再继续。
51
126
 
52
- ### 2.4 分支范围检查
127
+ ### 5. 分支范围检查
53
128
 
54
129
  检查当前分支是否涉及多个业务领域。如果跨领域,给出拆分建议并中止提交。
55
130
 
@@ -68,7 +143,7 @@ if [ -f "$SCOPE_HOOK" ]; then
68
143
  fi
69
144
  ```
70
145
 
71
- ### 2.5 运行单元测试
146
+ ### 6. 运行单元测试
72
147
 
73
148
  ```bash
74
149
  git status --short
@@ -84,7 +159,7 @@ git status --short
84
159
  - 测试全部通过 → 继续
85
160
  - 任一测试失败 → 提示用户修复后再提交
86
161
 
87
- ### 3. 收集上下文
162
+ ### 7. 收集上下文
88
163
 
89
164
  ```bash
90
165
  BRANCH=$(git branch --show-current)
@@ -97,24 +172,24 @@ grep -iE '(issue|jira|lc-|proj-)' "openspec/changes/$BRANCH/proposal.md" 2>/dev/
97
172
  git log "$FORK_POINT"..HEAD --format="%B" 2>/dev/null | grep -ioP '[A-Z]+-\d+' | head -1
98
173
  ```
99
174
 
100
- ### 4. 生成提交信息
175
+ ### 8. 生成提交信息
101
176
 
102
177
  **标题:** 优先 OpenSpec proposal 标题 → 分支名(kebab-case 转中文)→ 变更总结。依赖更新用 `更新 {依赖} 至 {版本}`,问题修复用 `解决{问题描述}的问题`。
103
178
 
104
179
  **正文:** 中/复杂变更用 `本次提交改动如下:` + 3-6 条要点。尾部隔空行加 `JIRA: {完整 URL}`(优先从 proposal.md/jira-start 元数据中获取 JIRA 链接),如有 PRD 链接加 `Ref: {URL}`。
105
180
 
106
- ### 5. 展示并确认
181
+ ### 9. 展示并确认
107
182
 
108
183
  展示完整提交信息,用户确认即可提交;支持 `--amend` 合并;用户可要求修改后重新生成。
109
184
 
110
- ### 6. 执行提交
185
+ ### 10. 执行提交
111
186
 
112
187
  ```bash
113
188
  git add -A && git commit -m "<完整提交信息>"
114
189
  # 用户要求 amend 时: git commit --amend -m "<完整提交信息>"
115
190
  ```
116
191
 
117
- ### 7. 推送(仅在用户要求时)
192
+ ### 11. 推送(仅在用户要求时)
118
193
 
119
194
  ```bash
120
195
  # 新提交(有远程跟踪):git push
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstorm/cli",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "DeepStorm CLI — 一键配置项目开发环境",
5
5
  "license": "MIT",
6
6
  "author": "billkang",