@ranger1/dx 0.1.102 → 0.1.104
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 +3 -2
- package/lib/codex-initial.js +59 -22
- package/lib/exec.js +12 -0
- package/lib/nx-ignore.js +45 -0
- package/package.json +1 -1
- package/skills/doctor/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -221,8 +221,9 @@ dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts
|
|
|
221
221
|
|
|
222
222
|
关于 `dx initial`:
|
|
223
223
|
|
|
224
|
-
- `dx initial` 会把 npm 包内置的 `skills/`
|
|
225
|
-
-
|
|
224
|
+
- `dx initial` 会把 npm 包内置的 `skills/` 覆盖同步到 `~/.agents/skills`。
|
|
225
|
+
- `~/.claude/skills` 中包内管理的同名非软链接 skill 会先删除,再创建指向 `~/.agents/skills` 的软链接。
|
|
226
|
+
- `~/.codex/skills` 中包内管理的同名非软链接 skill 会被清理;已有软链接不会按旧副本删除。
|
|
226
227
|
- 不属于包内管理的其他用户自有 skill 目录会保留。
|
|
227
228
|
|
|
228
229
|
关于 `help`:
|
package/lib/codex-initial.js
CHANGED
|
@@ -4,9 +4,13 @@ import os from 'node:os'
|
|
|
4
4
|
|
|
5
5
|
import { logger } from './logger.js'
|
|
6
6
|
|
|
7
|
-
const DEPRECATED_SKILL_DIRS = ['pr-review-loop', 'git-commit-and-pr', 'autospec']
|
|
8
7
|
const TEMP_DIR_PATTERN = /^\..+\.(tmp|backup)-\d+-\d+$/
|
|
9
8
|
|
|
9
|
+
async function collectSkillNames(dir) {
|
|
10
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
11
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
async function collectAllFiles(dir) {
|
|
11
15
|
const out = []
|
|
12
16
|
|
|
@@ -65,7 +69,6 @@ async function copyDirMerge({ srcDir, dstDir }) {
|
|
|
65
69
|
|
|
66
70
|
for (const entry of entries) {
|
|
67
71
|
if (!entry.isDirectory()) continue
|
|
68
|
-
if (DEPRECATED_SKILL_DIRS.includes(entry.name)) continue
|
|
69
72
|
|
|
70
73
|
const srcSkillDir = join(srcDir, entry.name)
|
|
71
74
|
const dstSkillDir = join(dstDir, entry.name)
|
|
@@ -109,9 +112,18 @@ async function copyDirMerge({ srcDir, dstDir }) {
|
|
|
109
112
|
return { fileCount }
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
async function
|
|
113
|
-
for (const
|
|
114
|
-
|
|
115
|
+
async function removeManagedNonSymlinkSkills(skillsDir, skillNames) {
|
|
116
|
+
for (const skillName of skillNames) {
|
|
117
|
+
const target = join(skillsDir, skillName)
|
|
118
|
+
let stat
|
|
119
|
+
try {
|
|
120
|
+
stat = await fs.lstat(target)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error?.code === 'ENOENT') continue
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
if (stat.isSymbolicLink()) continue
|
|
126
|
+
await fs.rm(target, { recursive: true, force: true })
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
|
|
@@ -124,32 +136,57 @@ async function removeStaleTempDirs(skillsDir) {
|
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
|
|
139
|
+
async function linkSkillsToClaude({ skillNames, agentsSkillsDir, claudeSkillsDir }) {
|
|
140
|
+
for (const skillName of skillNames) {
|
|
141
|
+
const srcSkillDir = join(agentsSkillsDir, skillName)
|
|
142
|
+
const claudeSkillPath = join(claudeSkillsDir, skillName)
|
|
143
|
+
|
|
144
|
+
let stat
|
|
145
|
+
try {
|
|
146
|
+
stat = await fs.lstat(claudeSkillPath)
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error?.code !== 'ENOENT') throw error
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (stat?.isSymbolicLink()) {
|
|
152
|
+
const currentTarget = await fs.readlink(claudeSkillPath)
|
|
153
|
+
if (currentTarget === srcSkillDir) continue
|
|
154
|
+
await fs.rm(claudeSkillPath, { recursive: true, force: true })
|
|
155
|
+
} else if (stat) {
|
|
156
|
+
await fs.rm(claudeSkillPath, { recursive: true, force: true })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await fs.symlink(srcSkillDir, claudeSkillPath, 'dir')
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
127
163
|
export async function runCodexInitial(options = {}) {
|
|
128
164
|
const packageRoot = options.packageRoot
|
|
129
165
|
if (!packageRoot) throw new Error('runCodexInitial: 缺少 packageRoot')
|
|
130
166
|
|
|
131
167
|
const homeDir = options.homeDir || os.homedir()
|
|
132
168
|
const srcSkillsDir = join(packageRoot, 'skills')
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
]
|
|
169
|
+
const agentsSkillsDir = join(homeDir, '.agents', 'skills')
|
|
170
|
+
const claudeSkillsDir = join(homeDir, '.claude', 'skills')
|
|
171
|
+
const codexSkillsDir = join(homeDir, '.codex', 'skills')
|
|
137
172
|
|
|
138
173
|
await assertDirExists(srcSkillsDir, '模板目录 skills')
|
|
174
|
+
const skillNames = await collectSkillNames(srcSkillsDir)
|
|
139
175
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
176
|
+
await ensureDir(codexSkillsDir)
|
|
177
|
+
await ensureDir(claudeSkillsDir)
|
|
178
|
+
await ensureDir(agentsSkillsDir)
|
|
179
|
+
|
|
180
|
+
await removeManagedNonSymlinkSkills(codexSkillsDir, skillNames)
|
|
181
|
+
await removeManagedNonSymlinkSkills(claudeSkillsDir, skillNames)
|
|
182
|
+
|
|
183
|
+
await removeStaleTempDirs(agentsSkillsDir)
|
|
184
|
+
const copyStats = await copyDirMerge({ srcDir: srcSkillsDir, dstDir: agentsSkillsDir })
|
|
185
|
+
await removeStaleTempDirs(agentsSkillsDir)
|
|
186
|
+
await linkSkillsToClaude({ skillNames, agentsSkillsDir, claudeSkillsDir })
|
|
150
187
|
|
|
151
188
|
logger.success('已初始化 skills 模板')
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
189
|
+
logger.info(`agents skills: 覆盖复制 ${copyStats.fileCount} 个文件 -> ${agentsSkillsDir}`)
|
|
190
|
+
logger.info(`claude skills: 已创建 ${skillNames.length} 个软链接 -> ${claudeSkillsDir}`)
|
|
191
|
+
logger.info(`codex skills: 已清理 ${skillNames.length} 个包内托管 skill 的旧副本 -> ${codexSkillsDir}`)
|
|
155
192
|
}
|
package/lib/exec.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveTargetRequiredVars,
|
|
10
10
|
} from './env-policy.js'
|
|
11
11
|
import { confirmManager } from './confirm.js'
|
|
12
|
+
import { ensureNxIgnoreToolDirs, isNxCommand } from './nx-ignore.js'
|
|
12
13
|
|
|
13
14
|
const execPromise = promisify(nodeExec)
|
|
14
15
|
|
|
@@ -171,6 +172,17 @@ export class ExecManager {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
if (isNxCommand(fullCommand)) {
|
|
176
|
+
try {
|
|
177
|
+
const result = ensureNxIgnoreToolDirs(cwd || process.cwd())
|
|
178
|
+
if (result.changed) {
|
|
179
|
+
logger.info(`已更新 .nxignore,排除工具元数据目录: ${result.added.join(', ')}`)
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.warn(`自动更新 .nxignore 失败: ${error?.message || String(error)}`)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
174
186
|
logger.command(fullCommand)
|
|
175
187
|
|
|
176
188
|
// 执行命令(可能重试)
|
package/lib/nx-ignore.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export const NX_IGNORE_TOOL_DIR_PATTERNS = [
|
|
5
|
+
'.cache/',
|
|
6
|
+
'.claude/',
|
|
7
|
+
'.codex/',
|
|
8
|
+
'.idea/',
|
|
9
|
+
'.omc/',
|
|
10
|
+
'.omx/',
|
|
11
|
+
'.opencode/',
|
|
12
|
+
'.pytest_cache/',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const MANAGED_BLOCK_START = '# dx managed tool metadata ignores'
|
|
16
|
+
|
|
17
|
+
export function isNxCommand(command) {
|
|
18
|
+
return /\bnx(?:\.js)?\b/.test(String(command || ''))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ensureNxIgnoreToolDirs(projectRoot = process.cwd()) {
|
|
22
|
+
const root = String(projectRoot || process.cwd())
|
|
23
|
+
const nxIgnorePath = join(root, '.nxignore')
|
|
24
|
+
const existing = existsSync(nxIgnorePath) ? readFileSync(nxIgnorePath, 'utf8') : ''
|
|
25
|
+
const existingLines = new Set(
|
|
26
|
+
existing
|
|
27
|
+
.split(/\r?\n/)
|
|
28
|
+
.map(line => line.trim())
|
|
29
|
+
.filter(Boolean),
|
|
30
|
+
)
|
|
31
|
+
const missing = NX_IGNORE_TOOL_DIR_PATTERNS.filter(pattern => !existingLines.has(pattern))
|
|
32
|
+
|
|
33
|
+
if (missing.length === 0) {
|
|
34
|
+
return { changed: false, path: nxIgnorePath, added: [] }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const prefix = existing.trimEnd()
|
|
38
|
+
const blockLines = existingLines.has(MANAGED_BLOCK_START)
|
|
39
|
+
? missing
|
|
40
|
+
: [MANAGED_BLOCK_START, ...missing]
|
|
41
|
+
const next = `${prefix ? `${prefix}\n\n` : ''}${blockLines.join('\n')}\n`
|
|
42
|
+
|
|
43
|
+
writeFileSync(nxIgnorePath, next)
|
|
44
|
+
return { changed: true, path: nxIgnorePath, added: missing }
|
|
45
|
+
}
|
package/package.json
CHANGED
package/skills/doctor/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: 仅在用户显式调用 $doctor 或明确要求使用 doctor 技
|
|
|
7
7
|
|
|
8
8
|
## 概览
|
|
9
9
|
|
|
10
|
-
本技能用于把当前机器调整到可稳定运行
|
|
10
|
+
本技能用于把当前机器调整到可稳定运行 agent 开发工作流的状态。
|
|
11
11
|
|
|
12
12
|
不要把执行路径写死。模型应先识别当前系统、shell、包管理器、已有工具版本与用户权限,再自行选择最合适的检测和修复方式。完成修复后必须复检,并给出可读报告。
|
|
13
13
|
|
|
@@ -20,7 +20,8 @@ description: 仅在用户显式调用 $doctor 或明确要求使用 doctor 技
|
|
|
20
20
|
- `node`、`npm`、`pnpm` 可用,且满足当前工作流需要。
|
|
21
21
|
- `dx` 可用,并已完成必要初始化。
|
|
22
22
|
- `agent-browser` 可用,且 Chromium/浏览器依赖已安装到可运行状态。
|
|
23
|
-
- `rg`(
|
|
23
|
+
- `rg`(rip grep)可用。
|
|
24
|
+
- `rtk` 可用。
|
|
24
25
|
- 常用 PATH 配置在当前 shell 中可生效;若需要持久化,说明写入了哪个 shell 配置文件。
|
|
25
26
|
|
|
26
27
|
## 执行原则
|
|
@@ -38,7 +39,6 @@ description: 仅在用户显式调用 $doctor 或明确要求使用 doctor 技
|
|
|
38
39
|
- 操作系统与架构
|
|
39
40
|
- 当前 shell 与 PATH
|
|
40
41
|
- `python3`、`python`、`node`、`npm`、`pnpm`、`dx`、`agent-browser`、`rg` 的存在性与版本
|
|
41
|
-
- `CODEX_HOME` 与相关配置目录是否存在
|
|
42
42
|
2. 对照目标状态判断缺口。
|
|
43
43
|
3. 制定最小修复动作并执行。
|
|
44
44
|
4. 每次修复后重新验证相关项。
|