@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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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: `
|
|
8490
|
+
error: `registry \u8FD4\u56DE\u72B6\u6001\u7801 ${response.status}`
|
|
8443
8491
|
};
|
|
8444
8492
|
}
|
|
8445
8493
|
const data = await response.json();
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
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
|
-
|
|
8460
|
-
hasUpdate
|
|
8500
|
+
latest: null,
|
|
8501
|
+
error: hint
|
|
8461
8502
|
};
|
|
8462
|
-
}
|
|
8463
|
-
|
|
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:
|
|
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)(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
66
|
+
"command": "bash .claude/hooks/reef-run-tests.sh"
|
|
67
67
|
}
|
|
68
68
|
]
|
|
69
69
|
}
|
|
@@ -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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
181
|
+
### 9. 展示并确认
|
|
107
182
|
|
|
108
183
|
展示完整提交信息,用户确认即可提交;支持 `--amend` 合并;用户可要求修改后重新生成。
|
|
109
184
|
|
|
110
|
-
###
|
|
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
|
-
###
|
|
192
|
+
### 11. 推送(仅在用户要求时)
|
|
118
193
|
|
|
119
194
|
```bash
|
|
120
195
|
# 新提交(有远程跟踪):git push
|