Skip to content

Commit 79d8bc0

Browse files
王璨claude
andcommitted
fix: scope persisted cwd to current project
Only reuse a saved cwd when the current startup path matches the recorded project, and document the project-scoped cwd behavior in the config docs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7ad145d commit 79d8bc0

6 files changed

Lines changed: 87 additions & 16 deletions

File tree

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ node dist/dscode.mjs
7676
</tr>
7777
</table>
7878

79-
> 欢迎大家在 GitHub 给项目点个 Star、Watch,顺手点赞支持
79+
> 如果你喜欢这个项目,欢迎在 GitHub 点个 ⭐ Star、👀 Watch、👍 点赞支持!你的鼓励是我持续打磨 dscode 的动力,也非常感谢每一条反馈和建议
8080
8181
### 首次启动
8282

@@ -86,6 +86,7 @@ node dist/dscode.mjs
8686
```
8787

8888
`/config` 命令写入的配置会持久化到 `~/.dscode/config.json`,后续启动无需重复设置。
89+
其中 `cwd` 会记录本次启动对应的项目目录;只有在你手动执行 `/config cwd <path>` 时,才会对当前项目复用该目录。
8990
声明式配置单独放在 `settings.json`:用户级 `~/.dscode/settings.json`,项目级 `<project>/.dscode/settings.json`
9091

9192
## What you get
@@ -125,7 +126,7 @@ dscode 通过 **MCP (Model Context Protocol)** 连接外部工具,把 DeepSeek
125126
/config model <id> # 切换模型(即时生效)
126127
/config thinking <level> # 设置思考强度
127128
/config key <api-key> # 设置 API Key
128-
/config cwd <path> # 设置工作目录(重启生效)
129+
/config cwd <path> # 为当前项目设置工作目录(重启生效)
129130
/config help # 查看帮助
130131
```
131132

@@ -257,7 +258,7 @@ Always push the branch before creating a PR.
257258

258259
推荐把配置分成两类:
259260

260-
- `/config` 命令写入的用户命令配置:`~/.dscode/config.json`
261+
- `/config` 命令写入的用户命令配置:`~/.dscode/config.json`(包含当前项目的 cwd 记录)
261262
- 声明式 settings:
262263
- 用户级:`~/.dscode/settings.json`
263264
- 项目级:`<project>/.dscode/settings.json`
@@ -270,7 +271,8 @@ Always push the branch before creating a PR.
270271
{
271272
"modelId": "deepseek-v4-flash",
272273
"thinkingLevel": "high",
273-
"cwd": "/absolute/path/to/project"
274+
"cwd": "/absolute/path/to/project/subdir",
275+
"cwdProjectPath": "/absolute/path/to/project"
274276
}
275277
```
276278

docs/harness.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ function loadConfig(): HarnessConfig {
235235
}
236236
```
237237

238-
`/config` 命令写入的字段(如 `modelId` / `thinkingLevel` / `cwd` / `apiKey`)来自用户级 `~/.dscode/config.json`
238+
`/config` 命令写入的字段(如 `modelId` / `thinkingLevel` / `cwd` / `cwdProjectPath` / `apiKey`)来自用户级 `~/.dscode/config.json`;其中 `cwd` 只会在启动项目与 `cwdProjectPath` 匹配时复用
239239
`permissions` / `mcp` / `skills` 等声明式配置来自 `settings.json`,其中项目级 `settings.json` 覆盖用户级 `settings.json`
240240

241241
## 模型切换

src/core/config.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ export function saveUserConfig(partial: Record<string, unknown>): void {
6060
saveJsonSafe(path, { ...existing, ...partial });
6161
}
6262

63+
export function saveUserProjectCwd(startupPath: string, cwd: string): void {
64+
saveUserConfig({
65+
cwd: resolve(cwd),
66+
cwdProjectPath: resolve(startupPath),
67+
});
68+
}
69+
6370
export function loadConfig(): HarnessConfig {
6471
const startupPath = resolve(process.env.DSCODE_PROJECT_PATH ?? process.cwd());
6572
const configDir = dsConfigHome();
@@ -70,13 +77,21 @@ export function loadConfig(): HarnessConfig {
7077

7178
let projectPath = startupPath;
7279
const configuredCwd = userConfig.cwd;
73-
if (typeof configuredCwd === "string" && configuredCwd.trim() !== "") {
80+
const configuredCwdProjectPath = userConfig.cwdProjectPath;
81+
if (
82+
typeof configuredCwd === "string" &&
83+
configuredCwd.trim() !== "" &&
84+
typeof configuredCwdProjectPath === "string" &&
85+
resolve(configuredCwdProjectPath) === startupPath
86+
) {
7487
const nextProjectPath = resolve(configuredCwd);
7588
if (existsSync(nextProjectPath)) {
7689
projectPath = nextProjectPath;
7790
}
7891
}
7992

93+
saveUserProjectCwd(startupPath, projectPath);
94+
8095
if (projectPath !== process.cwd()) {
8196
process.chdir(projectPath);
8297
}
@@ -148,6 +163,7 @@ export function loadConfig(): HarnessConfig {
148163
apiKey,
149164
thinkingLevel,
150165
maxTokens,
166+
startupPath,
151167
projectPath,
152168
configDir,
153169
dataDir,

src/core/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface HarnessConfig {
1212
apiKey?: string;
1313
thinkingLevel: ThinkingLevel;
1414
maxTokens: number;
15+
startupPath: string;
1516
projectPath: string;
1617
configDir: string;
1718
dataDir: string;

src/ui/commands.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { PermissionManager } from "../permissions/manager.js";
1313
import type { ContextManager } from "../context/manager.js";
1414
import type { MCPManager } from "../mcp/manager.js";
1515
import type { TuiApp } from "./tui-app.js";
16-
import { saveUserConfig, maskApiKey } from "../core/config.js";
16+
import { saveUserConfig, saveUserProjectCwd, maskApiKey } from "../core/config.js";
1717
import { readImageFile, readClipboardImage } from "../utils/image.js";
1818

1919
interface CommandContext {
@@ -253,7 +253,7 @@ const COMMANDS: SlashCommandDef[] = [
253253
"/config model <id> Switch model (saved to ~/.dscode/config.json)",
254254
"/config thinking <level> Set thinking level (saved to ~/.dscode/config.json)",
255255
"/config key <api-key> Set your DeepSeek API key",
256-
"/config cwd <path> Set working directory (saved to ~/.dscode/config.json)",
256+
"/config cwd <path> Set working directory for this project (saved to ~/.dscode/config.json)",
257257
"/config help Show this help",
258258
].join("\n"));
259259
break;
@@ -275,8 +275,8 @@ const COMMANDS: SlashCommandDef[] = [
275275
return;
276276
}
277277
const resolvedCwd = resolve(cwd);
278-
saveUserConfig({ cwd: resolvedCwd });
279-
ctx.tui.addInfo(`CWD set to: ${resolvedCwd} (saved to ~/.dscode/config.json, restart required)`);
278+
saveUserProjectCwd(ctx.config.startupPath, resolvedCwd);
279+
ctx.tui.addInfo(`CWD set to: ${resolvedCwd} (saved to ~/.dscode/config.json for this project, restart required)`);
280280
break;
281281
}
282282
case "key": {

tests/core/config.test.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,29 @@ describe("config and settings loading", () => {
2121
else process.env.DSCODE_DATA_HOME = originalDataHome;
2222
});
2323

24-
it("switches projectPath using user config cwd on restart", () => {
24+
it("defaults to startup path and records it in user config", () => {
25+
const root = mkdtempSync(join(tmpdir(), "dscode-config-"));
26+
const configHome = join(root, "home");
27+
const workspace = join(root, "workspace");
28+
29+
mkdirSync(configHome, { recursive: true });
30+
mkdirSync(workspace, { recursive: true });
31+
32+
process.env.DSCODE_PROJECT_PATH = workspace;
33+
process.env.DSCODE_CONFIG_HOME = configHome;
34+
process.env.DSCODE_DATA_HOME = configHome;
35+
36+
const config = loadConfig();
37+
const saved = JSON.parse(readFileSync(join(configHome, "config.json"), "utf8"));
38+
39+
expect(realpathSync(config.projectPath)).toBe(realpathSync(workspace));
40+
expect(realpathSync(process.cwd())).toBe(realpathSync(workspace));
41+
expect(saved).toMatchObject({ cwd: workspace, cwdProjectPath: workspace });
42+
43+
rmSync(root, { recursive: true, force: true });
44+
});
45+
46+
it("switches projectPath using persisted cwd for the same startup project", () => {
2547
const root = mkdtempSync(join(tmpdir(), "dscode-config-"));
2648
const configHome = join(root, "home");
2749
const workspace = join(root, "workspace");
@@ -31,7 +53,7 @@ describe("config and settings loading", () => {
3153
mkdirSync(target, { recursive: true });
3254
writeFileSync(
3355
join(configHome, "config.json"),
34-
JSON.stringify({ cwd: target, modelId: "deepseek-v4-pro" }) + "\n",
56+
JSON.stringify({ cwd: target, cwdProjectPath: workspace, modelId: "deepseek-v4-pro" }) + "\n",
3557
"utf8",
3658
);
3759

@@ -48,7 +70,36 @@ describe("config and settings loading", () => {
4870
rmSync(root, { recursive: true, force: true });
4971
});
5072

51-
it("keeps startup path when user config cwd does not exist", () => {
73+
it("keeps startup path when persisted cwd belongs to another startup project", () => {
74+
const root = mkdtempSync(join(tmpdir(), "dscode-config-"));
75+
const configHome = join(root, "home");
76+
const workspace = join(root, "workspace");
77+
const otherWorkspace = join(root, "other-workspace");
78+
const target = join(workspace, "nested-project");
79+
80+
mkdirSync(configHome, { recursive: true });
81+
mkdirSync(workspace, { recursive: true });
82+
mkdirSync(otherWorkspace, { recursive: true });
83+
mkdirSync(target, { recursive: true });
84+
writeFileSync(
85+
join(configHome, "config.json"),
86+
JSON.stringify({ cwd: target, cwdProjectPath: workspace }) + "\n",
87+
"utf8",
88+
);
89+
90+
process.env.DSCODE_PROJECT_PATH = otherWorkspace;
91+
process.env.DSCODE_CONFIG_HOME = configHome;
92+
process.env.DSCODE_DATA_HOME = configHome;
93+
94+
const config = loadConfig();
95+
96+
expect(realpathSync(config.projectPath)).toBe(realpathSync(otherWorkspace));
97+
expect(realpathSync(process.cwd())).toBe(realpathSync(otherWorkspace));
98+
99+
rmSync(root, { recursive: true, force: true });
100+
});
101+
102+
it("keeps startup path when persisted cwd does not exist", () => {
52103
const root = mkdtempSync(join(tmpdir(), "dscode-config-"));
53104
const configHome = join(root, "home");
54105
const workspace = join(root, "workspace");
@@ -57,7 +108,7 @@ describe("config and settings loading", () => {
57108
mkdirSync(workspace, { recursive: true });
58109
writeFileSync(
59110
join(configHome, "config.json"),
60-
JSON.stringify({ cwd: join(workspace, "missing") }) + "\n",
111+
JSON.stringify({ cwd: join(workspace, "missing"), cwdProjectPath: workspace }) + "\n",
61112
"utf8",
62113
);
63114

@@ -115,10 +166,11 @@ describe("config and settings loading", () => {
115166
process.env.DSCODE_CONFIG_HOME = configHome;
116167
process.env.DSCODE_DATA_HOME = configHome;
117168

118-
saveUserConfig({ modelId: "deepseek-v4-pro", cwd: "/tmp/project" });
169+
saveUserConfig({ modelId: "deepseek-v4-pro" });
119170

120171
const saved = JSON.parse(readFileSync(join(configHome, "config.json"), "utf8"));
121-
expect(saved).toMatchObject({ modelId: "deepseek-v4-pro", cwd: "/tmp/project" });
172+
expect(saved).toMatchObject({ modelId: "deepseek-v4-pro" });
173+
expect(saved.cwd).toBeUndefined();
122174

123175
rmSync(root, { recursive: true, force: true });
124176
});

0 commit comments

Comments
 (0)