clawt 3.9.6 → 3.9.8
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/README.md +2 -2
- package/dist/index.js +275 -160
- package/dist/postinstall.js +8 -2
- package/docs/config-file.md +1 -1
- package/docs/init.md +3 -2
- package/docs/project-config.md +11 -2
- package/docs/tasks.md +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +3 -1
- package/src/commands/status.ts +73 -41
- package/src/constants/messages/tasks.ts +3 -2
- package/src/constants/project-config.ts +4 -0
- package/src/constants/tasks-template.ts +1 -1
- package/src/types/projectConfig.ts +2 -0
- package/src/utils/claude.ts +3 -3
- package/src/utils/git-branch.ts +46 -1
- package/src/utils/git-core.ts +22 -1
- package/src/utils/index.ts +6 -2
- package/src/utils/interactive-panel-render.ts +4 -2
- package/src/utils/interactive-panel-state.ts +36 -2
- package/src/utils/interactive-panel.ts +63 -27
- package/src/utils/project-config.ts +37 -2
- package/src/utils/shell.ts +25 -1
- package/tests/unit/commands/init.test.ts +1 -0
- package/tests/unit/commands/status.test.ts +18 -25
- package/tests/unit/utils/project-config.test.ts +50 -0
package/dist/index.js
CHANGED
|
@@ -574,8 +574,10 @@ var TASKS_CMD_MESSAGES = {
|
|
|
574
574
|
TASK_INIT_FILE_EXISTS: (path2) => `\u6587\u4EF6\u5DF2\u5B58\u5728: ${path2}\uFF0C\u5982\u9700\u8986\u76D6\u8BF7\u5148\u5220\u9664`,
|
|
575
575
|
/** 任务模板生成成功 */
|
|
576
576
|
TASK_INIT_SUCCESS: (path2) => `\u2713 \u4EFB\u52A1\u6A21\u677F\u5DF2\u751F\u6210: ${path2}`,
|
|
577
|
-
/**
|
|
578
|
-
TASK_INIT_HINT: (path2) => `\
|
|
577
|
+
/** 任务模板使用提示(分行列出 run 和 resume 两种用法) */
|
|
578
|
+
TASK_INIT_HINT: (path2) => `\u6267\u884C\u4EFB\u52A1:
|
|
579
|
+
clawt run -f ${path2} # \u521B\u5EFA worktree \u5E76\u6267\u884C\uFF08\u5206\u652F\u540D\u9700\u4E0D\u5B58\u5728\uFF09
|
|
580
|
+
clawt resume -f ${path2} # \u5728\u5DF2\u6709 worktree \u4E2D\u8FFD\u95EE\uFF08\u5206\u652F\u540D\u9700\u5DF2\u5B58\u5728\uFF09`
|
|
579
581
|
};
|
|
580
582
|
|
|
581
583
|
// src/constants/messages/post-create.ts
|
|
@@ -780,6 +782,10 @@ var PROJECT_CONFIG_DEFINITIONS = {
|
|
|
780
782
|
postCreate: {
|
|
781
783
|
defaultValue: void 0,
|
|
782
784
|
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"
|
|
785
|
+
},
|
|
786
|
+
claudeCodeCommand: {
|
|
787
|
+
defaultValue: void 0,
|
|
788
|
+
description: "Claude Code CLI \u542F\u52A8\u6307\u4EE4\uFF08\u672A\u8BBE\u7F6E\u65F6\u56DE\u9000\u5230\u5168\u5C40\u914D\u7F6E\uFF09"
|
|
783
789
|
}
|
|
784
790
|
};
|
|
785
791
|
function deriveDefaultConfig2(definitions) {
|
|
@@ -912,7 +918,7 @@ var TASK_TEMPLATE_CONTENT = `# Clawt \u4EFB\u52A1\u6587\u4EF6
|
|
|
912
918
|
# \u683C\u5F0F\u8BF4\u660E: \u6807\u7B7E\u5916\u7684\u6587\u672C\u4F1A\u88AB\u5FFD\u7565\uFF0C\u6BCF\u4E2A\u4EFB\u52A1\u7528 START/END \u6807\u7B7E\u5305\u88F9
|
|
913
919
|
#
|
|
914
920
|
# \u89C4\u5219:
|
|
915
|
-
# 1. \u6BCF\u4E2A\u4EFB\u52A1\u5757\u7528
|
|
921
|
+
# 1. \u6BCF\u4E2A\u4EFB\u52A1\u5757\u7528 <START> \u548C <END> \u6807\u7B7E\u5305\u88F9\uFF08\u5B9E\u9645\u6807\u7B7E\u89C1\u4E0B\u65B9\u793A\u4F8B\uFF09
|
|
916
922
|
# 2. \u5757\u5185 # branch: <\u5206\u652F\u540D> \u58F0\u660E\u5206\u652F\u540D\uFF08\u4F7F\u7528 -b \u53C2\u6570\u65F6\u53EF\u7701\u7565\uFF09
|
|
917
923
|
# 3. \u5757\u5185\u5176\u4F59\u884C\u4E3A\u4EFB\u52A1\u63CF\u8FF0\uFF08\u652F\u6301\u591A\u884C\uFF09
|
|
918
924
|
|
|
@@ -1001,7 +1007,8 @@ function enableConsoleTransport() {
|
|
|
1001
1007
|
}
|
|
1002
1008
|
|
|
1003
1009
|
// src/utils/shell.ts
|
|
1004
|
-
import { execSync as execSync2, execFileSync, spawn, spawnSync } from "child_process";
|
|
1010
|
+
import { exec, execSync as execSync2, execFileSync, spawn, spawnSync } from "child_process";
|
|
1011
|
+
import { promisify } from "util";
|
|
1005
1012
|
|
|
1006
1013
|
// src/utils/git-lock.ts
|
|
1007
1014
|
import { join as join2, isAbsolute } from "path";
|
|
@@ -1067,6 +1074,7 @@ function waitForGitIndexLockRetrySync() {
|
|
|
1067
1074
|
}
|
|
1068
1075
|
|
|
1069
1076
|
// src/utils/shell.ts
|
|
1077
|
+
var execPromise = promisify(exec);
|
|
1070
1078
|
function getEnvWithoutNestedSessionFlag() {
|
|
1071
1079
|
const { CLAUDECODE: _, ...env } = process.env;
|
|
1072
1080
|
return { ...env, CLAUDE_CODE_ENTRYPOINT: CLAUDE_CODE_ENTRYPOINT_VALUE };
|
|
@@ -1095,6 +1103,15 @@ function execCommand(command, options) {
|
|
|
1095
1103
|
}
|
|
1096
1104
|
}
|
|
1097
1105
|
}
|
|
1106
|
+
async function execCommandAsync(command, options) {
|
|
1107
|
+
logger.debug(`\u6267\u884C\u5F02\u6B65\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
1108
|
+
const { stdout } = await execPromise(command, {
|
|
1109
|
+
cwd: options?.cwd,
|
|
1110
|
+
encoding: "utf-8",
|
|
1111
|
+
maxBuffer: EXEC_MAX_BUFFER
|
|
1112
|
+
});
|
|
1113
|
+
return stdout.trim();
|
|
1114
|
+
}
|
|
1098
1115
|
function spawnProcess(command, args, options) {
|
|
1099
1116
|
logger.debug(`\u542F\u52A8\u5B50\u8FDB\u7A0B: ${command} ${args.join(" ")}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
1100
1117
|
return spawn(command, args, {
|
|
@@ -1262,6 +1279,9 @@ function getProjectName(cwd) {
|
|
|
1262
1279
|
function getStatusPorcelain(cwd) {
|
|
1263
1280
|
return execCommand("git status --porcelain", { cwd });
|
|
1264
1281
|
}
|
|
1282
|
+
async function getStatusPorcelainAsync(cwd) {
|
|
1283
|
+
return execCommandAsync("git status --porcelain", { cwd });
|
|
1284
|
+
}
|
|
1265
1285
|
function isWorkingDirClean(cwd) {
|
|
1266
1286
|
return getStatusPorcelain(cwd) === "";
|
|
1267
1287
|
}
|
|
@@ -1316,6 +1336,10 @@ function getDiffStat(worktreePath) {
|
|
|
1316
1336
|
const output = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
|
|
1317
1337
|
return parseShortStat(output);
|
|
1318
1338
|
}
|
|
1339
|
+
async function getDiffStatAsync(worktreePath) {
|
|
1340
|
+
const output = await execCommandAsync("git diff --shortstat HEAD", { cwd: worktreePath });
|
|
1341
|
+
return parseShortStat(output);
|
|
1342
|
+
}
|
|
1319
1343
|
function gitApplyCachedFromStdin(patchContent, cwd) {
|
|
1320
1344
|
execCommandWithInput("git", ["apply", "--cached"], { input: patchContent, cwd });
|
|
1321
1345
|
}
|
|
@@ -1423,12 +1447,16 @@ function getCommitCountAhead(branchName, cwd) {
|
|
|
1423
1447
|
const output = execCommand(`git rev-list --count HEAD..${branchName}`, { cwd });
|
|
1424
1448
|
return parseInt(output, 10) || 0;
|
|
1425
1449
|
}
|
|
1426
|
-
function
|
|
1450
|
+
function parseDivergenceOutput(output) {
|
|
1451
|
+
const [leftStr, rightStr] = output.trim().split(/\s+/);
|
|
1452
|
+
return { ahead: parseInt(rightStr, 10) || 0, behind: parseInt(leftStr, 10) || 0 };
|
|
1453
|
+
}
|
|
1454
|
+
async function getCommitDivergenceAsync(branchName, cwd) {
|
|
1427
1455
|
try {
|
|
1428
|
-
const output =
|
|
1429
|
-
return
|
|
1456
|
+
const output = await execCommandAsync(`git rev-list --left-right --count HEAD...${branchName}`, { cwd });
|
|
1457
|
+
return parseDivergenceOutput(output);
|
|
1430
1458
|
} catch {
|
|
1431
|
-
return 0;
|
|
1459
|
+
return { ahead: 0, behind: 0 };
|
|
1432
1460
|
}
|
|
1433
1461
|
}
|
|
1434
1462
|
function getCurrentBranch(cwd) {
|
|
@@ -1638,7 +1666,7 @@ function validateBranchesNotExist(branchNames) {
|
|
|
1638
1666
|
}
|
|
1639
1667
|
|
|
1640
1668
|
// src/utils/project-config.ts
|
|
1641
|
-
import { existsSync as
|
|
1669
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1642
1670
|
import { join as join4 } from "path";
|
|
1643
1671
|
|
|
1644
1672
|
// src/utils/fs.ts
|
|
@@ -1680,6 +1708,101 @@ function calculateDirSize(dirPath) {
|
|
|
1680
1708
|
return totalSize;
|
|
1681
1709
|
}
|
|
1682
1710
|
|
|
1711
|
+
// src/utils/config.ts
|
|
1712
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1713
|
+
function loadConfig() {
|
|
1714
|
+
if (!existsSync3(CONFIG_PATH)) {
|
|
1715
|
+
return { ...DEFAULT_CONFIG };
|
|
1716
|
+
}
|
|
1717
|
+
try {
|
|
1718
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
1719
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1720
|
+
} catch {
|
|
1721
|
+
logger.warn(MESSAGES.CONFIG_CORRUPTED);
|
|
1722
|
+
writeDefaultConfig();
|
|
1723
|
+
return { ...DEFAULT_CONFIG };
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
function writeConfig(config2) {
|
|
1727
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1728
|
+
}
|
|
1729
|
+
function writeDefaultConfig() {
|
|
1730
|
+
writeConfig(DEFAULT_CONFIG);
|
|
1731
|
+
}
|
|
1732
|
+
function saveConfig(config2) {
|
|
1733
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1734
|
+
}
|
|
1735
|
+
function getConfigValue(key) {
|
|
1736
|
+
const config2 = loadConfig();
|
|
1737
|
+
return config2[key];
|
|
1738
|
+
}
|
|
1739
|
+
function ensureClawtDirs() {
|
|
1740
|
+
ensureDir(CLAWT_HOME);
|
|
1741
|
+
ensureDir(LOGS_DIR);
|
|
1742
|
+
ensureDir(WORKTREES_DIR);
|
|
1743
|
+
ensureDir(PROJECTS_CONFIG_DIR);
|
|
1744
|
+
}
|
|
1745
|
+
function parseConcurrency(optionValue, configValue) {
|
|
1746
|
+
if (optionValue === void 0) {
|
|
1747
|
+
return configValue;
|
|
1748
|
+
}
|
|
1749
|
+
const parsed = parseInt(optionValue, 10);
|
|
1750
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1751
|
+
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
1752
|
+
}
|
|
1753
|
+
return parsed;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/utils/json.ts
|
|
1757
|
+
function primitiveToString(value) {
|
|
1758
|
+
if (value === void 0) {
|
|
1759
|
+
return "undefined";
|
|
1760
|
+
}
|
|
1761
|
+
if (value === null) {
|
|
1762
|
+
return "null";
|
|
1763
|
+
}
|
|
1764
|
+
if (typeof value === "symbol") {
|
|
1765
|
+
return value.toString();
|
|
1766
|
+
}
|
|
1767
|
+
if (typeof value === "function") {
|
|
1768
|
+
return `[Function: ${value.name || "anonymous"}]`;
|
|
1769
|
+
}
|
|
1770
|
+
return String(value);
|
|
1771
|
+
}
|
|
1772
|
+
function safeStringify(value, indent = 2) {
|
|
1773
|
+
if (value === null || typeof value !== "object") {
|
|
1774
|
+
return primitiveToString(value);
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1778
|
+
return JSON.stringify(
|
|
1779
|
+
value,
|
|
1780
|
+
(_key, val) => {
|
|
1781
|
+
if (typeof val === "bigint") {
|
|
1782
|
+
return val.toString();
|
|
1783
|
+
}
|
|
1784
|
+
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
1785
|
+
return primitiveToString(val);
|
|
1786
|
+
}
|
|
1787
|
+
if (typeof val === "object" && val !== null) {
|
|
1788
|
+
if (seen.has(val)) {
|
|
1789
|
+
return "[Circular]";
|
|
1790
|
+
}
|
|
1791
|
+
seen.add(val);
|
|
1792
|
+
}
|
|
1793
|
+
return val;
|
|
1794
|
+
},
|
|
1795
|
+
indent
|
|
1796
|
+
);
|
|
1797
|
+
} catch {
|
|
1798
|
+
try {
|
|
1799
|
+
return JSON.stringify(String(value), null, indent);
|
|
1800
|
+
} catch {
|
|
1801
|
+
return "[Unserializable]";
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1683
1806
|
// src/utils/project-config.ts
|
|
1684
1807
|
function getProjectConfigPath(projectName) {
|
|
1685
1808
|
return join4(PROJECTS_CONFIG_DIR, projectName, "config.json");
|
|
@@ -1687,11 +1810,11 @@ function getProjectConfigPath(projectName) {
|
|
|
1687
1810
|
function loadProjectConfig() {
|
|
1688
1811
|
const projectName = getProjectName();
|
|
1689
1812
|
const configPath = getProjectConfigPath(projectName);
|
|
1690
|
-
if (!
|
|
1813
|
+
if (!existsSync4(configPath)) {
|
|
1691
1814
|
return null;
|
|
1692
1815
|
}
|
|
1693
1816
|
try {
|
|
1694
|
-
const raw =
|
|
1817
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
1695
1818
|
return JSON.parse(raw);
|
|
1696
1819
|
} catch (error) {
|
|
1697
1820
|
logger.warn(`\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`);
|
|
@@ -1703,7 +1826,7 @@ function saveProjectConfig(config2) {
|
|
|
1703
1826
|
const configPath = getProjectConfigPath(projectName);
|
|
1704
1827
|
const projectDir = join4(PROJECTS_CONFIG_DIR, projectName);
|
|
1705
1828
|
ensureDir(projectDir);
|
|
1706
|
-
|
|
1829
|
+
writeFileSync2(configPath, safeStringify({ ...config2 }, 2), "utf-8");
|
|
1707
1830
|
logger.info(`\u9879\u76EE\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${configPath}`);
|
|
1708
1831
|
}
|
|
1709
1832
|
function requireProjectConfig() {
|
|
@@ -1731,6 +1854,24 @@ function getValidateRunCommand() {
|
|
|
1731
1854
|
const config2 = loadProjectConfig();
|
|
1732
1855
|
return config2?.validateRunCommand || void 0;
|
|
1733
1856
|
}
|
|
1857
|
+
function resolveClaudeCodeCommand() {
|
|
1858
|
+
const projectConfig = loadProjectConfig();
|
|
1859
|
+
if (projectConfig?.claudeCodeCommand) {
|
|
1860
|
+
return projectConfig.claudeCodeCommand;
|
|
1861
|
+
}
|
|
1862
|
+
return getConfigValue("claudeCodeCommand");
|
|
1863
|
+
}
|
|
1864
|
+
function normalizeProjectConfig(config2, key, value) {
|
|
1865
|
+
if (value === "") {
|
|
1866
|
+
const def = PROJECT_CONFIG_DEFINITIONS[key];
|
|
1867
|
+
if (def?.defaultValue === void 0) {
|
|
1868
|
+
const normalized = { ...config2 };
|
|
1869
|
+
delete normalized[key];
|
|
1870
|
+
return normalized;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return config2;
|
|
1874
|
+
}
|
|
1734
1875
|
|
|
1735
1876
|
// src/utils/validate-branch.ts
|
|
1736
1877
|
import Enquirer from "enquirer";
|
|
@@ -1897,7 +2038,7 @@ async function runPreChecks(options) {
|
|
|
1897
2038
|
|
|
1898
2039
|
// src/utils/worktree.ts
|
|
1899
2040
|
import { join as join5 } from "path";
|
|
1900
|
-
import { existsSync as
|
|
2041
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
1901
2042
|
function getProjectWorktreeDir() {
|
|
1902
2043
|
const projectName = getProjectName();
|
|
1903
2044
|
return join5(WORKTREES_DIR, projectName);
|
|
@@ -1934,7 +2075,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1934
2075
|
}
|
|
1935
2076
|
function getProjectWorktrees() {
|
|
1936
2077
|
const projectDir = getProjectWorktreeDir();
|
|
1937
|
-
if (!
|
|
2078
|
+
if (!existsSync5(projectDir)) {
|
|
1938
2079
|
return [];
|
|
1939
2080
|
}
|
|
1940
2081
|
const worktreeListOutput = gitWorktreeList();
|
|
@@ -1984,51 +2125,6 @@ function getWorktreeStatus(worktree) {
|
|
|
1984
2125
|
}
|
|
1985
2126
|
}
|
|
1986
2127
|
|
|
1987
|
-
// src/utils/config.ts
|
|
1988
|
-
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1989
|
-
function loadConfig() {
|
|
1990
|
-
if (!existsSync5(CONFIG_PATH)) {
|
|
1991
|
-
return { ...DEFAULT_CONFIG };
|
|
1992
|
-
}
|
|
1993
|
-
try {
|
|
1994
|
-
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
1995
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1996
|
-
} catch {
|
|
1997
|
-
logger.warn(MESSAGES.CONFIG_CORRUPTED);
|
|
1998
|
-
writeDefaultConfig();
|
|
1999
|
-
return { ...DEFAULT_CONFIG };
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
function writeConfig(config2) {
|
|
2003
|
-
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
2004
|
-
}
|
|
2005
|
-
function writeDefaultConfig() {
|
|
2006
|
-
writeConfig(DEFAULT_CONFIG);
|
|
2007
|
-
}
|
|
2008
|
-
function saveConfig(config2) {
|
|
2009
|
-
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
2010
|
-
}
|
|
2011
|
-
function getConfigValue(key) {
|
|
2012
|
-
const config2 = loadConfig();
|
|
2013
|
-
return config2[key];
|
|
2014
|
-
}
|
|
2015
|
-
function ensureClawtDirs() {
|
|
2016
|
-
ensureDir(CLAWT_HOME);
|
|
2017
|
-
ensureDir(LOGS_DIR);
|
|
2018
|
-
ensureDir(WORKTREES_DIR);
|
|
2019
|
-
ensureDir(PROJECTS_CONFIG_DIR);
|
|
2020
|
-
}
|
|
2021
|
-
function parseConcurrency(optionValue, configValue) {
|
|
2022
|
-
if (optionValue === void 0) {
|
|
2023
|
-
return configValue;
|
|
2024
|
-
}
|
|
2025
|
-
const parsed = parseInt(optionValue, 10);
|
|
2026
|
-
if (Number.isNaN(parsed) || parsed < 0) {
|
|
2027
|
-
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
2028
|
-
}
|
|
2029
|
-
return parsed;
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
2128
|
// src/utils/prompt.ts
|
|
2033
2129
|
import Enquirer2 from "enquirer";
|
|
2034
2130
|
async function promptCommitMessage(promptMessage, nonInteractiveErrorMessage) {
|
|
@@ -2200,7 +2296,7 @@ function hasClaudeSessionHistory(worktreePath) {
|
|
|
2200
2296
|
return entries.some((entry) => entry.endsWith(".jsonl"));
|
|
2201
2297
|
}
|
|
2202
2298
|
function launchInteractiveClaude(worktree, options = {}) {
|
|
2203
|
-
const commandStr =
|
|
2299
|
+
const commandStr = resolveClaudeCodeCommand();
|
|
2204
2300
|
const parts = commandStr.split(/\s+/).filter(Boolean);
|
|
2205
2301
|
const cmd = parts[0];
|
|
2206
2302
|
const args = [
|
|
@@ -2235,7 +2331,7 @@ function escapeShellSingleQuote(str) {
|
|
|
2235
2331
|
return str.replace(/'/g, "'\\''");
|
|
2236
2332
|
}
|
|
2237
2333
|
function buildClaudeCommand(worktree, hasPreviousSession) {
|
|
2238
|
-
const commandStr =
|
|
2334
|
+
const commandStr = resolveClaudeCodeCommand();
|
|
2239
2335
|
const systemPrompt = APPEND_SYSTEM_PROMPT;
|
|
2240
2336
|
const escapedPath = escapeShellSingleQuote(worktree.path);
|
|
2241
2337
|
const escapedPrompt = escapeShellSingleQuote(systemPrompt);
|
|
@@ -3562,56 +3658,6 @@ async function checkForUpdates(currentVersion) {
|
|
|
3562
3658
|
}
|
|
3563
3659
|
}
|
|
3564
3660
|
|
|
3565
|
-
// src/utils/json.ts
|
|
3566
|
-
function primitiveToString(value) {
|
|
3567
|
-
if (value === void 0) {
|
|
3568
|
-
return "undefined";
|
|
3569
|
-
}
|
|
3570
|
-
if (value === null) {
|
|
3571
|
-
return "null";
|
|
3572
|
-
}
|
|
3573
|
-
if (typeof value === "symbol") {
|
|
3574
|
-
return value.toString();
|
|
3575
|
-
}
|
|
3576
|
-
if (typeof value === "function") {
|
|
3577
|
-
return `[Function: ${value.name || "anonymous"}]`;
|
|
3578
|
-
}
|
|
3579
|
-
return String(value);
|
|
3580
|
-
}
|
|
3581
|
-
function safeStringify(value, indent = 2) {
|
|
3582
|
-
if (value === null || typeof value !== "object") {
|
|
3583
|
-
return primitiveToString(value);
|
|
3584
|
-
}
|
|
3585
|
-
try {
|
|
3586
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
3587
|
-
return JSON.stringify(
|
|
3588
|
-
value,
|
|
3589
|
-
(_key, val) => {
|
|
3590
|
-
if (typeof val === "bigint") {
|
|
3591
|
-
return val.toString();
|
|
3592
|
-
}
|
|
3593
|
-
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
3594
|
-
return primitiveToString(val);
|
|
3595
|
-
}
|
|
3596
|
-
if (typeof val === "object" && val !== null) {
|
|
3597
|
-
if (seen.has(val)) {
|
|
3598
|
-
return "[Circular]";
|
|
3599
|
-
}
|
|
3600
|
-
seen.add(val);
|
|
3601
|
-
}
|
|
3602
|
-
return val;
|
|
3603
|
-
},
|
|
3604
|
-
indent
|
|
3605
|
-
);
|
|
3606
|
-
} catch {
|
|
3607
|
-
try {
|
|
3608
|
-
return JSON.stringify(String(value), null, indent);
|
|
3609
|
-
} catch {
|
|
3610
|
-
return "[Unserializable]";
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
}
|
|
3614
|
-
|
|
3615
3661
|
// src/utils/validate-runner.ts
|
|
3616
3662
|
function handleErrorClipboard(clipboardContent) {
|
|
3617
3663
|
const success = copyToClipboard(clipboardContent);
|
|
@@ -3795,7 +3841,7 @@ function buildSeparatorWithHint(cols, hint) {
|
|
|
3795
3841
|
const rightLen = remaining - leftLen;
|
|
3796
3842
|
return `${chalk9.gray("\u2500".repeat(leftLen))} ${hint} ${chalk9.gray("\u2500".repeat(rightLen))}`;
|
|
3797
3843
|
}
|
|
3798
|
-
function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown) {
|
|
3844
|
+
function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown, cachedPanelLines) {
|
|
3799
3845
|
const lines = [];
|
|
3800
3846
|
lines.push(PANEL_TITLE(statusResult.main.projectName));
|
|
3801
3847
|
lines.push(renderConfiguredBranchLine(statusResult.main));
|
|
@@ -3806,7 +3852,7 @@ function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols,
|
|
|
3806
3852
|
lines.push(PANEL_NO_WORKTREES);
|
|
3807
3853
|
lines.push(buildSeparatorWithHint(cols, ""));
|
|
3808
3854
|
} else {
|
|
3809
|
-
const panelLines = buildGroupedWorktreeLines(statusResult.worktrees, selectedIndex);
|
|
3855
|
+
const panelLines = cachedPanelLines ?? buildGroupedWorktreeLines(statusResult.worktrees, selectedIndex);
|
|
3810
3856
|
const hasOverflowUp = scrollOffset > 0;
|
|
3811
3857
|
const hasOverflowDown = scrollOffset + visibleRows < panelLines.length;
|
|
3812
3858
|
lines.push(buildSeparatorWithHint(cols, hasOverflowUp ? PANEL_OVERFLOW_UP_HINT : ""));
|
|
@@ -4002,8 +4048,11 @@ var PanelStateManager = class {
|
|
|
4002
4048
|
displayOrder = [];
|
|
4003
4049
|
/** 滚动偏移(基于行数) */
|
|
4004
4050
|
scrollOffset = 0;
|
|
4051
|
+
/** 缓存的面板行列表,在 updateData 和导航时更新 */
|
|
4052
|
+
cachedPanelLines = [];
|
|
4005
4053
|
/**
|
|
4006
4054
|
* 更新状态数据
|
|
4055
|
+
* 一次性计算 displayOrder 和 cachedPanelLines,后续 adjustScrollForSelection 和 render 复用缓存
|
|
4007
4056
|
* @param {StatusResult} newStatus - 新的状态数据
|
|
4008
4057
|
* @param {string} [previousBranch] - 刷新前选中的分支名
|
|
4009
4058
|
*/
|
|
@@ -4022,6 +4071,7 @@ var PanelStateManager = class {
|
|
|
4022
4071
|
} else {
|
|
4023
4072
|
this.selectedDisplayIndex = 0;
|
|
4024
4073
|
}
|
|
4074
|
+
this.rebuildCachedPanelLines();
|
|
4025
4075
|
}
|
|
4026
4076
|
/** 获取当前状态数据 */
|
|
4027
4077
|
getStatusResult() {
|
|
@@ -4035,6 +4085,13 @@ var PanelStateManager = class {
|
|
|
4035
4085
|
getScrollOffset() {
|
|
4036
4086
|
return this.scrollOffset;
|
|
4037
4087
|
}
|
|
4088
|
+
/**
|
|
4089
|
+
* 获取缓存的面板行列表
|
|
4090
|
+
* @returns {PanelLine[]} 缓存的面板行列表
|
|
4091
|
+
*/
|
|
4092
|
+
getCachedPanelLines() {
|
|
4093
|
+
return this.cachedPanelLines;
|
|
4094
|
+
}
|
|
4038
4095
|
/**
|
|
4039
4096
|
* 向上导航
|
|
4040
4097
|
* @returns {boolean} 是否发生变化
|
|
@@ -4043,6 +4100,7 @@ var PanelStateManager = class {
|
|
|
4043
4100
|
if (!this.statusResult || this.displayOrder.length === 0) return false;
|
|
4044
4101
|
if (this.selectedDisplayIndex > 0) {
|
|
4045
4102
|
this.selectedDisplayIndex--;
|
|
4103
|
+
this.rebuildCachedPanelLines();
|
|
4046
4104
|
this.adjustScrollForSelection();
|
|
4047
4105
|
return true;
|
|
4048
4106
|
}
|
|
@@ -4056,6 +4114,7 @@ var PanelStateManager = class {
|
|
|
4056
4114
|
if (!this.statusResult || this.displayOrder.length === 0) return false;
|
|
4057
4115
|
if (this.selectedDisplayIndex < this.displayOrder.length - 1) {
|
|
4058
4116
|
this.selectedDisplayIndex++;
|
|
4117
|
+
this.rebuildCachedPanelLines();
|
|
4059
4118
|
this.adjustScrollForSelection();
|
|
4060
4119
|
return true;
|
|
4061
4120
|
}
|
|
@@ -4072,13 +4131,14 @@ var PanelStateManager = class {
|
|
|
4072
4131
|
}
|
|
4073
4132
|
/**
|
|
4074
4133
|
* 调整滚动位置以确保选中项在可见区域内
|
|
4134
|
+
* 复用 cachedPanelLines,不再重新调用 buildGroupedWorktreeLines
|
|
4075
4135
|
*/
|
|
4076
4136
|
adjustScrollForSelection() {
|
|
4077
4137
|
if (!this.statusResult || this.displayOrder.length === 0) return;
|
|
4078
4138
|
const originalIndex = this.getSelectedOriginalIndex();
|
|
4079
4139
|
const rows = process.stdout.rows || 24;
|
|
4080
4140
|
const visibleRows = calculateVisibleRows(rows);
|
|
4081
|
-
const panelLines =
|
|
4141
|
+
const panelLines = this.cachedPanelLines;
|
|
4082
4142
|
let firstLine = -1;
|
|
4083
4143
|
let lastLine = -1;
|
|
4084
4144
|
for (let i = 0; i < panelLines.length; i++) {
|
|
@@ -4102,6 +4162,18 @@ var PanelStateManager = class {
|
|
|
4102
4162
|
this.scrollOffset = groupStart;
|
|
4103
4163
|
}
|
|
4104
4164
|
}
|
|
4165
|
+
/**
|
|
4166
|
+
* 重建缓存的 panelLines
|
|
4167
|
+
* 在数据更新或导航变化时调用
|
|
4168
|
+
*/
|
|
4169
|
+
rebuildCachedPanelLines() {
|
|
4170
|
+
if (!this.statusResult) {
|
|
4171
|
+
this.cachedPanelLines = [];
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
const originalIndex = this.getSelectedOriginalIndex();
|
|
4175
|
+
this.cachedPanelLines = buildGroupedWorktreeLines(this.statusResult.worktrees, originalIndex);
|
|
4176
|
+
}
|
|
4105
4177
|
};
|
|
4106
4178
|
|
|
4107
4179
|
// src/utils/interactive-panel.ts
|
|
@@ -4126,13 +4198,17 @@ var InteractivePanel = class {
|
|
|
4126
4198
|
exitHandler;
|
|
4127
4199
|
/** 操作锁(防止操作期间响应按键) */
|
|
4128
4200
|
isOperating;
|
|
4201
|
+
/** 刷新锁(防止异步刷新期间触发重复刷新) */
|
|
4202
|
+
isRefreshing;
|
|
4129
4203
|
/** Promise resolve 函数(stop 时调用以完成 start 返回的 Promise) */
|
|
4130
4204
|
resolveStart;
|
|
4131
|
-
/**
|
|
4205
|
+
/** 数据收集函数引用(异步,支持并行收集 worktree 数据) */
|
|
4132
4206
|
collectStatusFn;
|
|
4207
|
+
/** 上一帧的总行数,用于 footer-only 渲染时定位最后一行 */
|
|
4208
|
+
lastFrameLineCount = 0;
|
|
4133
4209
|
/**
|
|
4134
4210
|
* 创建交互式面板
|
|
4135
|
-
* @param {() => StatusResult} collectStatusFn -
|
|
4211
|
+
* @param {() => Promise<StatusResult>} collectStatusFn - 异步数据收集函数
|
|
4136
4212
|
*/
|
|
4137
4213
|
constructor(collectStatusFn) {
|
|
4138
4214
|
this.stateManager = new PanelStateManager();
|
|
@@ -4145,6 +4221,7 @@ var InteractivePanel = class {
|
|
|
4145
4221
|
this.resizeHandler = null;
|
|
4146
4222
|
this.exitHandler = null;
|
|
4147
4223
|
this.isOperating = false;
|
|
4224
|
+
this.isRefreshing = false;
|
|
4148
4225
|
this.resolveStart = null;
|
|
4149
4226
|
this.collectStatusFn = collectStatusFn;
|
|
4150
4227
|
}
|
|
@@ -4153,14 +4230,14 @@ var InteractivePanel = class {
|
|
|
4153
4230
|
* 非 TTY 时打印提示并退出
|
|
4154
4231
|
* @returns {Promise<void>} 面板关闭时 resolve
|
|
4155
4232
|
*/
|
|
4156
|
-
start() {
|
|
4233
|
+
async start() {
|
|
4157
4234
|
if (!this.isTTY) {
|
|
4158
4235
|
console.log(PANEL_NOT_TTY);
|
|
4159
|
-
return
|
|
4236
|
+
return;
|
|
4160
4237
|
}
|
|
4238
|
+
this.stateManager.updateData(await this.collectStatusFn());
|
|
4161
4239
|
return new Promise((resolve4) => {
|
|
4162
4240
|
this.resolveStart = resolve4;
|
|
4163
|
-
this.stateManager.updateData(this.collectStatusFn());
|
|
4164
4241
|
this.initTerminal();
|
|
4165
4242
|
this.keyboardController.start();
|
|
4166
4243
|
this.startAutoRefresh();
|
|
@@ -4290,7 +4367,7 @@ var InteractivePanel = class {
|
|
|
4290
4367
|
if (this.refreshCountdown > 0) {
|
|
4291
4368
|
this.refreshCountdown--;
|
|
4292
4369
|
}
|
|
4293
|
-
this.
|
|
4370
|
+
this.renderFooterOnly();
|
|
4294
4371
|
}, PANEL_COUNTDOWN_INTERVAL_MS);
|
|
4295
4372
|
if (this.refreshTimer.unref) this.refreshTimer.unref();
|
|
4296
4373
|
if (this.countdownTimer.unref) this.countdownTimer.unref();
|
|
@@ -4309,19 +4386,25 @@ var InteractivePanel = class {
|
|
|
4309
4386
|
}
|
|
4310
4387
|
}
|
|
4311
4388
|
/**
|
|
4312
|
-
* 刷新数据:记录当前选中分支 →
|
|
4389
|
+
* 刷新数据:记录当前选中分支 → 异步重新收集 → 恢复选中位置 → 重置倒计时 → 重绘
|
|
4390
|
+
* 使用 isRefreshing 锁防止异步刷新期间触发重复刷新
|
|
4313
4391
|
*/
|
|
4314
|
-
refreshData() {
|
|
4315
|
-
if (this.stopped || this.isOperating) return;
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4392
|
+
async refreshData() {
|
|
4393
|
+
if (this.stopped || this.isOperating || this.isRefreshing) return;
|
|
4394
|
+
this.isRefreshing = true;
|
|
4395
|
+
try {
|
|
4396
|
+
const previousBranch = this.stateManager.getSelectedBranch();
|
|
4397
|
+
this.stateManager.updateData(await this.collectStatusFn(), previousBranch || void 0);
|
|
4398
|
+
this.stateManager.adjustScrollForSelection();
|
|
4399
|
+
this.refreshCountdown = PANEL_REFRESH_INTERVAL_MS / 1e3;
|
|
4400
|
+
this.render();
|
|
4401
|
+
} finally {
|
|
4402
|
+
this.isRefreshing = false;
|
|
4403
|
+
}
|
|
4321
4404
|
}
|
|
4322
4405
|
/**
|
|
4323
4406
|
* 渲染一帧面板内容
|
|
4324
|
-
*
|
|
4407
|
+
* 使用同步输出防止闪烁,复用缓存的 panelLines 避免重复 groupWorktreesByDate 计算
|
|
4325
4408
|
*/
|
|
4326
4409
|
render() {
|
|
4327
4410
|
const statusResult = this.stateManager.getStatusResult();
|
|
@@ -4334,7 +4417,8 @@ var InteractivePanel = class {
|
|
|
4334
4417
|
this.stateManager.getScrollOffset(),
|
|
4335
4418
|
rows,
|
|
4336
4419
|
cols,
|
|
4337
|
-
this.refreshCountdown
|
|
4420
|
+
this.refreshCountdown,
|
|
4421
|
+
this.stateManager.getCachedPanelLines()
|
|
4338
4422
|
);
|
|
4339
4423
|
process.stdout.write(SYNC_OUTPUT_START);
|
|
4340
4424
|
process.stdout.write(CLEAR_SCREEN);
|
|
@@ -4343,6 +4427,22 @@ var InteractivePanel = class {
|
|
|
4343
4427
|
const suffix = i < frameLines.length - 1 ? "\n" : "";
|
|
4344
4428
|
process.stdout.write(`${truncateToTerminalWidth(frameLines[i], cols)}${suffix}`);
|
|
4345
4429
|
}
|
|
4430
|
+
this.lastFrameLineCount = frameLines.length;
|
|
4431
|
+
process.stdout.write(SYNC_OUTPUT_END);
|
|
4432
|
+
}
|
|
4433
|
+
/**
|
|
4434
|
+
* 仅更新 footer 行(倒计时文本)
|
|
4435
|
+
* 使用 ANSI 光标定位直接覆写最后一行,避免全量重绘
|
|
4436
|
+
*/
|
|
4437
|
+
renderFooterOnly() {
|
|
4438
|
+
if (this.stopped || this.isOperating || this.lastFrameLineCount === 0) return;
|
|
4439
|
+
const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
4440
|
+
const footerText = renderFooter(this.refreshCountdown);
|
|
4441
|
+
const truncated = truncateToTerminalWidth(footerText, cols);
|
|
4442
|
+
process.stdout.write(SYNC_OUTPUT_START);
|
|
4443
|
+
process.stdout.write(`\x1B[${this.lastFrameLineCount};1H`);
|
|
4444
|
+
process.stdout.write("\x1B[2K");
|
|
4445
|
+
process.stdout.write(truncated);
|
|
4346
4446
|
process.stdout.write(SYNC_OUTPUT_END);
|
|
4347
4447
|
}
|
|
4348
4448
|
/**
|
|
@@ -4363,7 +4463,7 @@ var InteractivePanel = class {
|
|
|
4363
4463
|
this.initTerminal();
|
|
4364
4464
|
this.keyboardController.start();
|
|
4365
4465
|
this.isOperating = false;
|
|
4366
|
-
this.refreshData();
|
|
4466
|
+
await this.refreshData();
|
|
4367
4467
|
this.startAutoRefresh();
|
|
4368
4468
|
this.render();
|
|
4369
4469
|
}
|
|
@@ -5507,7 +5607,7 @@ async function handleStatus(options) {
|
|
|
5507
5607
|
await panel.start();
|
|
5508
5608
|
return;
|
|
5509
5609
|
}
|
|
5510
|
-
const statusResult = collectStatus();
|
|
5610
|
+
const statusResult = await collectStatus();
|
|
5511
5611
|
logger.info(`status \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${statusResult.main.projectName}\uFF0C\u5171 ${statusResult.totalWorktrees} \u4E2A worktree`);
|
|
5512
5612
|
if (options.json) {
|
|
5513
5613
|
printStatusAsJson(statusResult);
|
|
@@ -5515,7 +5615,7 @@ async function handleStatus(options) {
|
|
|
5515
5615
|
}
|
|
5516
5616
|
printStatusAsText(statusResult);
|
|
5517
5617
|
}
|
|
5518
|
-
function collectStatus() {
|
|
5618
|
+
async function collectStatus() {
|
|
5519
5619
|
const projectName = getProjectName();
|
|
5520
5620
|
const currentBranch = getCurrentBranch();
|
|
5521
5621
|
const isClean = isWorkingDirClean();
|
|
@@ -5533,7 +5633,9 @@ function collectStatus() {
|
|
|
5533
5633
|
deletions
|
|
5534
5634
|
};
|
|
5535
5635
|
const worktrees = getProjectWorktrees();
|
|
5536
|
-
const worktreeStatuses =
|
|
5636
|
+
const worktreeStatuses = await Promise.all(
|
|
5637
|
+
worktrees.map((wt) => collectWorktreeDetailedStatusAsync(wt, projectName))
|
|
5638
|
+
);
|
|
5537
5639
|
const snapshots = collectSnapshots(projectName, worktrees);
|
|
5538
5640
|
return {
|
|
5539
5641
|
main: main2,
|
|
@@ -5542,49 +5644,61 @@ function collectStatus() {
|
|
|
5542
5644
|
totalWorktrees: worktrees.length
|
|
5543
5645
|
};
|
|
5544
5646
|
}
|
|
5545
|
-
function
|
|
5546
|
-
const
|
|
5547
|
-
|
|
5548
|
-
|
|
5647
|
+
async function collectWorktreeDetailedStatusAsync(worktree, projectName) {
|
|
5648
|
+
const [divergence, porcelain, diffStat] = await Promise.all([
|
|
5649
|
+
countCommitDivergenceAsync(worktree.branch),
|
|
5650
|
+
detectStatusPorcelainAsync(worktree.path),
|
|
5651
|
+
countDiffStatAsync(worktree.path)
|
|
5652
|
+
]);
|
|
5653
|
+
const changeStatus = detectChangeStatusFromPorcelain(porcelain, divergence.commitsAhead);
|
|
5549
5654
|
const createdAt = getWorktreeCreatedTime(worktree.path);
|
|
5550
5655
|
return {
|
|
5551
5656
|
path: worktree.path,
|
|
5552
5657
|
branch: worktree.branch,
|
|
5553
5658
|
changeStatus,
|
|
5554
|
-
commitsAhead,
|
|
5555
|
-
commitsBehind,
|
|
5659
|
+
commitsAhead: divergence.commitsAhead,
|
|
5660
|
+
commitsBehind: divergence.commitsBehind,
|
|
5556
5661
|
snapshotTime: resolveSnapshotTime(projectName, worktree.branch),
|
|
5557
|
-
insertions,
|
|
5558
|
-
deletions,
|
|
5662
|
+
insertions: diffStat.insertions,
|
|
5663
|
+
deletions: diffStat.deletions,
|
|
5559
5664
|
createdAt
|
|
5560
5665
|
};
|
|
5561
5666
|
}
|
|
5562
|
-
function
|
|
5667
|
+
function detectChangeStatusFromPorcelain(porcelain, commitsAhead) {
|
|
5668
|
+
const hasConflict = porcelain.split("\n").some((line) => /^(UU|AA|DD|DU|UD|AU|UA)/.test(line));
|
|
5669
|
+
if (hasConflict) {
|
|
5670
|
+
return "conflict";
|
|
5671
|
+
}
|
|
5672
|
+
if (porcelain !== "") {
|
|
5673
|
+
return "uncommitted";
|
|
5674
|
+
}
|
|
5675
|
+
if (commitsAhead > 0) {
|
|
5676
|
+
return "committed";
|
|
5677
|
+
}
|
|
5678
|
+
return "clean";
|
|
5679
|
+
}
|
|
5680
|
+
async function detectStatusPorcelainAsync(worktreePath) {
|
|
5563
5681
|
try {
|
|
5564
|
-
|
|
5565
|
-
return "conflict";
|
|
5566
|
-
}
|
|
5567
|
-
if (!isWorkingDirClean(worktree.path)) {
|
|
5568
|
-
return "uncommitted";
|
|
5569
|
-
}
|
|
5570
|
-
if (hasLocalCommits(worktree.branch)) {
|
|
5571
|
-
return "committed";
|
|
5572
|
-
}
|
|
5573
|
-
return "clean";
|
|
5682
|
+
return await getStatusPorcelainAsync(worktreePath);
|
|
5574
5683
|
} catch {
|
|
5575
|
-
return "
|
|
5684
|
+
return "";
|
|
5576
5685
|
}
|
|
5577
5686
|
}
|
|
5578
|
-
function
|
|
5687
|
+
async function countCommitDivergenceAsync(branchName) {
|
|
5579
5688
|
try {
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
commitsBehind: getCommitCountBehind(branchName)
|
|
5583
|
-
};
|
|
5689
|
+
const { ahead, behind } = await getCommitDivergenceAsync(branchName);
|
|
5690
|
+
return { commitsAhead: ahead, commitsBehind: behind };
|
|
5584
5691
|
} catch {
|
|
5585
5692
|
return { commitsAhead: 0, commitsBehind: 0 };
|
|
5586
5693
|
}
|
|
5587
5694
|
}
|
|
5695
|
+
async function countDiffStatAsync(worktreePath) {
|
|
5696
|
+
try {
|
|
5697
|
+
return await getDiffStatAsync(worktreePath);
|
|
5698
|
+
} catch {
|
|
5699
|
+
return { insertions: 0, deletions: 0 };
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5588
5702
|
function countDiffStat(worktreePath) {
|
|
5589
5703
|
try {
|
|
5590
5704
|
return getDiffStat(worktreePath);
|
|
@@ -6189,7 +6303,8 @@ async function handleInitShow(options) {
|
|
|
6189
6303
|
PROJECT_CONFIG_DEFINITIONS,
|
|
6190
6304
|
{ selectPrompt: MESSAGES.INIT_SELECT_PROMPT }
|
|
6191
6305
|
);
|
|
6192
|
-
const
|
|
6306
|
+
const mergedConfig = { ...config2, [key]: newValue };
|
|
6307
|
+
const updatedConfig = normalizeProjectConfig(mergedConfig, key, newValue);
|
|
6193
6308
|
saveProjectConfig(updatedConfig);
|
|
6194
6309
|
printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
|
|
6195
6310
|
}
|