dsh-claude-compat 0.2.0 → 0.3.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/README.md +20 -18
- package/README.zh-CN.md +20 -18
- package/package.json +6 -2
- package/src/index.js +85 -38
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
12
|
+
<img src="https://img.shields.io/badge/version-0.3.0-blue?style=flat-square" alt="Version">
|
|
13
13
|
<img src="https://img.shields.io/badge/node-%3E%3D20-green?style=flat-square&logo=node.js&logoColor=white" alt="Node">
|
|
14
14
|
<img src="https://img.shields.io/badge/license-MIT-orange?style=flat-square" alt="License">
|
|
15
15
|
</p>
|
|
@@ -24,15 +24,24 @@
|
|
|
24
24
|
| `commands/*.md` | DSH skill provider | Same, plus user-invocable: `/command-name` works in the slash menu. |
|
|
25
25
|
| `rules/*.md` | Message-stream injection | Rules are concatenated, wrapped in a `<system-reminder>` envelope, and prepended as a user-role message at the front of the message array once per session — the same channel Claude Code uses (`prependUserContext`), which models follow reliably. |
|
|
26
26
|
|
|
27
|
+
The same three directories are also read from the **user-level** `~/.claude/` (skills, commands, rules). Same-name skills/commands/rules are deduped with a fixed priority:
|
|
28
|
+
|
|
29
|
+
**project `.claude` > DSH native > `~/.claude`**
|
|
30
|
+
|
|
31
|
+
- Project entries carry rank `50`, `~/.claude` entries rank `700`, and DSH's own bundled skills sit at rank `600` (`BUNDLED_SKILL_RANK`) — so a project skill always overrides the DSH-bundled and user copies, and a user skill never overrides a DSH-native one.
|
|
32
|
+
- Rule files with the same basename in `~/.claude/rules` are skipped when the project already provides one.
|
|
33
|
+
|
|
27
34
|
`CLAUDE.md` / `AGENTS.md` are **not** touched — DSH's built-in `dsh-agent-instructions` already handles those.
|
|
28
35
|
|
|
29
36
|
## Requirements
|
|
30
37
|
|
|
31
38
|
- DSH with a profile (e.g. `web`)
|
|
32
|
-
- A project using Claude Code conventions: `.claude/skills/`, `.claude/commands/`, `.claude/rules/`
|
|
39
|
+
- A project using Claude Code conventions: `.claude/skills/`, `.claude/commands/`, `.claude/rules/` (all optional; `~/.claude/` equivalents are also picked up)
|
|
33
40
|
|
|
34
41
|
## Install
|
|
35
42
|
|
|
43
|
+
One command — the package declares `dsh.bundle`, so DSH activates it automatically (no manual `cordis.patch.yml` editing):
|
|
44
|
+
|
|
36
45
|
```bash
|
|
37
46
|
dsh plugin --profile web add dsh-claude-compat
|
|
38
47
|
```
|
|
@@ -43,29 +52,22 @@ Or from GitHub:
|
|
|
43
52
|
dsh plugin --profile web add github:biedongbin/dsh-claude-compat
|
|
44
53
|
```
|
|
45
54
|
|
|
46
|
-
Then register it in `~/.dsh/profiles/web/cordis.patch.yml` (merge into your existing patch list):
|
|
47
|
-
|
|
48
|
-
```yaml
|
|
49
|
-
- insert:
|
|
50
|
-
- id: claude-compat
|
|
51
|
-
name: 'dsh-claude-compat'
|
|
52
|
-
config:
|
|
53
|
-
enableSkills: true
|
|
54
|
-
enableRules: true
|
|
55
|
-
```
|
|
56
|
-
|
|
57
55
|
Restart DSH (`dsh web`). Done — skills show up in `/`, rules are injected into every new session.
|
|
58
56
|
|
|
59
57
|
## Configuration
|
|
60
58
|
|
|
61
59
|
| Option | Default | Description |
|
|
62
60
|
|---|---|---|
|
|
63
|
-
| `enableSkills` | `true` | Register the `.claude/skills` + `.claude/commands` provider |
|
|
64
|
-
| `enableRules` | `true` | Inject
|
|
65
|
-
| `rulesMaxBytes` | `65536` | Hard cap on total injected rules text |
|
|
61
|
+
| `enableSkills` | `true` | Register the `.claude/skills` + `.claude/commands` provider (project and `~/.claude`) |
|
|
62
|
+
| `enableRules` | `true` | Inject project + `~/.claude` `rules/*.md` into the message stream |
|
|
63
|
+
| `rulesMaxBytes` | `65536` | Hard cap on total injected project rules text |
|
|
64
|
+
| `userRulesMaxBytes` | `65536` | Hard cap on total injected `~/.claude/rules` text |
|
|
66
65
|
| `projectRootMarkers` | `[".git"]` | Ancestor markers for project-root discovery |
|
|
67
|
-
| `skillRank` | `
|
|
68
|
-
| `skillSource` | `project-claude` | Source tag for catalog entries |
|
|
66
|
+
| `skillRank` | `50` | Provider rank for project `.claude` skills (wins every DSH-native collision) |
|
|
67
|
+
| `skillSource` | `project-claude` | Source tag for project catalog entries |
|
|
68
|
+
| `userSkillRank` | `700` | Provider rank for `~/.claude` skills (loses to DSH-native `600`) |
|
|
69
|
+
| `userSkillSource` | `user-claude` | Source tag for `~/.claude` catalog entries |
|
|
70
|
+
| `userClaudeDir` | `~/.claude` | User-level `.claude` directory (`~` expands to the home dir) |
|
|
69
71
|
|
|
70
72
|
## Notes
|
|
71
73
|
|
package/README.zh-CN.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
12
|
+
<img src="https://img.shields.io/badge/version-0.3.0-blue?style=flat-square" alt="Version">
|
|
13
13
|
<img src="https://img.shields.io/badge/node-%3E%3D20-green?style=flat-square&logo=node.js&logoColor=white" alt="Node">
|
|
14
14
|
<img src="https://img.shields.io/badge/license-MIT-orange?style=flat-square" alt="License">
|
|
15
15
|
</p>
|
|
@@ -24,15 +24,24 @@
|
|
|
24
24
|
| `commands/*.md` | DSH skill provider | 同上,且用户可直接调用:斜杠菜单里 `/command-name` 可用。 |
|
|
25
25
|
| `rules/*.md` | 消息流注入 | rules 全文拼接,包 `<system-reminder>` 信封,每会话一次以 user-role 消息插在消息数组最前 —— 与 Claude Code 同通道(`prependUserContext`),模型可靠遵循。 |
|
|
26
26
|
|
|
27
|
+
同类三个目录同样读取**用户级** `~/.claude/`(skills、commands、rules)。同名 skill/command/rule 去重,优先级固定:
|
|
28
|
+
|
|
29
|
+
**项目 `.claude` > DSH 原生 > `~/.claude`**
|
|
30
|
+
|
|
31
|
+
- 项目条目 rank=`50`,`~/.claude` 条目 rank=`700`,DSH 自带 bundled skills 固定 rank=`600`(`BUNDLED_SKILL_RANK`)——项目 skill 永远压过 DSH 内置与用户副本;用户 skill 永远压不过 DSH 原生。
|
|
32
|
+
- `~/.claude/rules` 中与项目同 basename 的 rule 文件被跳过(项目优先)。
|
|
33
|
+
|
|
27
34
|
`CLAUDE.md` / `AGENTS.md` **不碰** —— DSH 内置 `dsh-agent-instructions` 已处理。
|
|
28
35
|
|
|
29
36
|
## 环境要求
|
|
30
37
|
|
|
31
38
|
- DSH 及其 profile(如 `web`)
|
|
32
|
-
- 使用 Claude Code 约定的项目:`.claude/skills/`、`.claude/commands/`、`.claude/rules/`
|
|
39
|
+
- 使用 Claude Code 约定的项目:`.claude/skills/`、`.claude/commands/`、`.claude/rules/`(均可选;`~/.claude/` 对应目录同样生效)
|
|
33
40
|
|
|
34
41
|
## 安装
|
|
35
42
|
|
|
43
|
+
一条命令 —— 包声明了 `dsh.bundle`,DSH 自动激活(无需手改 `cordis.patch.yml`):
|
|
44
|
+
|
|
36
45
|
```bash
|
|
37
46
|
dsh plugin --profile web add dsh-claude-compat
|
|
38
47
|
```
|
|
@@ -43,29 +52,22 @@ dsh plugin --profile web add dsh-claude-compat
|
|
|
43
52
|
dsh plugin --profile web add github:biedongbin/dsh-claude-compat
|
|
44
53
|
```
|
|
45
54
|
|
|
46
|
-
然后在 `~/.dsh/profiles/web/cordis.patch.yml` 注册(合并进现有 patch 列表):
|
|
47
|
-
|
|
48
|
-
```yaml
|
|
49
|
-
- insert:
|
|
50
|
-
- id: claude-compat
|
|
51
|
-
name: 'dsh-claude-compat'
|
|
52
|
-
config:
|
|
53
|
-
enableSkills: true
|
|
54
|
-
enableRules: true
|
|
55
|
-
```
|
|
56
|
-
|
|
57
55
|
重启 DSH(`dsh web`)。完成 —— skills 出现在 `/` 菜单,rules 注入每个新会话。
|
|
58
56
|
|
|
59
57
|
## 配置
|
|
60
58
|
|
|
61
59
|
| 选项 | 默认值 | 说明 |
|
|
62
60
|
|---|---|---|
|
|
63
|
-
| `enableSkills` | `true` | 注册 `.claude/skills` + `.claude/commands` provider |
|
|
64
|
-
| `enableRules` | `true` |
|
|
65
|
-
| `rulesMaxBytes` | `65536` |
|
|
61
|
+
| `enableSkills` | `true` | 注册 `.claude/skills` + `.claude/commands` provider(项目与 `~/.claude` 均含) |
|
|
62
|
+
| `enableRules` | `true` | 注入项目 + `~/.claude` 的 `rules/*.md` 到消息流 |
|
|
63
|
+
| `rulesMaxBytes` | `65536` | 注入项目 rules 总量硬上限 |
|
|
64
|
+
| `userRulesMaxBytes` | `65536` | 注入 `~/.claude/rules` 总量硬上限 |
|
|
66
65
|
| `projectRootMarkers` | `[".git"]` | 项目根发现的祖先标记 |
|
|
67
|
-
| `skillRank` | `
|
|
68
|
-
| `skillSource` | `project-claude` | catalog 条目来源标签 |
|
|
66
|
+
| `skillRank` | `50` | 项目 `.claude` skills 的 provider 排名(压过一切 DSH 原生冲突) |
|
|
67
|
+
| `skillSource` | `project-claude` | 项目 catalog 条目来源标签 |
|
|
68
|
+
| `userSkillRank` | `700` | `~/.claude` skills 的 provider 排名(输给 DSH 原生 `600`) |
|
|
69
|
+
| `userSkillSource` | `user-claude` | `~/.claude` catalog 条目来源标签 |
|
|
70
|
+
| `userClaudeDir` | `~/.claude` | 用户级 `.claude` 目录(`~` 展开为 home 目录) |
|
|
69
71
|
|
|
70
72
|
## 说明
|
|
71
73
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-compat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness plugin: bridge Claude Code's .claude/
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "DeepSeek Harness plugin: bridge Claude Code's .claude/ directories (project and ~/.claude: skills, commands, rules) into DSH native skill registry and message-stream rules injection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
17
|
"README.md",
|
|
18
|
+
"README.zh-CN.md",
|
|
18
19
|
"cordis.patch.yml"
|
|
19
20
|
],
|
|
20
21
|
"keywords": [
|
|
@@ -44,5 +45,8 @@
|
|
|
44
45
|
},
|
|
45
46
|
"engines": {
|
|
46
47
|
"node": ">=20"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"check": "node --check src/index.js && node --test test/self-check.test.js"
|
|
47
51
|
}
|
|
48
52
|
}
|
package/src/index.js
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Two contributions to the live runtime:
|
|
4
4
|
// 1. Skill provider on ctx.skills — scans <projectRoot>/.claude/skills/**/SKILL.md
|
|
5
|
-
// and <projectRoot>/.claude/commands/*.md
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
5
|
+
// and <projectRoot>/.claude/commands/*.md (plus the user-level
|
|
6
|
+
// ~/.claude/skills and ~/.claude/commands), surfaces them as DSH skills so
|
|
7
|
+
// the `/skill-name` slash trigger, the `skill` tool, and the model-visible
|
|
8
|
+
// catalog pick them up natively. Only name+description load at discovery;
|
|
9
|
+
// the body loads on demand — same contract as the shipped filesystem
|
|
10
|
+
// provider.
|
|
11
|
+
// 2. Rules message-stream injection — injects <projectRoot>/.claude/rules/*.md
|
|
12
|
+
// and ~/.claude/rules/*.md as ONE user-role <system-reminder> message
|
|
13
|
+
// prepended at the front of the message array, once per session
|
|
14
|
+
// (agent/pre-step). This mirrors Claude Code's prependUserContext channel,
|
|
15
|
+
// which models follow reliably. Same-name rule files are deduped,
|
|
16
|
+
// project .claude winning over ~/.claude.
|
|
17
|
+
// CLAUDE.md / AGENTS.md are already handled by dsh-agent-instructions,
|
|
11
18
|
// so we do NOT re-inject them here.
|
|
12
19
|
//
|
|
13
20
|
// Skill name flattening: .claude/skills/gitnexus/gitnexus-guide/SKILL.md →
|
|
@@ -17,13 +24,22 @@
|
|
|
17
24
|
// Commands: .claude/commands/commit-changes.md → skill "commit-changes",
|
|
18
25
|
// user-invocable forced true so `/commit-changes` works in the slash menu.
|
|
19
26
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
27
|
+
// Precedence (DSH registry semantics: candidates sort by rank ascending and
|
|
28
|
+
// duplicate skill names are first-wins — LOWER rank wins). The native ladder
|
|
29
|
+
// is project-dsh 100, project-agents 200, custom 300, user-dsh 400,
|
|
30
|
+
// user-agents 500, DSH bundled 600 (fixed BUNDLED_SKILL_RANK). This plugin
|
|
31
|
+
// emits project .claude at rank 50 and ~/.claude at rank 700, so conflicts
|
|
32
|
+
// resolve as: project .claude > DSH native (600) > ~/.claude.
|
|
33
|
+
//
|
|
34
|
+
// Rules are read once per session (cached per session cwd, from
|
|
35
|
+
// agent.session.header.cwd — NOT process.cwd(), the DSH process may be
|
|
36
|
+
// launched from anywhere). Editing a rule mid-session takes effect in the
|
|
37
|
+
// next session.
|
|
23
38
|
|
|
24
39
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
25
40
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
26
41
|
import { join, dirname, resolve } from 'node:path';
|
|
42
|
+
import { homedir } from 'node:os';
|
|
27
43
|
import z from '@deepseek-ai/schemastery';
|
|
28
44
|
import { parse } from 'yaml';
|
|
29
45
|
import { isSkillName } from '@deepseek-ai/dsh-skill';
|
|
@@ -34,11 +50,17 @@ export const inject = ['skills'];
|
|
|
34
50
|
|
|
35
51
|
export const Config = z.object({
|
|
36
52
|
projectRootMarkers: z.array(z.string()).default(['.git']),
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
53
|
+
// DSH dedupes by rank ascending (lower wins). 50 beats the DSH-native
|
|
54
|
+
// project roots (100/200) and the bundled root (600) — project .claude wins
|
|
55
|
+
// every collision. 700 loses to bundled 600 — DSH native wins over
|
|
56
|
+
// ~/.claude. Final priority: project .claude > DSH native > ~/.claude.
|
|
57
|
+
skillRank: z.number().default(50),
|
|
40
58
|
skillSource: z.string().default('project-claude'),
|
|
59
|
+
userSkillRank: z.number().default(700),
|
|
60
|
+
userSkillSource: z.string().default('user-claude'),
|
|
61
|
+
userClaudeDir: z.string().default('~/.claude'),
|
|
41
62
|
rulesMaxBytes: z.number().default(65536),
|
|
63
|
+
userRulesMaxBytes: z.number().default(65536),
|
|
42
64
|
enableRules: z.boolean().default(true),
|
|
43
65
|
enableSkills: z.boolean().default(true),
|
|
44
66
|
});
|
|
@@ -60,27 +82,38 @@ class ClaudeCompatSkillProvider {
|
|
|
60
82
|
this.ctx = ctx;
|
|
61
83
|
this.name = 'claude-compat';
|
|
62
84
|
this.config = config;
|
|
63
|
-
this.skillRank = config.skillRank ??
|
|
85
|
+
this.skillRank = config.skillRank ?? 50;
|
|
64
86
|
this.source = config.skillSource ?? 'project-claude';
|
|
87
|
+
this.userSkillRank = config.userSkillRank ?? 700;
|
|
88
|
+
this.userSkillSource = config.userSkillSource ?? 'user-claude';
|
|
89
|
+
this.userClaudeDir = resolveUserClaudeDir(config.userClaudeDir);
|
|
65
90
|
control.signal.addEventListener('abort', () => {}, { once: true });
|
|
66
91
|
}
|
|
67
92
|
|
|
68
93
|
async list(options) {
|
|
69
94
|
const cwd = options?.cwd;
|
|
70
|
-
if (cwd === undefined || cwd === null) return [];
|
|
71
|
-
const projectRoot = await findProjectRoot(cwd, this.config.projectRootMarkers);
|
|
72
|
-
if (projectRoot === undefined) return [];
|
|
73
|
-
const claudeDir = join(projectRoot, '.claude');
|
|
74
|
-
if (!(await pathExists(claudeDir))) return [];
|
|
75
|
-
|
|
76
95
|
const candidates = [];
|
|
77
|
-
|
|
96
|
+
// Project .claude needs a cwd to locate the project root; user ~/.claude
|
|
97
|
+
// is cwd-independent (mirrors DSH's own user-dsh/user-agents roots, which
|
|
98
|
+
// are scanned unconditionally).
|
|
99
|
+
if (cwd !== undefined && cwd !== null) {
|
|
100
|
+
const projectRoot = await findProjectRoot(cwd, this.config.projectRootMarkers);
|
|
101
|
+
if (projectRoot !== undefined) {
|
|
102
|
+
await this.addRootCandidates(candidates, join(projectRoot, '.claude'), this.source, this.skillRank);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
await this.addRootCandidates(candidates, this.userClaudeDir, this.userSkillSource, this.userSkillRank);
|
|
106
|
+
return candidates;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async addRootCandidates(candidates, claudeDir, source, rank) {
|
|
110
|
+
if (!(await pathExists(claudeDir))) return;
|
|
111
|
+
for (const c of await discoverSkills(join(claudeDir, 'skills'), this.name, source, rank)) {
|
|
78
112
|
candidates.push(c);
|
|
79
113
|
}
|
|
80
|
-
for (const c of await discoverCommands(join(claudeDir, 'commands'), this.name,
|
|
114
|
+
for (const c of await discoverCommands(join(claudeDir, 'commands'), this.name, source, rank)) {
|
|
81
115
|
candidates.push(c);
|
|
82
116
|
}
|
|
83
|
-
return candidates;
|
|
84
117
|
}
|
|
85
118
|
|
|
86
119
|
async get(candidate) {
|
|
@@ -103,7 +136,7 @@ class ClaudeCompatSkillProvider {
|
|
|
103
136
|
}
|
|
104
137
|
}
|
|
105
138
|
|
|
106
|
-
// ─── discovery:
|
|
139
|
+
// ─── discovery: <root>/.claude/skills (recursive, ≤3 levels) ─────────────────
|
|
107
140
|
|
|
108
141
|
async function discoverSkills(rootDir, providerName, source, rank) {
|
|
109
142
|
const out = [];
|
|
@@ -132,7 +165,7 @@ async function discoverSkills(rootDir, providerName, source, rank) {
|
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
167
|
|
|
135
|
-
// ─── discovery:
|
|
168
|
+
// ─── discovery: <root>/.claude/commands (flat) ───────────────────────────────
|
|
136
169
|
|
|
137
170
|
async function discoverCommands(rootDir, providerName, source, rank) {
|
|
138
171
|
const out = [];
|
|
@@ -241,22 +274,29 @@ function registerRulesSection(ctx, config) {
|
|
|
241
274
|
}
|
|
242
275
|
|
|
243
276
|
function buildRulesText(cwd, config, maxBytes) {
|
|
244
|
-
const projectRoot = findProjectRootSync(cwd, config.projectRootMarkers);
|
|
245
|
-
if (projectRoot === undefined) return '';
|
|
246
|
-
const rulesDir = join(projectRoot, '.claude', 'rules');
|
|
247
|
-
const files = listMdFilesSync(rulesDir);
|
|
248
|
-
if (files.length === 0) return '';
|
|
249
277
|
const parts = [];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
let
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
278
|
+
const seen = new Set();
|
|
279
|
+
const pushRoot = (rootDir, cap) => {
|
|
280
|
+
let remaining = cap;
|
|
281
|
+
const files = listMdFilesSync(rootDir);
|
|
282
|
+
for (const f of files) {
|
|
283
|
+
const basename = f.split('/').pop();
|
|
284
|
+
if (seen.has(basename)) continue; // higher-priority root already included
|
|
285
|
+
let raw;
|
|
286
|
+
try { raw = readFileSync(f, 'utf8'); } catch { continue; }
|
|
287
|
+
const chunk = `## ${basename}\n\n${raw.trim()}\n`;
|
|
288
|
+
if (chunk.length > remaining) break;
|
|
289
|
+
remaining -= chunk.length;
|
|
290
|
+
seen.add(basename);
|
|
291
|
+
parts.push(chunk);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
// Project .claude/rules first: wins same-name collisions against ~/.claude.
|
|
295
|
+
const projectRoot = findProjectRootSync(cwd, config.projectRootMarkers);
|
|
296
|
+
if (projectRoot !== undefined) {
|
|
297
|
+
pushRoot(join(projectRoot, '.claude', 'rules'), maxBytes);
|
|
259
298
|
}
|
|
299
|
+
pushRoot(join(resolveUserClaudeDir(config.userClaudeDir), 'rules'), config.userRulesMaxBytes ?? maxBytes);
|
|
260
300
|
if (parts.length === 0) return '';
|
|
261
301
|
// Exact envelope Claude Code uses in prependUserContext (api.ts):
|
|
262
302
|
// user-role <system-reminder> with "# claudeMd" framing.
|
|
@@ -294,6 +334,13 @@ function listMdFilesSync(dir) {
|
|
|
294
334
|
|
|
295
335
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
296
336
|
|
|
337
|
+
function resolveUserClaudeDir(dir) {
|
|
338
|
+
if (dir === undefined || dir === null || dir === '') return join(homedir(), '.claude');
|
|
339
|
+
if (dir === '~') return homedir();
|
|
340
|
+
if (dir.startsWith('~/')) return join(homedir(), dir.slice(2));
|
|
341
|
+
return resolve(dir);
|
|
342
|
+
}
|
|
343
|
+
|
|
297
344
|
async function findProjectRoot(cwd, markers = ['.git']) {
|
|
298
345
|
let current = resolve(cwd);
|
|
299
346
|
while (true) {
|