clawt 3.7.0 → 3.8.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/.claude/agents/docs-sync-updater.md +44 -24
- package/.clawt/postCreate.sh +2 -0
- package/CLAUDE.md +10 -0
- package/README.md +50 -2
- package/dist/index.js +180 -29
- package/dist/postinstall.js +33 -1
- package/docs/alias.md +114 -0
- package/docs/completion.md +55 -0
- package/docs/config-file.md +49 -0
- package/docs/config.md +93 -0
- package/docs/cover-validate.md +94 -0
- package/docs/create.md +105 -0
- package/docs/home.md +70 -0
- package/docs/init.md +89 -0
- package/docs/list.md +73 -0
- package/docs/log.md +67 -0
- package/docs/merge.md +152 -0
- package/docs/notification.md +94 -0
- package/docs/post-create-hook.md +142 -0
- package/docs/project-config.md +139 -0
- package/docs/projects.md +135 -0
- package/docs/remove.md +90 -0
- package/docs/reset.md +37 -0
- package/docs/resume.md +132 -0
- package/docs/run.md +150 -0
- package/docs/spec.md +431 -0
- package/docs/status.md +345 -0
- package/docs/sync.md +128 -0
- package/docs/tasks.md +74 -0
- package/docs/update-check.md +95 -0
- package/docs/validate.md +416 -0
- package/package.json +1 -1
- package/src/commands/create.ts +5 -0
- package/src/commands/run.ts +12 -0
- package/src/constants/config.ts +1 -1
- package/src/constants/git.ts +3 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/post-create.ts +29 -0
- package/src/constants/project-config.ts +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/post-create.ts +198 -0
- package/src/types/command.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/postCreateHook.ts +24 -0
- package/src/types/projectConfig.ts +3 -1
- package/src/utils/claude.ts +2 -1
- package/src/utils/git-core.ts +4 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/shell.ts +3 -0
- package/src/utils/task-executor.ts +5 -1
- package/tests/unit/commands/create.test.ts +1 -0
- package/tests/unit/commands/run.test.ts +1 -0
- package/tests/unit/constants/messages-post-create.test.ts +112 -0
- package/tests/unit/hooks/post-create.test.ts +434 -0
- package/tests/unit/utils/claude.test.ts +76 -1
package/dist/index.js
CHANGED
|
@@ -564,6 +564,30 @@ var TASKS_CMD_MESSAGES = {
|
|
|
564
564
|
TASK_INIT_HINT: (path2) => `\u4F7F\u7528 clawt run -f ${path2} \u6267\u884C\u4EFB\u52A1`
|
|
565
565
|
};
|
|
566
566
|
|
|
567
|
+
// src/constants/messages/post-create.ts
|
|
568
|
+
var POST_CREATE_MESSAGES = {
|
|
569
|
+
/** hook 执行跳过(--no-post-create) */
|
|
570
|
+
HOOK_SKIPPED: "\u5DF2\u8DF3\u8FC7 postCreate hook\uFF08--no-post-create\uFF09",
|
|
571
|
+
/** 无 hook 配置 */
|
|
572
|
+
HOOK_NOT_CONFIGURED: "\u672A\u914D\u7F6E postCreate hook\uFF0C\u8DF3\u8FC7",
|
|
573
|
+
/** hook 来源提示 */
|
|
574
|
+
HOOK_SOURCE_INFO: (source) => `postCreate hook \u6765\u6E90: ${source}`,
|
|
575
|
+
/** hook 开始执行 */
|
|
576
|
+
HOOK_EXECUTING: (branch, command) => `[${branch}] \u6B63\u5728\u6267\u884C postCreate hook: ${command}`,
|
|
577
|
+
/** hook 执行成功 */
|
|
578
|
+
HOOK_SUCCESS: (branch) => `[${branch}] postCreate hook \u6267\u884C\u6210\u529F`,
|
|
579
|
+
/** hook 执行失败 */
|
|
580
|
+
HOOK_FAILED: (branch, error) => `[${branch}] postCreate hook \u6267\u884C\u5931\u8D25: ${error}`,
|
|
581
|
+
/** hook 执行汇总 */
|
|
582
|
+
HOOK_SUMMARY: (succeeded, failed) => `postCreate hook \u6267\u884C\u5B8C\u6210: ${succeeded} \u6210\u529F, ${failed} \u5931\u8D25`,
|
|
583
|
+
/** hook 后台执行中提示 */
|
|
584
|
+
HOOK_BACKGROUND_START: (count, command) => `postCreate hook \u6B63\u5728\u540E\u53F0\u6267\u884C (${count} \u4E2A worktree): ${command}`,
|
|
585
|
+
/** postCreate.sh 自动添加执行权限 */
|
|
586
|
+
POST_CREATE_SCRIPT_AUTO_CHMOD: (path2) => `${path2} \u4E0D\u53EF\u6267\u884C\uFF0C\u5DF2\u81EA\u52A8\u6DFB\u52A0\u6267\u884C\u6743\u9650`,
|
|
587
|
+
/** postCreate.sh 不可执行(自动 chmod 失败时降级提示) */
|
|
588
|
+
POST_CREATE_SCRIPT_NOT_EXECUTABLE: (path2) => `\u68C0\u6D4B\u5230 ${path2} \u4F46\u4E0D\u53EF\u6267\u884C\uFF0C\u81EA\u52A8\u6DFB\u52A0\u6743\u9650\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C chmod +x ${path2}`
|
|
589
|
+
};
|
|
590
|
+
|
|
567
591
|
// src/constants/messages/interactive-panel.ts
|
|
568
592
|
import chalk from "chalk";
|
|
569
593
|
|
|
@@ -652,7 +676,8 @@ var MESSAGES = {
|
|
|
652
676
|
...INIT_MESSAGES,
|
|
653
677
|
...COVER_VALIDATE_MESSAGES,
|
|
654
678
|
...HOME_MESSAGES,
|
|
655
|
-
...TASKS_CMD_MESSAGES
|
|
679
|
+
...TASKS_CMD_MESSAGES,
|
|
680
|
+
...POST_CREATE_MESSAGES
|
|
656
681
|
};
|
|
657
682
|
|
|
658
683
|
// src/constants/exitCodes.ts
|
|
@@ -670,7 +695,7 @@ var VALID_TERMINAL_APPS = ["auto", "iterm2", "terminal"];
|
|
|
670
695
|
var ITERM2_APP_PATH = "/Applications/iTerm.app";
|
|
671
696
|
|
|
672
697
|
// src/constants/config.ts
|
|
673
|
-
var APPEND_SYSTEM_PROMPT = "Currently, you are in the git worktree directory.
|
|
698
|
+
var APPEND_SYSTEM_PROMPT = "Currently, you are in the git worktree directory.";
|
|
674
699
|
var CONFIG_DEFINITIONS = {
|
|
675
700
|
autoDeleteBranch: {
|
|
676
701
|
defaultValue: false,
|
|
@@ -743,6 +768,10 @@ var PROJECT_CONFIG_DEFINITIONS = {
|
|
|
743
768
|
validateRunCommand: {
|
|
744
769
|
defaultValue: void 0,
|
|
745
770
|
description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
|
|
771
|
+
},
|
|
772
|
+
postCreate: {
|
|
773
|
+
defaultValue: void 0,
|
|
774
|
+
description: "worktree \u521B\u5EFA\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF0C\u7528\u4E8E\u5B89\u88C5\u4F9D\u8D56\u7B49\u521D\u59CB\u5316\u64CD\u4F5C"
|
|
746
775
|
}
|
|
747
776
|
};
|
|
748
777
|
function deriveDefaultConfig2(definitions) {
|
|
@@ -762,6 +791,7 @@ var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFIN
|
|
|
762
791
|
|
|
763
792
|
// src/constants/git.ts
|
|
764
793
|
var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
|
|
794
|
+
var EXEC_MAX_BUFFER = 200 * 1024 * 1024;
|
|
765
795
|
|
|
766
796
|
// src/constants/logger.ts
|
|
767
797
|
var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
|
|
@@ -967,7 +997,8 @@ function execCommand(command, options) {
|
|
|
967
997
|
const result = execSync(command, {
|
|
968
998
|
cwd: options?.cwd,
|
|
969
999
|
encoding: "utf-8",
|
|
970
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1000
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1001
|
+
maxBuffer: EXEC_MAX_BUFFER
|
|
971
1002
|
});
|
|
972
1003
|
return result.trim();
|
|
973
1004
|
}
|
|
@@ -992,7 +1023,8 @@ function execCommandWithInput(command, args, options) {
|
|
|
992
1023
|
cwd: options.cwd,
|
|
993
1024
|
input: options.input,
|
|
994
1025
|
encoding: "utf-8",
|
|
995
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1026
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1027
|
+
maxBuffer: EXEC_MAX_BUFFER
|
|
996
1028
|
});
|
|
997
1029
|
return result.trim();
|
|
998
1030
|
}
|
|
@@ -1184,7 +1216,8 @@ function gitDiffBinaryAgainstBranch(branchName, cwd) {
|
|
|
1184
1216
|
logger.debug(`\u6267\u884C\u547D\u4EE4: git diff HEAD...${branchName} --binary${cwd ? ` (cwd: ${cwd})` : ""}`);
|
|
1185
1217
|
return execSync2(`git diff HEAD...${branchName} --binary`, {
|
|
1186
1218
|
cwd,
|
|
1187
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1219
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1220
|
+
maxBuffer: EXEC_MAX_BUFFER
|
|
1188
1221
|
});
|
|
1189
1222
|
}
|
|
1190
1223
|
function gitApplyFromStdin(patchContent, cwd) {
|
|
@@ -1221,7 +1254,8 @@ function gitDiffTree(baseTreeHash, targetTreeHash, cwd) {
|
|
|
1221
1254
|
logger.debug(`\u6267\u884C\u547D\u4EE4: git diff-tree -p --binary ${baseTreeHash} ${targetTreeHash}${cwd ? ` (cwd: ${cwd})` : ""}`);
|
|
1222
1255
|
return execSync2(`git diff-tree -p --binary ${baseTreeHash} ${targetTreeHash}`, {
|
|
1223
1256
|
cwd,
|
|
1224
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1257
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1258
|
+
maxBuffer: EXEC_MAX_BUFFER
|
|
1225
1259
|
});
|
|
1226
1260
|
}
|
|
1227
1261
|
function gitApplyCachedCheck(patchContent, cwd) {
|
|
@@ -2015,8 +2049,9 @@ function escapeShellSingleQuote(str) {
|
|
|
2015
2049
|
}
|
|
2016
2050
|
function buildClaudeCommand(worktree, hasPreviousSession) {
|
|
2017
2051
|
const commandStr = getConfigValue("claudeCodeCommand");
|
|
2052
|
+
const systemPrompt = APPEND_SYSTEM_PROMPT;
|
|
2018
2053
|
const escapedPath = escapeShellSingleQuote(worktree.path);
|
|
2019
|
-
const escapedPrompt = escapeShellSingleQuote(
|
|
2054
|
+
const escapedPrompt = escapeShellSingleQuote(systemPrompt);
|
|
2020
2055
|
const continueFlag = hasPreviousSession ? " --continue" : "";
|
|
2021
2056
|
return `cd '${escapedPath}' && ${commandStr} --append-system-prompt '${escapedPrompt}'${continueFlag}`;
|
|
2022
2057
|
}
|
|
@@ -2836,7 +2871,8 @@ function parseStreamEvent(event) {
|
|
|
2836
2871
|
|
|
2837
2872
|
// src/utils/task-executor.ts
|
|
2838
2873
|
function executeClaudeTask(worktree, task, onActivity, continueSession) {
|
|
2839
|
-
const
|
|
2874
|
+
const systemPrompt = APPEND_SYSTEM_PROMPT;
|
|
2875
|
+
const args = ["-p", task, "--output-format", "stream-json", "--verbose", "--permission-mode", "bypassPermissions", "--append-system-prompt", systemPrompt];
|
|
2840
2876
|
if (continueSession) {
|
|
2841
2877
|
args.push("--continue");
|
|
2842
2878
|
}
|
|
@@ -4321,6 +4357,117 @@ async function handleMergeConflict(currentBranch, incomingBranch, cwd, autoFlag)
|
|
|
4321
4357
|
return resolveConflictsWithAI(currentBranch, incomingBranch, cwd);
|
|
4322
4358
|
}
|
|
4323
4359
|
|
|
4360
|
+
// src/hooks/post-create.ts
|
|
4361
|
+
import { existsSync as existsSync10, accessSync, chmodSync, constants as fsConstants } from "fs";
|
|
4362
|
+
import { spawn as spawn2 } from "child_process";
|
|
4363
|
+
import { join as join8 } from "path";
|
|
4364
|
+
var POST_CREATE_SCRIPT_RELATIVE_PATH = ".clawt/postCreate.sh";
|
|
4365
|
+
function isExecutable(filePath) {
|
|
4366
|
+
try {
|
|
4367
|
+
accessSync(filePath, fsConstants.X_OK);
|
|
4368
|
+
return true;
|
|
4369
|
+
} catch {
|
|
4370
|
+
return false;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
function autoFixExecutablePermission(filePath) {
|
|
4374
|
+
try {
|
|
4375
|
+
chmodSync(filePath, 493);
|
|
4376
|
+
printInfo(MESSAGES.POST_CREATE_SCRIPT_AUTO_CHMOD(filePath));
|
|
4377
|
+
} catch {
|
|
4378
|
+
printWarning(MESSAGES.POST_CREATE_SCRIPT_NOT_EXECUTABLE(filePath));
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
function normalizeCommand(value) {
|
|
4382
|
+
if (!value) return null;
|
|
4383
|
+
const trimmed = value.trim();
|
|
4384
|
+
return trimmed || null;
|
|
4385
|
+
}
|
|
4386
|
+
function resolvePostCreateHook() {
|
|
4387
|
+
const config2 = loadProjectConfig();
|
|
4388
|
+
if (config2?.postCreate) {
|
|
4389
|
+
const command = normalizeCommand(config2.postCreate);
|
|
4390
|
+
if (command) {
|
|
4391
|
+
return { command, source: "projectConfig" };
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
const mainWorktreePath = getMainWorktreePath();
|
|
4395
|
+
const scriptPath = join8(mainWorktreePath, POST_CREATE_SCRIPT_RELATIVE_PATH);
|
|
4396
|
+
if (existsSync10(scriptPath)) {
|
|
4397
|
+
if (!isExecutable(scriptPath)) {
|
|
4398
|
+
autoFixExecutablePermission(scriptPath);
|
|
4399
|
+
}
|
|
4400
|
+
return { command: scriptPath, source: "postCreateScript" };
|
|
4401
|
+
}
|
|
4402
|
+
return null;
|
|
4403
|
+
}
|
|
4404
|
+
function getSourceLabel(hook) {
|
|
4405
|
+
return hook.source === "projectConfig" ? "\u9879\u76EE\u914D\u7F6E (postCreate)" : ".clawt/postCreate.sh";
|
|
4406
|
+
}
|
|
4407
|
+
function executeOneHook(worktree, hook) {
|
|
4408
|
+
return new Promise((resolve4) => {
|
|
4409
|
+
const result = {
|
|
4410
|
+
worktreePath: worktree.path,
|
|
4411
|
+
branch: worktree.branch,
|
|
4412
|
+
success: true,
|
|
4413
|
+
source: hook.source
|
|
4414
|
+
};
|
|
4415
|
+
try {
|
|
4416
|
+
const child = spawn2(hook.command, {
|
|
4417
|
+
cwd: worktree.path,
|
|
4418
|
+
stdio: "ignore",
|
|
4419
|
+
shell: true
|
|
4420
|
+
});
|
|
4421
|
+
child.on("error", (err) => {
|
|
4422
|
+
result.success = false;
|
|
4423
|
+
result.error = err.message;
|
|
4424
|
+
logger.error(`postCreate hook \u5F02\u5E38: ${hook.command} @ ${worktree.path}: ${result.error}`);
|
|
4425
|
+
resolve4(result);
|
|
4426
|
+
});
|
|
4427
|
+
child.on("close", (code) => {
|
|
4428
|
+
if (code !== null && code !== 0) {
|
|
4429
|
+
result.success = false;
|
|
4430
|
+
result.error = `\u547D\u4EE4\u9000\u51FA\u7801: ${code}`;
|
|
4431
|
+
logger.error(`postCreate hook \u5931\u8D25: ${hook.command} (\u9000\u51FA\u7801: ${code}) @ ${worktree.path}`);
|
|
4432
|
+
} else {
|
|
4433
|
+
logger.info(`postCreate hook \u6210\u529F: ${hook.command} @ ${worktree.path}`);
|
|
4434
|
+
}
|
|
4435
|
+
resolve4(result);
|
|
4436
|
+
});
|
|
4437
|
+
} catch (err) {
|
|
4438
|
+
result.success = false;
|
|
4439
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
4440
|
+
logger.error(`postCreate hook \u542F\u52A8\u5931\u8D25: ${hook.command} @ ${worktree.path}: ${result.error}`);
|
|
4441
|
+
resolve4(result);
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
function executePostCreateHooks(worktrees, hook) {
|
|
4446
|
+
const promises = worktrees.map((worktree) => executeOneHook(worktree, hook));
|
|
4447
|
+
return Promise.all(promises);
|
|
4448
|
+
}
|
|
4449
|
+
function runPostCreateHooks(worktrees, skip) {
|
|
4450
|
+
if (skip) {
|
|
4451
|
+
printInfo(MESSAGES.HOOK_SKIPPED);
|
|
4452
|
+
return;
|
|
4453
|
+
}
|
|
4454
|
+
const hook = resolvePostCreateHook();
|
|
4455
|
+
if (!hook) {
|
|
4456
|
+
printInfo(MESSAGES.HOOK_NOT_CONFIGURED);
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
printInfo(MESSAGES.HOOK_SOURCE_INFO(getSourceLabel(hook)));
|
|
4460
|
+
printInfo("");
|
|
4461
|
+
printInfo(MESSAGES.HOOK_BACKGROUND_START(worktrees.length, hook.command));
|
|
4462
|
+
executePostCreateHooks(worktrees, hook).then((results) => {
|
|
4463
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
4464
|
+
const failed = results.filter((r) => !r.success).length;
|
|
4465
|
+
logger.info(`postCreate hook \u6C47\u603B: ${succeeded} \u6210\u529F, ${failed} \u5931\u8D25`);
|
|
4466
|
+
}).catch((err) => {
|
|
4467
|
+
logger.error(`postCreate hook \u6267\u884C\u5F02\u5E38: ${err instanceof Error ? err.message : String(err)}`);
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4324
4471
|
// src/commands/list.ts
|
|
4325
4472
|
import chalk10 from "chalk";
|
|
4326
4473
|
function registerListCommand(program2) {
|
|
@@ -4374,7 +4521,7 @@ function printListAsText(projectName, worktrees) {
|
|
|
4374
4521
|
|
|
4375
4522
|
// src/commands/create.ts
|
|
4376
4523
|
function registerCreateCommand(program2) {
|
|
4377
|
-
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F\uFF08\u542B\u9A8C\u8BC1\u5206\u652F\uFF09").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action(async (options) => {
|
|
4524
|
+
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F\uFF08\u542B\u9A8C\u8BC1\u5206\u652F\uFF09").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").option("--post-create", "\u6267\u884C postCreate hook\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF0C--no-post-create \u8DF3\u8FC7\uFF09", true).action(async (options) => {
|
|
4378
4525
|
await handleCreate(options);
|
|
4379
4526
|
});
|
|
4380
4527
|
}
|
|
@@ -4389,6 +4536,7 @@ async function handleCreate(options) {
|
|
|
4389
4536
|
}
|
|
4390
4537
|
logger.info(`create \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u6570\u91CF: ${count}`);
|
|
4391
4538
|
const worktrees = createWorktrees(options.branch, count);
|
|
4539
|
+
runPostCreateHooks(worktrees, !options.postCreate);
|
|
4392
4540
|
printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
|
|
4393
4541
|
printInfo("");
|
|
4394
4542
|
worktrees.forEach((wt, index) => {
|
|
@@ -4482,7 +4630,7 @@ async function handleRemove(options) {
|
|
|
4482
4630
|
|
|
4483
4631
|
// src/commands/run.ts
|
|
4484
4632
|
function registerRunCommand(program2) {
|
|
4485
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree + \u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1\uFF08\u652F\u6301\u4EFB\u52A1\u6587\u4EF6\uFF09").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
|
|
4633
|
+
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree + \u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1\uFF08\u652F\u6301\u4EFB\u52A1\u6587\u4EF6\uFF09").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").option("--post-create", "\u6267\u884C postCreate hook\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF0C--no-post-create \u8DF3\u8FC7\uFF09", true).action(async (options) => {
|
|
4486
4634
|
await handleRun(options);
|
|
4487
4635
|
});
|
|
4488
4636
|
}
|
|
@@ -4505,6 +4653,7 @@ async function handleRunFromFile(options) {
|
|
|
4505
4653
|
const branches = entries.map((e) => sanitizeBranchName(e.branch));
|
|
4506
4654
|
worktrees = createWorktreesByBranches(branches);
|
|
4507
4655
|
}
|
|
4656
|
+
runPostCreateHooks(worktrees, !options.postCreate);
|
|
4508
4657
|
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
4509
4658
|
logger.info(`run \u547D\u4EE4\uFF08\u6587\u4EF6\u6A21\u5F0F\uFF09\u6267\u884C\uFF0C\u4EFB\u52A1\u6570: ${entries.length}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
4510
4659
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
@@ -4556,6 +4705,7 @@ async function handleRun(options) {
|
|
|
4556
4705
|
}
|
|
4557
4706
|
const worktrees2 = createWorktrees(options.branch, 1);
|
|
4558
4707
|
const worktree = worktrees2[0];
|
|
4708
|
+
runPostCreateHooks(worktrees2, !options.postCreate);
|
|
4559
4709
|
printSuccess(MESSAGES.WORKTREE_CREATED(1));
|
|
4560
4710
|
launchInteractiveClaude(worktree);
|
|
4561
4711
|
return;
|
|
@@ -4565,6 +4715,7 @@ async function handleRun(options) {
|
|
|
4565
4715
|
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
4566
4716
|
logger.info(`run \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u4EFB\u52A1\u6570: ${count}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
4567
4717
|
const worktrees = createWorktrees(options.branch, count);
|
|
4718
|
+
runPostCreateHooks(worktrees, !options.postCreate);
|
|
4568
4719
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
4569
4720
|
}
|
|
4570
4721
|
|
|
@@ -5455,8 +5606,8 @@ function registerAliasCommand(program2) {
|
|
|
5455
5606
|
}
|
|
5456
5607
|
|
|
5457
5608
|
// src/commands/projects.ts
|
|
5458
|
-
import { existsSync as
|
|
5459
|
-
import { join as
|
|
5609
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
5610
|
+
import { join as join9 } from "path";
|
|
5460
5611
|
import chalk13 from "chalk";
|
|
5461
5612
|
function registerProjectsCommand(program2) {
|
|
5462
5613
|
program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
|
|
@@ -5480,8 +5631,8 @@ function handleProjectsOverview(json) {
|
|
|
5480
5631
|
printProjectsOverviewAsText(result);
|
|
5481
5632
|
}
|
|
5482
5633
|
function handleProjectDetail(name, json) {
|
|
5483
|
-
const projectDir =
|
|
5484
|
-
if (!
|
|
5634
|
+
const projectDir = join9(WORKTREES_DIR, name);
|
|
5635
|
+
if (!existsSync11(projectDir)) {
|
|
5485
5636
|
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
5486
5637
|
process.exit(1);
|
|
5487
5638
|
}
|
|
@@ -5494,7 +5645,7 @@ function handleProjectDetail(name, json) {
|
|
|
5494
5645
|
printProjectDetailAsText(result);
|
|
5495
5646
|
}
|
|
5496
5647
|
function collectProjectsOverview() {
|
|
5497
|
-
if (!
|
|
5648
|
+
if (!existsSync11(WORKTREES_DIR)) {
|
|
5498
5649
|
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
5499
5650
|
}
|
|
5500
5651
|
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
@@ -5503,7 +5654,7 @@ function collectProjectsOverview() {
|
|
|
5503
5654
|
if (!entry.isDirectory()) {
|
|
5504
5655
|
continue;
|
|
5505
5656
|
}
|
|
5506
|
-
const projectDir =
|
|
5657
|
+
const projectDir = join9(WORKTREES_DIR, entry.name);
|
|
5507
5658
|
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
5508
5659
|
projects.push(overview);
|
|
5509
5660
|
}
|
|
@@ -5520,7 +5671,7 @@ function collectSingleProjectOverview(name, projectDir) {
|
|
|
5520
5671
|
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
5521
5672
|
const worktreeCount = worktreeDirs.length;
|
|
5522
5673
|
const diskUsage = calculateDirSize(projectDir);
|
|
5523
|
-
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) =>
|
|
5674
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join9(projectDir, e.name)));
|
|
5524
5675
|
return {
|
|
5525
5676
|
name,
|
|
5526
5677
|
worktreeCount,
|
|
@@ -5535,7 +5686,7 @@ function collectProjectDetail(name, projectDir) {
|
|
|
5535
5686
|
if (!entry.isDirectory()) {
|
|
5536
5687
|
continue;
|
|
5537
5688
|
}
|
|
5538
|
-
const wtPath =
|
|
5689
|
+
const wtPath = join9(projectDir, entry.name);
|
|
5539
5690
|
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
5540
5691
|
worktrees.push(detail);
|
|
5541
5692
|
}
|
|
@@ -5634,7 +5785,7 @@ function printWorktreeDetailItem(wt) {
|
|
|
5634
5785
|
}
|
|
5635
5786
|
|
|
5636
5787
|
// src/commands/completion.ts
|
|
5637
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as
|
|
5788
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync13 } from "fs";
|
|
5638
5789
|
import { resolve as resolve2 } from "path";
|
|
5639
5790
|
import { homedir as homedir2 } from "os";
|
|
5640
5791
|
|
|
@@ -5685,14 +5836,14 @@ compdef _clawt_completion clawt
|
|
|
5685
5836
|
}
|
|
5686
5837
|
|
|
5687
5838
|
// src/utils/completion-engine.ts
|
|
5688
|
-
import { existsSync as
|
|
5689
|
-
import { join as
|
|
5839
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
5840
|
+
import { join as join10, dirname, basename as basename2 } from "path";
|
|
5690
5841
|
function completeFilePath(partial) {
|
|
5691
5842
|
const cwd = process.cwd();
|
|
5692
5843
|
const hasDir = partial.includes("/");
|
|
5693
|
-
const searchDir = hasDir ?
|
|
5844
|
+
const searchDir = hasDir ? join10(cwd, dirname(partial)) : cwd;
|
|
5694
5845
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
5695
|
-
if (!
|
|
5846
|
+
if (!existsSync12(searchDir)) {
|
|
5696
5847
|
return [];
|
|
5697
5848
|
}
|
|
5698
5849
|
const entries = readdirSync6(searchDir);
|
|
@@ -5701,7 +5852,7 @@ function completeFilePath(partial) {
|
|
|
5701
5852
|
for (const entry of entries) {
|
|
5702
5853
|
if (!entry.startsWith(prefix)) continue;
|
|
5703
5854
|
if (entry.startsWith(".")) continue;
|
|
5704
|
-
const fullPath =
|
|
5855
|
+
const fullPath = join10(searchDir, entry);
|
|
5705
5856
|
try {
|
|
5706
5857
|
const stat = statSync5(fullPath);
|
|
5707
5858
|
if (stat.isDirectory()) {
|
|
@@ -5790,7 +5941,7 @@ function generateCompletions(program2, args) {
|
|
|
5790
5941
|
|
|
5791
5942
|
// src/commands/completion.ts
|
|
5792
5943
|
function appendToFile(filePath, content) {
|
|
5793
|
-
if (
|
|
5944
|
+
if (existsSync13(filePath)) {
|
|
5794
5945
|
const current = readFileSync6(filePath, "utf-8");
|
|
5795
5946
|
if (current.includes("clawt completion")) {
|
|
5796
5947
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
@@ -5925,19 +6076,19 @@ async function handleHome() {
|
|
|
5925
6076
|
}
|
|
5926
6077
|
|
|
5927
6078
|
// src/commands/tasks.ts
|
|
5928
|
-
import { resolve as resolve3, dirname as dirname2, join as
|
|
5929
|
-
import { existsSync as
|
|
6079
|
+
import { resolve as resolve3, dirname as dirname2, join as join11 } from "path";
|
|
6080
|
+
import { existsSync as existsSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
5930
6081
|
function registerTasksCommand(program2) {
|
|
5931
6082
|
const taskCmd = program2.command("tasks").description("\u4EFB\u52A1\u6587\u4EF6\u7BA1\u7406");
|
|
5932
6083
|
taskCmd.command("init").description("\u751F\u6210\u4EFB\u52A1\u6A21\u677F\u6587\u4EF6").argument("[path]", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").action(async (path2) => {
|
|
5933
|
-
const filePath = path2 ??
|
|
6084
|
+
const filePath = path2 ?? join11(TASK_TEMPLATE_OUTPUT_DIR, generateTaskFilename(TASK_TEMPLATE_FILENAME_PREFIX));
|
|
5934
6085
|
await handleTasksInit(filePath);
|
|
5935
6086
|
});
|
|
5936
6087
|
}
|
|
5937
6088
|
async function handleTasksInit(filePath) {
|
|
5938
6089
|
const absolutePath = resolve3(filePath);
|
|
5939
6090
|
logger.info(`tasks init \u547D\u4EE4\u6267\u884C\uFF0C\u76EE\u6807\u6587\u4EF6: ${absolutePath}`);
|
|
5940
|
-
if (
|
|
6091
|
+
if (existsSync14(absolutePath)) {
|
|
5941
6092
|
throw new ClawtError(MESSAGES.TASK_INIT_FILE_EXISTS(filePath));
|
|
5942
6093
|
}
|
|
5943
6094
|
ensureDir(dirname2(absolutePath));
|
package/dist/postinstall.js
CHANGED
|
@@ -541,6 +541,30 @@ var TASKS_CMD_MESSAGES = {
|
|
|
541
541
|
TASK_INIT_HINT: (path) => `\u4F7F\u7528 clawt run -f ${path} \u6267\u884C\u4EFB\u52A1`
|
|
542
542
|
};
|
|
543
543
|
|
|
544
|
+
// src/constants/messages/post-create.ts
|
|
545
|
+
var POST_CREATE_MESSAGES = {
|
|
546
|
+
/** hook 执行跳过(--no-post-create) */
|
|
547
|
+
HOOK_SKIPPED: "\u5DF2\u8DF3\u8FC7 postCreate hook\uFF08--no-post-create\uFF09",
|
|
548
|
+
/** 无 hook 配置 */
|
|
549
|
+
HOOK_NOT_CONFIGURED: "\u672A\u914D\u7F6E postCreate hook\uFF0C\u8DF3\u8FC7",
|
|
550
|
+
/** hook 来源提示 */
|
|
551
|
+
HOOK_SOURCE_INFO: (source) => `postCreate hook \u6765\u6E90: ${source}`,
|
|
552
|
+
/** hook 开始执行 */
|
|
553
|
+
HOOK_EXECUTING: (branch, command) => `[${branch}] \u6B63\u5728\u6267\u884C postCreate hook: ${command}`,
|
|
554
|
+
/** hook 执行成功 */
|
|
555
|
+
HOOK_SUCCESS: (branch) => `[${branch}] postCreate hook \u6267\u884C\u6210\u529F`,
|
|
556
|
+
/** hook 执行失败 */
|
|
557
|
+
HOOK_FAILED: (branch, error) => `[${branch}] postCreate hook \u6267\u884C\u5931\u8D25: ${error}`,
|
|
558
|
+
/** hook 执行汇总 */
|
|
559
|
+
HOOK_SUMMARY: (succeeded, failed) => `postCreate hook \u6267\u884C\u5B8C\u6210: ${succeeded} \u6210\u529F, ${failed} \u5931\u8D25`,
|
|
560
|
+
/** hook 后台执行中提示 */
|
|
561
|
+
HOOK_BACKGROUND_START: (count, command) => `postCreate hook \u6B63\u5728\u540E\u53F0\u6267\u884C (${count} \u4E2A worktree): ${command}`,
|
|
562
|
+
/** postCreate.sh 自动添加执行权限 */
|
|
563
|
+
POST_CREATE_SCRIPT_AUTO_CHMOD: (path) => `${path} \u4E0D\u53EF\u6267\u884C\uFF0C\u5DF2\u81EA\u52A8\u6DFB\u52A0\u6267\u884C\u6743\u9650`,
|
|
564
|
+
/** postCreate.sh 不可执行(自动 chmod 失败时降级提示) */
|
|
565
|
+
POST_CREATE_SCRIPT_NOT_EXECUTABLE: (path) => `\u68C0\u6D4B\u5230 ${path} \u4F46\u4E0D\u53EF\u6267\u884C\uFF0C\u81EA\u52A8\u6DFB\u52A0\u6743\u9650\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C chmod +x ${path}`
|
|
566
|
+
};
|
|
567
|
+
|
|
544
568
|
// src/constants/messages/interactive-panel.ts
|
|
545
569
|
import chalk from "chalk";
|
|
546
570
|
|
|
@@ -600,7 +624,8 @@ var MESSAGES = {
|
|
|
600
624
|
...INIT_MESSAGES,
|
|
601
625
|
...COVER_VALIDATE_MESSAGES,
|
|
602
626
|
...HOME_MESSAGES,
|
|
603
|
-
...TASKS_CMD_MESSAGES
|
|
627
|
+
...TASKS_CMD_MESSAGES,
|
|
628
|
+
...POST_CREATE_MESSAGES
|
|
604
629
|
};
|
|
605
630
|
|
|
606
631
|
// src/constants/terminal.ts
|
|
@@ -679,6 +704,10 @@ var PROJECT_CONFIG_DEFINITIONS = {
|
|
|
679
704
|
validateRunCommand: {
|
|
680
705
|
defaultValue: void 0,
|
|
681
706
|
description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
|
|
707
|
+
},
|
|
708
|
+
postCreate: {
|
|
709
|
+
defaultValue: void 0,
|
|
710
|
+
description: "worktree \u521B\u5EFA\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF0C\u7528\u4E8E\u5B89\u88C5\u4F9D\u8D56\u7B49\u521D\u59CB\u5316\u64CD\u4F5C"
|
|
682
711
|
}
|
|
683
712
|
};
|
|
684
713
|
function deriveDefaultConfig2(definitions) {
|
|
@@ -696,6 +725,9 @@ function deriveConfigDescriptions2(definitions) {
|
|
|
696
725
|
var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
|
|
697
726
|
var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
|
|
698
727
|
|
|
728
|
+
// src/constants/git.ts
|
|
729
|
+
var EXEC_MAX_BUFFER = 200 * 1024 * 1024;
|
|
730
|
+
|
|
699
731
|
// src/constants/update.ts
|
|
700
732
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
701
733
|
|
package/docs/alias.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
### 5.15 命令别名管理
|
|
2
|
+
|
|
3
|
+
**命令:**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 列出所有命令别名
|
|
7
|
+
clawt alias
|
|
8
|
+
clawt alias list
|
|
9
|
+
|
|
10
|
+
# 设置命令别名
|
|
11
|
+
clawt alias set <alias> <command>
|
|
12
|
+
|
|
13
|
+
# 移除命令别名
|
|
14
|
+
clawt alias remove <alias>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**子命令:**
|
|
18
|
+
|
|
19
|
+
| 子命令 | 说明 |
|
|
20
|
+
| ------ | ---- |
|
|
21
|
+
| `clawt alias` / `clawt alias list` | 列出所有已配置的命令别名 |
|
|
22
|
+
| `clawt alias set <alias> <command>` | 设置命令别名,将 `<alias>` 映射到 `<command>` |
|
|
23
|
+
| `clawt alias remove <alias>` | 移除指定的命令别名 |
|
|
24
|
+
|
|
25
|
+
**参数:**
|
|
26
|
+
|
|
27
|
+
| 参数 | 必填 | 说明 |
|
|
28
|
+
| ---- | ---- | ---- |
|
|
29
|
+
| `<alias>` | 是(set / remove) | 别名名称 |
|
|
30
|
+
| `<command>` | 是(set) | 目标内置命令名 |
|
|
31
|
+
|
|
32
|
+
**约束规则:**
|
|
33
|
+
|
|
34
|
+
1. **别名不能覆盖内置命令名**:别名不能与任何已注册的内置命令同名(动态检测,当前包括 `list`、`create`、`remove`、`run`、`resume`、`validate`、`cover`、`merge`、`config`、`sync`、`reset`、`status`、`alias`、`projects`、`completion`、`init`、`home`、`tasks`)。如果用户尝试设置与内置命令同名的别名,输出错误提示并返回
|
|
35
|
+
2. **目标必须是内置命令**:别名的目标(`<command>`)必须是已注册的内置命令名。如果指定了不存在的目标命令,输出错误提示并返回
|
|
36
|
+
3. **参数透传**:通过别名调用时,所有选项和参数会完全透传给目标命令,行为与直接调用目标命令完全一致
|
|
37
|
+
|
|
38
|
+
**持久化:**
|
|
39
|
+
|
|
40
|
+
别名配置存储在 `~/.clawt/config.json` 的 `aliases` 字段中(类型 `Record<string, string>`,默认 `{}`)。
|
|
41
|
+
|
|
42
|
+
**运行流程:**
|
|
43
|
+
|
|
44
|
+
#### `alias list`(默认)
|
|
45
|
+
|
|
46
|
+
1. 读取配置文件中的 `aliases` 字段
|
|
47
|
+
2. 如果没有配置任何别名,输出提示 `(无别名)`
|
|
48
|
+
3. 如果有别名,逐行输出所有别名映射
|
|
49
|
+
|
|
50
|
+
**输出格式:**
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
当前别名列表:
|
|
54
|
+
────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
l → list
|
|
57
|
+
r → run
|
|
58
|
+
v → validate
|
|
59
|
+
|
|
60
|
+
────────────────────────────────────────
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `alias set <alias> <command>`
|
|
64
|
+
|
|
65
|
+
1. **校验别名不与内置命令冲突**:检查 `<alias>` 是否为内置命令名,是则输出错误提示并返回
|
|
66
|
+
2. **校验目标命令存在**:检查 `<command>` 是否为已注册的内置命令名,不是则输出错误提示并返回
|
|
67
|
+
3. 将别名写入配置文件的 `aliases` 字段(如果别名已存在,覆盖旧值)
|
|
68
|
+
4. 输出成功提示
|
|
69
|
+
|
|
70
|
+
**输出格式:**
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
✓ 已设置别名: l → list
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### `alias remove <alias>`
|
|
77
|
+
|
|
78
|
+
1. 读取配置文件中的 `aliases` 字段
|
|
79
|
+
2. 检查指定的别名是否存在,不存在则输出错误提示并返回
|
|
80
|
+
3. 从 `aliases` 中删除该别名并写入配置文件
|
|
81
|
+
4. 输出成功提示
|
|
82
|
+
|
|
83
|
+
**输出格式:**
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
✓ 已移除别名: l
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**别名使用示例:**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 设置别名
|
|
93
|
+
clawt alias set l list
|
|
94
|
+
clawt alias set r run
|
|
95
|
+
clawt alias set v validate
|
|
96
|
+
|
|
97
|
+
# 使用别名(等同于对应的完整命令)
|
|
98
|
+
clawt l # 等同于 clawt list
|
|
99
|
+
clawt r task.md # 等同于 clawt run task.md
|
|
100
|
+
|
|
101
|
+
# 查看所有别名
|
|
102
|
+
clawt alias list
|
|
103
|
+
|
|
104
|
+
# 移除别名
|
|
105
|
+
clawt alias remove l
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**实现要点:**
|
|
109
|
+
|
|
110
|
+
- 消息常量定义在 `src/constants/messages/alias.ts`(`ALIAS_MESSAGES`),包括列表为空提示、设置/移除成功、别名不存在、与内置命令冲突、目标命令不存在等消息
|
|
111
|
+
- 别名应用逻辑位于 `src/utils/alias.ts` 的 `applyAliases` 函数:在主入口 `src/index.ts` 中,所有命令注册完成后调用,遍历配置中的 `aliases` 映射,通过 Commander.js 的 `.alias()` 方法为对应命令注册别名。如果目标命令不存在则跳过并输出 warn 级别日志
|
|
112
|
+
- 内置命令冲突检测通过 `getRegisteredCommandNames(program)` 动态获取所有已注册命令名,而非硬编码命令列表
|
|
113
|
+
|
|
114
|
+
---
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
### 5.16 `clawt completion` 命令
|
|
2
|
+
|
|
3
|
+
为终端环境(bash/zsh)生成并安装 `clawt` 的命令、选项及参数的自动补全脚本。
|
|
4
|
+
|
|
5
|
+
#### 语法
|
|
6
|
+
```bash
|
|
7
|
+
clawt completion bash
|
|
8
|
+
clawt completion zsh
|
|
9
|
+
clawt completion install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
#### 子命令说明
|
|
13
|
+
|
|
14
|
+
| 子命令 | 说明 |
|
|
15
|
+
| --------- | ----------------------------------------------------------------------------------- |
|
|
16
|
+
| `bash` | 输出适用于 bash 的补全脚本(用户可重定向到 `~/.bashrc`) |
|
|
17
|
+
| `zsh` | 输出适用于 zsh 的补全脚本(用户可重定向到 `~/.zshrc`) |
|
|
18
|
+
| `install` | 自动检测当前 shell 类型,将补全脚本追加到对应的配置文件中 |
|
|
19
|
+
|
|
20
|
+
#### `install` 子命令流程
|
|
21
|
+
|
|
22
|
+
1. 通过 `process.env.SHELL` 检测当前 shell 类型
|
|
23
|
+
2. 根据 shell 类型确定目标配置文件:
|
|
24
|
+
- zsh → `~/.zshrc`(追加 `source <(clawt completion zsh)`)
|
|
25
|
+
- bash → `~/.bashrc`(追加 `eval "$(clawt completion bash)"`)
|
|
26
|
+
3. 检查目标文件中是否已包含 `clawt completion`,已存在则跳过并提示
|
|
27
|
+
4. 追加成功后提示用户重启终端或 source 配置文件
|
|
28
|
+
5. 未知 shell 类型时输出警告,提示手动配置
|
|
29
|
+
|
|
30
|
+
#### 动态补全特性
|
|
31
|
+
|
|
32
|
+
补全脚本通过内部子命令 `_complete` 实现动态补全,不对外公开。补全引擎基于 Commander.js 的命令树结构遍历,支持以下场景:
|
|
33
|
+
|
|
34
|
+
| 场景 | 补全行为 |
|
|
35
|
+
| ---------------------------- | ---------------------------------------------------------- |
|
|
36
|
+
| `-b` / `--branch` 参数之后 | 动态列出当前项目所有 worktree 分支名(通过 `getProjectWorktrees`) |
|
|
37
|
+
| `-f` / `--file` 参数之后 | 动态列出匹配的文件和子目录(不限制文件类型,支持子目录递归浏览) |
|
|
38
|
+
| `config set` / `config get` 之后 | 动态列出所有配置项键名(从 `CONFIG_DEFINITIONS` 获取) |
|
|
39
|
+
| 输入以 `-` 开头 | 列出当前命令层级的可用选项(short/long) |
|
|
40
|
+
| 其他情况 | 列出当前命令层级的可用子命令及别名;若输入为空,同时列出可用选项 |
|
|
41
|
+
|
|
42
|
+
**文件路径补全细节:**
|
|
43
|
+
- 支持子目录递归浏览(如 `tasks/` 后继续 Tab 可深入子目录)
|
|
44
|
+
- 目录候选项以 `/` 结尾,补全时不自动追加空格
|
|
45
|
+
- 不限制文件类型,列出所有非隐藏文件
|
|
46
|
+
- 跳过隐藏文件和目录(以 `.` 开头)
|
|
47
|
+
|
|
48
|
+
#### 实现说明
|
|
49
|
+
|
|
50
|
+
- 补全命令注册函数:`registerCompletionCommand()`(在 `src/commands/completion.ts`)
|
|
51
|
+
- 消息常量:`COMPLETION_MESSAGES`(在 `src/constants/messages/completion.ts`)
|
|
52
|
+
- 核心函数:`generateCompletions()` 解析当前输入上下文并输出候选项,`completeFilePath()` 处理文件路径补全,`tryCompleteSpecialArg()` 处理特殊参数(分支名、文件路径、配置键)的动态补全,`completeFromCommandTree()` 基于命令树遍历生成子命令和选项候选项
|
|
53
|
+
- shell 脚本生成:`getBashScript()`、`getZshScript()` 分别生成对应 shell 的补全脚本
|
|
54
|
+
|
|
55
|
+
---
|