clawt 3.6.2 → 3.7.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/index.js +45 -5
- package/package.json +1 -1
- package/src/commands/merge.ts +4 -3
- package/src/commands/remove.ts +1 -0
- package/src/index.ts +7 -2
- package/src/utils/config-strategy.ts +7 -0
- package/src/utils/conflict-resolver.ts +1 -1
- package/src/utils/formatter.ts +9 -2
- package/src/utils/index.ts +1 -0
- package/src/utils/interactive.ts +27 -0
- package/src/utils/ui-prompts.ts +14 -0
- package/src/utils/validate-branch.ts +13 -1
- package/tests/unit/commands/cover-validate.test.ts +1 -0
- package/tests/unit/commands/merge.test.ts +1 -0
- package/tests/unit/commands/remove.test.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -1313,6 +1313,20 @@ function gitWorktreePrune(cwd) {
|
|
|
1313
1313
|
// src/utils/formatter.ts
|
|
1314
1314
|
import chalk4 from "chalk";
|
|
1315
1315
|
import { createInterface } from "readline";
|
|
1316
|
+
|
|
1317
|
+
// src/utils/interactive.ts
|
|
1318
|
+
var nonInteractiveFlag = false;
|
|
1319
|
+
function setNonInteractive(value) {
|
|
1320
|
+
nonInteractiveFlag = value;
|
|
1321
|
+
}
|
|
1322
|
+
function isNonInteractive() {
|
|
1323
|
+
if (nonInteractiveFlag) return true;
|
|
1324
|
+
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
1325
|
+
if (process.env.CLAWT_NON_INTERACTIVE === "true" || process.env.CLAWT_NON_INTERACTIVE === "1") return true;
|
|
1326
|
+
return false;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/utils/formatter.ts
|
|
1316
1330
|
function printSuccess(message) {
|
|
1317
1331
|
console.log(chalk4.green(message));
|
|
1318
1332
|
}
|
|
@@ -1334,7 +1348,10 @@ function printSeparator() {
|
|
|
1334
1348
|
function printDoubleSeparator() {
|
|
1335
1349
|
console.log(MESSAGES.DOUBLE_SEPARATOR);
|
|
1336
1350
|
}
|
|
1337
|
-
function confirmAction(question) {
|
|
1351
|
+
function confirmAction(question, nonInteractiveDefault = true) {
|
|
1352
|
+
if (isNonInteractive()) {
|
|
1353
|
+
return Promise.resolve(nonInteractiveDefault);
|
|
1354
|
+
}
|
|
1338
1355
|
return new Promise((resolve4) => {
|
|
1339
1356
|
const rl = createInterface({
|
|
1340
1357
|
input: process.stdin,
|
|
@@ -1610,6 +1627,14 @@ async function rebuildValidateBranch(branchName, cwd) {
|
|
|
1610
1627
|
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u91CD\u5EFA: ${getValidateBranchName(branchName)}`);
|
|
1611
1628
|
}
|
|
1612
1629
|
async function handleDirtyWorkingDir(cwd) {
|
|
1630
|
+
if (isNonInteractive()) {
|
|
1631
|
+
gitAddAll(cwd);
|
|
1632
|
+
gitStashPush("clawt:auto-stash", cwd);
|
|
1633
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1634
|
+
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
1635
|
+
}
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1613
1638
|
printWarning("\u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
|
|
1614
1639
|
const choice = await new Enquirer.Select({
|
|
1615
1640
|
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
@@ -1659,7 +1684,7 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1659
1684
|
return;
|
|
1660
1685
|
}
|
|
1661
1686
|
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
1662
|
-
const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
|
|
1687
|
+
const confirmed = isNonInteractive() ? true : await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
|
|
1663
1688
|
if (!confirmed) {
|
|
1664
1689
|
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
1665
1690
|
}
|
|
@@ -2094,6 +2119,9 @@ import { statSync as statSync3 } from "fs";
|
|
|
2094
2119
|
// src/utils/ui-prompts.ts
|
|
2095
2120
|
import Enquirer3 from "enquirer";
|
|
2096
2121
|
async function promptSelectBranch(worktrees, message) {
|
|
2122
|
+
if (isNonInteractive()) {
|
|
2123
|
+
throw new ClawtError("\u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u65E0\u6CD5\u8FDB\u884C\u5206\u652F\u9009\u62E9\uFF0C\u8BF7\u901A\u8FC7 -b \u53C2\u6570\u7CBE\u786E\u6307\u5B9A\u5206\u652F\u540D");
|
|
2124
|
+
}
|
|
2097
2125
|
const selectedBranch = await new Enquirer3.Select({
|
|
2098
2126
|
message,
|
|
2099
2127
|
choices: worktrees.map((wt) => ({
|
|
@@ -2104,6 +2132,9 @@ async function promptSelectBranch(worktrees, message) {
|
|
|
2104
2132
|
return worktrees.find((wt) => wt.branch === selectedBranch);
|
|
2105
2133
|
}
|
|
2106
2134
|
async function promptMultiSelectBranches(worktrees, message) {
|
|
2135
|
+
if (isNonInteractive()) {
|
|
2136
|
+
throw new ClawtError("\u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u65E0\u6CD5\u8FDB\u884C\u5206\u652F\u591A\u9009\uFF0C\u8BF7\u901A\u8FC7 -b \u53C2\u6570\u7CBE\u786E\u6307\u5B9A\u5206\u652F\u540D");
|
|
2137
|
+
}
|
|
2107
2138
|
const branchChoices = worktrees.map((wt) => ({
|
|
2108
2139
|
name: wt.branch,
|
|
2109
2140
|
message: wt.branch
|
|
@@ -2143,6 +2174,9 @@ async function promptMultiSelectBranches(worktrees, message) {
|
|
|
2143
2174
|
return worktrees.filter((wt) => selectedBranches.includes(wt.branch));
|
|
2144
2175
|
}
|
|
2145
2176
|
async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
2177
|
+
if (isNonInteractive()) {
|
|
2178
|
+
throw new ClawtError("\u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u65E0\u6CD5\u8FDB\u884C\u5206\u652F\u591A\u9009\uFF0C\u8BF7\u901A\u8FC7 -b \u53C2\u6570\u7CBE\u786E\u6307\u5B9A\u5206\u652F\u540D");
|
|
2179
|
+
}
|
|
2146
2180
|
const groups = groupWorktreesByDate(worktrees);
|
|
2147
2181
|
const choices = buildGroupedChoices(groups);
|
|
2148
2182
|
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
@@ -3150,6 +3184,9 @@ function formatConfigValue(value) {
|
|
|
3150
3184
|
return chalk7.cyan(String(value));
|
|
3151
3185
|
}
|
|
3152
3186
|
async function interactiveConfigEditor(config2, definitions, options) {
|
|
3187
|
+
if (isNonInteractive()) {
|
|
3188
|
+
throw new ClawtError("\u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u65E0\u6CD5\u4F7F\u7528\u4EA4\u4E92\u5F0F\u914D\u7F6E\u7F16\u8F91\u5668\uFF0C\u8BF7\u4F7F\u7528 clawt config set <key> <value>");
|
|
3189
|
+
}
|
|
3153
3190
|
const keys = Object.keys(definitions);
|
|
3154
3191
|
const disabledKeys = options?.disabledKeys ?? {};
|
|
3155
3192
|
const configRecord = config2;
|
|
@@ -4920,7 +4957,7 @@ async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch
|
|
|
4920
4957
|
if (!hasCommitWithMessage(branchName, AUTO_SAVE_COMMIT_MESSAGE, mainWorktreePath)) {
|
|
4921
4958
|
return false;
|
|
4922
4959
|
}
|
|
4923
|
-
const shouldSquash = await confirmAction(MESSAGES.MERGE_SQUASH_PROMPT);
|
|
4960
|
+
const shouldSquash = await confirmAction(MESSAGES.MERGE_SQUASH_PROMPT, false);
|
|
4924
4961
|
if (!shouldSquash) {
|
|
4925
4962
|
return false;
|
|
4926
4963
|
}
|
|
@@ -4942,7 +4979,7 @@ async function shouldCleanupAfterMerge(branchName) {
|
|
|
4942
4979
|
printInfo(`\u5DF2\u914D\u7F6E\u81EA\u52A8\u5220\u9664\uFF0Cmerge \u6210\u529F\u540E\u5C06\u81EA\u52A8\u6E05\u7406 worktree \u548C\u5206\u652F: ${branchName}`);
|
|
4943
4980
|
return true;
|
|
4944
4981
|
}
|
|
4945
|
-
return confirmAction(`\u662F\u5426\u5220\u9664\u5BF9\u5E94\u7684 worktree \u548C\u5206\u652F (${branchName})\uFF1F
|
|
4982
|
+
return confirmAction(`\u662F\u5426\u5220\u9664\u5BF9\u5E94\u7684 worktree \u548C\u5206\u652F (${branchName})\uFF1F`, true);
|
|
4946
4983
|
}
|
|
4947
4984
|
function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
4948
4985
|
cleanupWorktrees([{ path: worktreePath, branch: branchName }]);
|
|
@@ -5914,11 +5951,14 @@ var require2 = createRequire(import.meta.url);
|
|
|
5914
5951
|
var { version } = require2("../package.json");
|
|
5915
5952
|
ensureClawtDirs();
|
|
5916
5953
|
var program = new Command();
|
|
5917
|
-
program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version).option("--debug", "\u8F93\u51FA\u8BE6\u7EC6\u8C03\u8BD5\u4FE1\u606F\u5230\u7EC8\u7AEF");
|
|
5954
|
+
program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version).option("--debug", "\u8F93\u51FA\u8BE6\u7EC6\u8C03\u8BD5\u4FE1\u606F\u5230\u7EC8\u7AEF").option("-y, --yes", "\u8DF3\u8FC7\u6240\u6709\u4EA4\u4E92\u5F0F\u786E\u8BA4\uFF0C\u9002\u7528\u4E8E\u811A\u672C/CI \u73AF\u5883");
|
|
5918
5955
|
program.hook("preAction", (thisCommand) => {
|
|
5919
5956
|
if (thisCommand.opts().debug) {
|
|
5920
5957
|
enableConsoleTransport();
|
|
5921
5958
|
}
|
|
5959
|
+
if (thisCommand.opts().yes) {
|
|
5960
|
+
setNonInteractive(true);
|
|
5961
|
+
}
|
|
5922
5962
|
});
|
|
5923
5963
|
registerListCommand(program);
|
|
5924
5964
|
registerCreateCommand(program);
|
package/package.json
CHANGED
package/src/commands/merge.ts
CHANGED
|
@@ -82,8 +82,8 @@ async function handleSquashIfNeeded(
|
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
//
|
|
86
|
-
const shouldSquash = await confirmAction(MESSAGES.MERGE_SQUASH_PROMPT);
|
|
85
|
+
// 提示用户是否压缩(非交互模式下默认跳过,保留原始提交)
|
|
86
|
+
const shouldSquash = await confirmAction(MESSAGES.MERGE_SQUASH_PROMPT, false);
|
|
87
87
|
if (!shouldSquash) {
|
|
88
88
|
return false;
|
|
89
89
|
}
|
|
@@ -120,7 +120,8 @@ async function shouldCleanupAfterMerge(branchName: string): Promise<boolean> {
|
|
|
120
120
|
printInfo(`已配置自动删除,merge 成功后将自动清理 worktree 和分支: ${branchName}`);
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
// 非交互模式下自动删除
|
|
124
|
+
return confirmAction(`是否删除对应的 worktree 和分支 (${branchName})?`, true);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
package/src/commands/remove.ts
CHANGED
|
@@ -100,6 +100,7 @@ async function handleRemove(options: RemoveOptions): Promise<void> {
|
|
|
100
100
|
let shouldDeleteBranch = autoDelete;
|
|
101
101
|
|
|
102
102
|
if (!autoDelete) {
|
|
103
|
+
// 非交互模式下默认删除分支(删除 worktree 时分支通常也不再需要)
|
|
103
104
|
shouldDeleteBranch = await confirmAction(MESSAGES.REMOVE_CONFIRM_DELETE_BRANCHES);
|
|
104
105
|
if (!shouldDeleteBranch) {
|
|
105
106
|
printHint(MESSAGES.REMOVE_BRANCHES_KEPT);
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { ClawtError } from './errors/index.js';
|
|
4
4
|
import { logger, enableConsoleTransport } from './logger/index.js';
|
|
5
5
|
import { EXIT_CODES } from './constants/index.js';
|
|
6
|
-
import { printError, ensureClawtDirs, loadConfig, applyAliases, checkForUpdates } from './utils/index.js';
|
|
6
|
+
import { printError, ensureClawtDirs, loadConfig, applyAliases, checkForUpdates, setNonInteractive } from './utils/index.js';
|
|
7
7
|
import { registerListCommand } from './commands/list.js';
|
|
8
8
|
import { registerCreateCommand } from './commands/create.js';
|
|
9
9
|
import { registerRemoveCommand } from './commands/remove.js';
|
|
@@ -36,13 +36,18 @@ program
|
|
|
36
36
|
.name('clawt')
|
|
37
37
|
.description('本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具')
|
|
38
38
|
.version(version)
|
|
39
|
-
.option('--debug', '输出详细调试信息到终端')
|
|
39
|
+
.option('--debug', '输出详细调试信息到终端')
|
|
40
|
+
.option('-y, --yes', '跳过所有交互式确认,适用于脚本/CI 环境');
|
|
40
41
|
|
|
41
42
|
// 在子命令 action 执行前检查 --debug 选项,按需启用控制台日志
|
|
42
43
|
program.hook('preAction', (thisCommand) => {
|
|
43
44
|
if (thisCommand.opts().debug) {
|
|
44
45
|
enableConsoleTransport();
|
|
45
46
|
}
|
|
47
|
+
// 检测 --yes 选项,启用非交互模式
|
|
48
|
+
if (thisCommand.opts().yes) {
|
|
49
|
+
setNonInteractive(true);
|
|
50
|
+
}
|
|
46
51
|
});
|
|
47
52
|
|
|
48
53
|
// 注册所有命令
|
|
@@ -2,6 +2,8 @@ import chalk from 'chalk';
|
|
|
2
2
|
import Enquirer from 'enquirer';
|
|
3
3
|
import { DEFAULT_CONFIG, CONFIG_DEFINITIONS, MESSAGES } from '../constants/index.js';
|
|
4
4
|
import type { ClawtConfig } from '../types/index.js';
|
|
5
|
+
import { isNonInteractive } from './interactive.js';
|
|
6
|
+
import { ClawtError } from '../errors/index.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* 校验 key 是否为有效的配置项名称
|
|
@@ -132,6 +134,11 @@ export async function interactiveConfigEditor<T extends object>(
|
|
|
132
134
|
definitions: Record<string, { description: string; allowedValues?: readonly string[] }>,
|
|
133
135
|
options?: { selectPrompt?: string; disabledKeys?: Record<string, string> },
|
|
134
136
|
): Promise<{ key: keyof T; newValue: unknown }> {
|
|
137
|
+
// 非交互模式下无法进行配置编辑,引导使用 config set 命令
|
|
138
|
+
if (isNonInteractive()) {
|
|
139
|
+
throw new ClawtError('非交互模式下无法使用交互式配置编辑器,请使用 clawt config set <key> <value>');
|
|
140
|
+
}
|
|
141
|
+
|
|
135
142
|
const keys = Object.keys(definitions);
|
|
136
143
|
const disabledKeys = options?.disabledKeys ?? {};
|
|
137
144
|
const configRecord = config as Record<string, unknown>;
|
|
@@ -162,7 +162,7 @@ export async function handleMergeConflict(
|
|
|
162
162
|
return resolveConflictsWithAI(currentBranch, incomingBranch, cwd);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
// mode === 'ask'
|
|
165
|
+
// mode === 'ask'(非交互模式下默认使用 AI 解决,因为无法手动解决)
|
|
166
166
|
const shouldUseAI = await confirmAction(MESSAGES.MERGE_CONFLICT_ASK_AI);
|
|
167
167
|
if (!shouldUseAI) {
|
|
168
168
|
throw new ClawtError(MESSAGES.MERGE_CONFLICT_MANUAL);
|
package/src/utils/formatter.ts
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { MESSAGES } from '../constants/index.js';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import type { WorktreeStatus } from '../types/index.js';
|
|
5
|
+
import { isNonInteractive } from './interactive.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* 输出成功信息
|
|
@@ -58,11 +59,17 @@ export function printDoubleSeparator(): void {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* 简易 yes/no
|
|
62
|
+
* 简易 yes/no 确认
|
|
63
|
+
* 非交互模式下根据 nonInteractiveDefault 参数自动返回默认值
|
|
62
64
|
* @param {string} question - 确认问题
|
|
65
|
+
* @param {boolean} [nonInteractiveDefault=true] - 非交互模式下的默认返回值,true 表示自动确认,false 表示自动拒绝
|
|
63
66
|
* @returns {Promise<boolean>} 用户是否确认
|
|
64
67
|
*/
|
|
65
|
-
export function confirmAction(question: string): Promise<boolean> {
|
|
68
|
+
export function confirmAction(question: string, nonInteractiveDefault: boolean = true): Promise<boolean> {
|
|
69
|
+
// 非交互模式下返回调用方声明的默认值
|
|
70
|
+
if (isNonInteractive()) {
|
|
71
|
+
return Promise.resolve(nonInteractiveDefault);
|
|
72
|
+
}
|
|
66
73
|
return new Promise((resolve) => {
|
|
67
74
|
const rl = createInterface({
|
|
68
75
|
input: process.stdin,
|
package/src/utils/index.ts
CHANGED
|
@@ -82,6 +82,7 @@ export { checkForUpdates } from './update-checker.js';
|
|
|
82
82
|
export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch, guardMainWorkBranchExists, getValidateRunCommand } from './project-config.js';
|
|
83
83
|
export { getValidateBranchName, createValidateBranch, deleteValidateBranch, rebuildValidateBranch, ensureOnMainWorkBranch, handleDirtyWorkingDir } from './validate-branch.js';
|
|
84
84
|
export { safeStringify } from './json.js';
|
|
85
|
+
export { isNonInteractive, setNonInteractive } from './interactive.js';
|
|
85
86
|
export { executeRunCommand } from './validate-runner.js';
|
|
86
87
|
export { migrateChangesViaPatch, computeCurrentTreeHash, saveCurrentSnapshotTree, loadOldSnapshotToStage, switchToValidateBranch } from './validate-core.js';
|
|
87
88
|
export { InteractivePanel } from './interactive-panel.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 非交互模式判断工具
|
|
3
|
+
* 优先级:CLI --yes > 环境变量 CI / CLAWT_NON_INTERACTIVE > 默认交互模式
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** 运行时标志,由 --yes 全局选项在 preAction hook 中设置 */
|
|
7
|
+
let nonInteractiveFlag = false;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 设置非交互模式运行时标志
|
|
11
|
+
* @param {boolean} value - 是否启用非交互模式
|
|
12
|
+
*/
|
|
13
|
+
export function setNonInteractive(value: boolean): void {
|
|
14
|
+
nonInteractiveFlag = value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 判断当前是否处于非交互模式
|
|
19
|
+
* 检查顺序:运行时标志(--yes)→ CI 环境变量 → CLAWT_NON_INTERACTIVE 环境变量
|
|
20
|
+
* @returns {boolean} 是否为非交互模式
|
|
21
|
+
*/
|
|
22
|
+
export function isNonInteractive(): boolean {
|
|
23
|
+
if (nonInteractiveFlag) return true;
|
|
24
|
+
if (process.env.CI === 'true' || process.env.CI === '1') return true;
|
|
25
|
+
if (process.env.CLAWT_NON_INTERACTIVE === 'true' || process.env.CLAWT_NON_INTERACTIVE === '1') return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
package/src/utils/ui-prompts.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
} from '../constants/index.js';
|
|
6
6
|
import type { WorktreeInfo } from '../types/index.js';
|
|
7
7
|
import { groupWorktreesByDate, buildGroupedChoices, buildGroupMembershipMap } from './worktree-matcher.js';
|
|
8
|
+
import { isNonInteractive } from './interactive.js';
|
|
9
|
+
import { ClawtError } from '../errors/index.js';
|
|
8
10
|
|
|
9
11
|
/** enquirer MultiSelect 选项条目的运行时结构 */
|
|
10
12
|
export interface MultiSelectChoice {
|
|
@@ -41,6 +43,10 @@ export type GroupedChoice = { name: string; message: string } | MultiSelectSepar
|
|
|
41
43
|
* @returns {Promise<WorktreeInfo>} 用户选择的 worktree
|
|
42
44
|
*/
|
|
43
45
|
export async function promptSelectBranch(worktrees: WorktreeInfo[], message: string): Promise<WorktreeInfo> {
|
|
46
|
+
// 非交互模式下无法进行交互选择,要求用户通过 -b 精确指定
|
|
47
|
+
if (isNonInteractive()) {
|
|
48
|
+
throw new ClawtError('非交互模式下无法进行分支选择,请通过 -b 参数精确指定分支名');
|
|
49
|
+
}
|
|
44
50
|
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
45
51
|
const selectedBranch: string = await new Enquirer.Select({
|
|
46
52
|
message,
|
|
@@ -62,6 +68,10 @@ export async function promptSelectBranch(worktrees: WorktreeInfo[], message: str
|
|
|
62
68
|
* @returns {Promise<WorktreeInfo[]>} 用户选择的 worktree 列表
|
|
63
69
|
*/
|
|
64
70
|
export async function promptMultiSelectBranches(worktrees: WorktreeInfo[], message: string): Promise<WorktreeInfo[]> {
|
|
71
|
+
// 非交互模式下无法进行交互多选,要求用户通过 -b 精确指定
|
|
72
|
+
if (isNonInteractive()) {
|
|
73
|
+
throw new ClawtError('非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
74
|
+
}
|
|
65
75
|
// 构建 choices 列表,顶部插入全选选项
|
|
66
76
|
const branchChoices = worktrees.map((wt) => ({
|
|
67
77
|
name: wt.branch,
|
|
@@ -131,6 +141,10 @@ export async function promptGroupedMultiSelectBranches(
|
|
|
131
141
|
worktrees: WorktreeInfo[],
|
|
132
142
|
message: string,
|
|
133
143
|
): Promise<WorktreeInfo[]> {
|
|
144
|
+
// 非交互模式下无法进行交互多选,要求用户通过 -b 精确指定
|
|
145
|
+
if (isNonInteractive()) {
|
|
146
|
+
throw new ClawtError('非交互模式下无法进行分支多选,请通过 -b 参数精确指定分支名');
|
|
147
|
+
}
|
|
134
148
|
const groups = groupWorktreesByDate(worktrees);
|
|
135
149
|
const choices = buildGroupedChoices(groups);
|
|
136
150
|
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
@@ -5,6 +5,7 @@ import { checkBranchExists, createBranch, deleteBranch, getCurrentBranch, gitChe
|
|
|
5
5
|
import { getMainWorkBranch } from './project-config.js';
|
|
6
6
|
import { printWarning, confirmAction } from './formatter.js';
|
|
7
7
|
import { ClawtError } from '../errors/index.js';
|
|
8
|
+
import { isNonInteractive } from './interactive.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 生成验证分支名
|
|
@@ -88,6 +89,16 @@ export async function rebuildValidateBranch(branchName: string, cwd?: string): P
|
|
|
88
89
|
* @param {string} [cwd] - 工作目录
|
|
89
90
|
*/
|
|
90
91
|
export async function handleDirtyWorkingDir(cwd?: string): Promise<void> {
|
|
92
|
+
// 非交互模式下默认执行 stash(安全策略,不丢弃代码)
|
|
93
|
+
if (isNonInteractive()) {
|
|
94
|
+
gitAddAll(cwd);
|
|
95
|
+
gitStashPush('clawt:auto-stash', cwd);
|
|
96
|
+
if (!isWorkingDirClean(cwd)) {
|
|
97
|
+
throw new ClawtError('工作区仍然不干净,请手动处理');
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
printWarning('当前分支有未提交的更改,请选择处理方式:\n');
|
|
92
103
|
|
|
93
104
|
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
@@ -159,7 +170,8 @@ export async function ensureOnMainWorkBranch(cwd?: string): Promise<void> {
|
|
|
159
170
|
|
|
160
171
|
// 当前在其他分支上,警告并确认后处理脏工作区再切换
|
|
161
172
|
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
162
|
-
|
|
173
|
+
// 非交互模式下自动确认继续
|
|
174
|
+
const confirmed = isNonInteractive() ? true : await confirmAction('是否继续执行?');
|
|
163
175
|
if (!confirmed) {
|
|
164
176
|
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
165
177
|
}
|
|
@@ -50,6 +50,7 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
50
50
|
confirmAction: vi.fn().mockResolvedValue(true),
|
|
51
51
|
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
52
52
|
guardMainWorkBranchExists: vi.fn(),
|
|
53
|
+
isNonInteractive: vi.fn().mockReturnValue(false),
|
|
53
54
|
}));
|
|
54
55
|
|
|
55
56
|
import { registerCoverValidateCommand, extractTargetBranchName, findTargetWorktreePath, computeIncrementalPatch } from '../../../src/commands/cover-validate.js';
|
|
@@ -78,6 +78,7 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
78
78
|
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
79
79
|
guardMainWorkBranchExists: vi.fn(),
|
|
80
80
|
handleMergeConflict: vi.fn(),
|
|
81
|
+
isNonInteractive: vi.fn().mockReturnValue(false),
|
|
81
82
|
}));
|
|
82
83
|
|
|
83
84
|
import { registerMergeCommand } from '../../../src/commands/merge.js';
|
|
@@ -56,6 +56,7 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
56
56
|
getCurrentBranch: vi.fn(),
|
|
57
57
|
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
58
58
|
guardMainWorkBranchExists: vi.fn(),
|
|
59
|
+
isNonInteractive: vi.fn().mockReturnValue(false),
|
|
59
60
|
}));
|
|
60
61
|
|
|
61
62
|
import { registerRemoveCommand } from '../../../src/commands/remove.js';
|