|
| 1 | +## Context |
| 2 | + |
| 3 | +当前权限持久化管线: |
| 4 | + |
| 5 | +``` |
| 6 | +persistRule(rule) |
| 7 | + → loadUserSettings() // ~/.dscode/settings.json |
| 8 | + → saveUserSettings(merged) // ~/.dscode/settings.json |
| 9 | +``` |
| 10 | + |
| 11 | +期望管线: |
| 12 | + |
| 13 | +``` |
| 14 | +persistRule(rule) |
| 15 | + → loadScopedSettings(projectSettingsPath(projectPath)) // $PROJECT/.dscode/settings.json |
| 16 | + → saveJsonSafe(path, merged) // $PROJECT/.dscode/settings.json |
| 17 | +``` |
| 18 | + |
| 19 | +基础设施已就绪:`projectSettingsPath()`、`loadScopedSettings()`、`saveJsonSafe()` 均已存在。仅缺 `saveProjectSettings()` 封装和 `PermissionManager` 拿到 `projectPath`。 |
| 20 | + |
| 21 | +## Goals / Non-Goals |
| 22 | + |
| 23 | +**Goals:** |
| 24 | +- `persistRule` 写入工程级 settings |
| 25 | +- 无 `.dscode/` 目录时自动创建 |
| 26 | +- 与现有加载合并逻辑兼容 |
| 27 | + |
| 28 | +**Non-Goals:** |
| 29 | +- 不迁移已有用户级旧规则 |
| 30 | +- 不改 UI 文案 |
| 31 | +- 不新增用户选项(工程级 vs 用户级选择) |
| 32 | + |
| 33 | +## Decisions |
| 34 | + |
| 35 | +### 1. `PermissionManager` 持有 `projectPath` |
| 36 | + |
| 37 | +**决策**:`PermissionManager` 构造函数新增 `projectPath: string` 参数。 |
| 38 | + |
| 39 | +``` |
| 40 | +class PermissionManager { |
| 41 | + private projectPath: string; |
| 42 | + |
| 43 | + constructor( |
| 44 | + config: PermissionsConfig, |
| 45 | + promptUser: PromptUserFn, |
| 46 | + projectPath: string, // ← 新增 |
| 47 | + onBeforePrompt?: () => void, |
| 48 | + ) { ... } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +**理由**:最简侵入路径。`Harness` 已有 `config.projectPath`,直接透传即可。 |
| 53 | + |
| 54 | +**替代方案**:通过回调注入 `(rule) => void` — 增加间接层,无必要。 |
| 55 | + |
| 56 | +### 2. `saveProjectSettings()` 封装 |
| 57 | + |
| 58 | +**决策**:在 `config.ts` 中新增函数,与 `saveUserSettings()` 对称。 |
| 59 | + |
| 60 | +```typescript |
| 61 | +export function saveProjectSettings( |
| 62 | + projectPath: string, |
| 63 | + partial: Record<string, unknown> |
| 64 | +): void { |
| 65 | + const path = projectSettingsPath(projectPath); |
| 66 | + const existing = loadScopedSettings(path); |
| 67 | + const merged = { ...existing, ...partial }; |
| 68 | + for (const [k, v] of Object.entries(partial)) { |
| 69 | + if (v === null) delete merged[k]; |
| 70 | + } |
| 71 | + saveJsonSafe(path, merged); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +**理由**:保持 API 一致性,`saveUserSettings` 和 `saveProjectSettings` 共享相同的 merge + null-delete 语义。 |
| 76 | + |
| 77 | +### 3. 无工程目录时的行为 |
| 78 | + |
| 79 | +**决策**:`saveJsonSafe` 已包含 `mkdirSync(dir, { recursive: true })`,`.dscode/` 不存在时自动创建。无需额外处理。 |
| 80 | + |
| 81 | +**理由**:已有基础设施覆盖此 edge case,保持简单。 |
| 82 | + |
| 83 | +### 4. persistRule 切换到工程级 |
| 84 | + |
| 85 | +**决策**:`persistRule` 使用 `loadScopedSettings(projectSettingsPath(this.projectPath))` + `saveProjectSettings(this.projectPath, ...)` 替代原有的 `loadUserSettings()` + `saveUserSettings()`。 |
| 86 | + |
| 87 | +``` |
| 88 | +private persistRule(rule: PermissionRuleConfig): void { |
| 89 | + const settings = loadScopedSettings(projectSettingsPath(this.projectPath)); |
| 90 | + // ... same logic, but use saveProjectSettings(this.projectPath, ...) |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +**理由**:逻辑不变,仅目标路径变化。 |
| 95 | + |
| 96 | +## Risks / Trade-offs |
| 97 | + |
| 98 | +- **风险**:纯 REPL 模式(无 project)下写入 `/dev/null` 或根目录 — 实际上 Harness 始终有 `projectPath`(来自 `cliCwd ?? process.cwd()`),这不是问题。 |
| 99 | +- **风险**:团队协作中,工程师 A 的 allow 规则提交到 git 后影响工程师 B — 这是预期行为,工程级 settings 天然适用于团队共享。 |
| 100 | + |
| 101 | +## Open Questions |
| 102 | + |
| 103 | +无。 |
0 commit comments