@ranger1/dx 0.1.98 → 0.1.100

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 CHANGED
@@ -192,53 +192,6 @@ target(端)不写死,由 `env-policy.jsonc.targets` 定义;`commands.jso
192
192
 
193
193
  查看 `example/`:包含一个最小可读的 `dx/config` 配置示例,以及如何在一个 pnpm+nx monorepo 中接入 dx。
194
194
 
195
- ## PR Review Loop(自动评审-修复闭环)
196
-
197
- 仓库内提供了基于 Codex Skill 的 PR 评审自动化工作流:并行评审 -> 聚合结论 -> 生成修复清单 -> 自动修复 -> 再评审,最多循环 3 轮,用于让 PR 更快收敛。
198
-
199
- ### 什么时候用
200
-
201
- - PR 变更较大、想要更系统地覆盖安全/性能/可维护性问题
202
- - 希望在 CI 通过前提下,把评审建议落成可执行修复清单(fixFile)
203
- - 希望避免同一个问题在不同轮次被反复提出(Decision Log)
204
-
205
- ### 如何运行
206
-
207
- 在 Codex 会话中触发该技能:
208
-
209
- ```text
210
- 使用 $pr-review-loop 对 PR #<PR_NUMBER> 执行审核闭环
211
- ```
212
-
213
- 技能入口与说明见:
214
-
215
- - `skills/pr-review-loop/SKILL.md`
216
- - `skills/pr-review-loop/references/agents/*.md`
217
-
218
- ### 工作流概览
219
-
220
- - 预检(`pr-precheck`):先做编译/基础 gate,不通过则终止流程
221
- - 获取上下文(`pr-context`):生成本轮上下文缓存 `contextFile` 与 `runId`
222
- - 并行评审(reviewers):按 `./reviewer/*-reviewer.md` 并行审查并产出 reviewFile
223
- - 聚合(`pr-review-aggregate` 模式 A):合并评审结果、去重、发布 Review Summary、生成 `fixFile`
224
- - 修复(`fixer`):按 `fixFile` 执行修复并产出 `fixReportFile`
225
- - 发布修复报告(`pr-review-aggregate` 模式 B)
226
-
227
- ### 缓存文件(项目内 `./.cache/`)
228
-
229
- 该流程中间产物写入 `./.cache/`,并在各阶段传递相对路径:
230
-
231
- - `./.cache/pr-context-pr<PR>-r<ROUND>-<RUN_ID>.md`(contextFile)
232
- - `./.cache/review-<ROLE_CODE>-pr<PR>-r<ROUND>-<RUN_ID>.md`(reviewFile)
233
- - `./.cache/fix-pr<PR>-r<ROUND>-<RUN_ID>.md`(fixFile)
234
- - `./.cache/fix-report-pr<PR>-r<ROUND>-<RUN_ID>.md`(fixReportFile)
235
-
236
- ### Decision Log(跨轮次决策日志)
237
-
238
- - 文件:`./.cache/decision-log-pr<PR_NUMBER>.md`
239
- - 作用:记录每轮 Fixed/Rejected 结论,后续轮次用于过滤重复问题
240
- - 规则:默认 append-only,保留历史决策用于收敛
241
-
242
195
  ## 命令
243
196
 
244
197
  dx 的命令由 `dx/config/commands.json` 驱动,并且内置了一些 internal runner(避免项目侧依赖任何 `scripts/lib/*.js`):
@@ -266,6 +219,12 @@ dx test e2e backend apps/backend/e2e/auth
266
219
  dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts
267
220
  ```
268
221
 
222
+ 关于 `dx initial`:
223
+
224
+ - `dx initial` 会把 npm 包内置的 `skills/` 同步到 `~/.codex/skills` 与 `~/.claude/skills`。
225
+ - 包内管理的同名 skill 目录会按当前包内容替换;旧版本中已删除的官方 skill 会被清理。
226
+ - 不属于包内管理的其他用户自有 skill 目录会保留。
227
+
269
228
  关于 `help`:
270
229
 
271
230
  - `dx --help`
@@ -4,18 +4,14 @@ 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']
7
+ const DEPRECATED_SKILL_DIRS = ['pr-review-loop', 'git-commit-and-pr', 'autospec']
8
+ const TEMP_DIR_PATTERN = /^\..+\.(tmp|backup)-\d+-\d+$/
8
9
 
9
10
  async function collectAllFiles(dir) {
10
11
  const out = []
11
12
 
12
13
  async function walk(current) {
13
- let entries
14
- try {
15
- entries = await fs.readdir(current, { withFileTypes: true })
16
- } catch {
17
- return
18
- }
14
+ const entries = await fs.readdir(current, { withFileTypes: true })
19
15
 
20
16
  for (const entry of entries) {
21
17
  const full = join(current, entry.name)
@@ -42,6 +38,15 @@ async function ensureDir(path) {
42
38
  await fs.mkdir(path, { recursive: true })
43
39
  }
44
40
 
41
+ async function pathExists(path) {
42
+ try {
43
+ await fs.access(path)
44
+ return true
45
+ } catch {
46
+ return false
47
+ }
48
+ }
49
+
45
50
  async function assertDirExists(path, label) {
46
51
  try {
47
52
  const st = await fs.stat(path)
@@ -55,18 +60,53 @@ async function assertDirExists(path, label) {
55
60
  }
56
61
 
57
62
  async function copyDirMerge({ srcDir, dstDir }) {
58
- const files = await collectAllFiles(srcDir)
59
-
60
- for (const file of files) {
61
- const rel = relative(srcDir, file)
62
- const topLevelDir = rel.split('/')[0]
63
- if (DEPRECATED_SKILL_DIRS.includes(topLevelDir)) continue
64
- const target = join(dstDir, rel)
65
- await ensureDir(dirname(target))
66
- await fs.copyFile(file, target)
63
+ const entries = await fs.readdir(srcDir, { withFileTypes: true })
64
+ let fileCount = 0
65
+
66
+ for (const entry of entries) {
67
+ if (!entry.isDirectory()) continue
68
+ if (DEPRECATED_SKILL_DIRS.includes(entry.name)) continue
69
+
70
+ const srcSkillDir = join(srcDir, entry.name)
71
+ const dstSkillDir = join(dstDir, entry.name)
72
+ const token = `${process.pid}-${Date.now()}`
73
+ const tmpSkillDir = join(dstDir, `.${entry.name}.tmp-${token}`)
74
+ const backupSkillDir = join(dstDir, `.${entry.name}.backup-${token}`)
75
+ let hasBackup = false
76
+
77
+ try {
78
+ const files = await collectAllFiles(srcSkillDir)
79
+ fileCount += files.length
80
+ await ensureDir(tmpSkillDir)
81
+ for (const file of files) {
82
+ const rel = relative(srcSkillDir, file)
83
+ const target = join(tmpSkillDir, rel)
84
+ await ensureDir(dirname(target))
85
+ await fs.copyFile(file, target)
86
+ }
87
+
88
+ if (await pathExists(dstSkillDir)) {
89
+ await fs.rename(dstSkillDir, backupSkillDir)
90
+ hasBackup = true
91
+ }
92
+ await fs.rename(tmpSkillDir, dstSkillDir)
93
+ if (hasBackup) {
94
+ await fs.rm(backupSkillDir, { recursive: true, force: true })
95
+ }
96
+ } catch (error) {
97
+ await fs.rm(tmpSkillDir, { recursive: true, force: true })
98
+ if (hasBackup && !(await pathExists(dstSkillDir))) {
99
+ await fs.rename(backupSkillDir, dstSkillDir)
100
+ hasBackup = false
101
+ }
102
+ if (hasBackup) {
103
+ await fs.rm(backupSkillDir, { recursive: true, force: true })
104
+ }
105
+ throw error
106
+ }
67
107
  }
68
108
 
69
- return { fileCount: files.length }
109
+ return { fileCount }
70
110
  }
71
111
 
72
112
  async function removeDeprecatedSkillDirs(skillsDir) {
@@ -75,6 +115,15 @@ async function removeDeprecatedSkillDirs(skillsDir) {
75
115
  }
76
116
  }
77
117
 
118
+ async function removeStaleTempDirs(skillsDir) {
119
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true })
120
+ for (const entry of entries) {
121
+ if (!entry.isDirectory()) continue
122
+ if (!TEMP_DIR_PATTERN.test(entry.name)) continue
123
+ await fs.rm(join(skillsDir, entry.name), { recursive: true, force: true })
124
+ }
125
+ }
126
+
78
127
  export async function runCodexInitial(options = {}) {
79
128
  const packageRoot = options.packageRoot
80
129
  if (!packageRoot) throw new Error('runCodexInitial: 缺少 packageRoot')
@@ -91,8 +140,10 @@ export async function runCodexInitial(options = {}) {
91
140
  const stats = []
92
141
  for (const target of targets) {
93
142
  await ensureDir(target.dir)
143
+ await removeStaleTempDirs(target.dir)
94
144
  await removeDeprecatedSkillDirs(target.dir)
95
145
  const copyStats = await copyDirMerge({ srcDir: srcSkillsDir, dstDir: target.dir })
146
+ await removeStaleTempDirs(target.dir)
96
147
  await removeDeprecatedSkillDirs(target.dir)
97
148
  stats.push({ ...target, ...copyStats })
98
149
  }
@@ -245,15 +245,31 @@ function isAllowedBySimpleGlob(path, globs) {
245
245
  if (glob === path) return true
246
246
  continue
247
247
  }
248
- // Only support a single trailing '*' for now.
249
- if (glob.endsWith('*')) {
250
- const prefix = glob.slice(0, -1)
251
- if (path.startsWith(prefix)) return true
252
- }
248
+ if (globToRegex(glob).test(path)) return true
253
249
  }
254
250
  return false
255
251
  }
256
252
 
253
+ function globToRegex(glob) {
254
+ let pattern = ''
255
+ for (let i = 0; i < glob.length; i++) {
256
+ const ch = glob[i]
257
+ if (ch === '*') {
258
+ if (glob[i + 1] === '*') {
259
+ pattern += '.*'
260
+ i += 1
261
+ } else {
262
+ pattern += '[^/]*'
263
+ }
264
+ } else if ('\\^$+?.()|[]{}'.includes(ch)) {
265
+ pattern += `\\${ch}`
266
+ } else {
267
+ pattern += ch
268
+ }
269
+ }
270
+ return new RegExp(`^${pattern}$`)
271
+ }
272
+
257
273
  function findOverlaps(namedSets) {
258
274
  const seen = new Map()
259
275
  const overlaps = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.98",
3
+ "version": "0.1.100",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,63 +1,66 @@
1
1
  ---
2
2
  name: doctor
3
- description: Use when 需要在本机一次性体检并修复 Codex 开发环境,包括 python3/python 别名、dx 初始化、agent-browser+Chromium 与 ripgrep。
3
+ description: 仅在用户显式调用 $doctor 或明确要求使用 doctor 技能时使用;不要通过关键词自动触发。
4
4
  ---
5
5
 
6
6
  # Doctor
7
7
 
8
8
  ## 概览
9
9
 
10
- 执行本技能时,优先运行 `scripts/doctor.sh`,由脚本完成并行检测、自动修复、最多三轮重试与最终报告。
11
- 并且在任何本地检测/修复动作前,必须先拉取并完整执行一次远程 INSTALL 指令(不是只下载)。
10
+ 本技能用于把当前机器调整到可稳定运行 Codex 开发工作流的状态。
12
11
 
13
- ## 适用场景
12
+ 不要把执行路径写死。模型应先识别当前系统、shell、包管理器、已有工具版本与用户权限,再自行选择最合适的检测和修复方式。完成修复后必须复检,并给出可读报告。
14
13
 
15
- - 新机器初始化 Codex 开发环境。
16
- - 发现命令缺失或版本漂移,希望一次性修复。
14
+ ## 目标状态
17
15
 
18
- ## 执行步骤
16
+ 最终环境应尽量满足:
19
17
 
20
- 1. 强制先执行并落地(不可跳过):
18
+ - `python3` 可用。
19
+ - `python` 可调用 Python 3,或有清晰说明当前系统无需/不应创建该别名。
20
+ - `node`、`npm`、`pnpm` 可用,且满足当前工作流需要。
21
+ - `dx` 可用,并已完成必要初始化。
22
+ - `agent-browser` 可用,且 Chromium/浏览器依赖已安装到可运行状态。
23
+ - `rg`(ripgrep)可用。
24
+ - 常用 PATH 配置在当前 shell 中可生效;若需要持久化,说明写入了哪个 shell 配置文件。
21
25
 
22
- ```bash
23
- INSTALL_URL="https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.codex/INSTALL.md"
24
- INSTALL_MD="$(mktemp -t codex-install.XXXXXX.md)"
26
+ ## 执行原则
25
27
 
26
- echo "Fetch and follow instructions from ${INSTALL_URL}"
27
- curl -fsSL "${INSTALL_URL}" -o "${INSTALL_MD}"
28
+ - 先诊断,再修复;不要未经确认就重复安装已经健康的工具。
29
+ - 优先使用系统已有的包管理器和用户态安装路径。
30
+ - 遇到多个可行方案时,选择对系统影响最小、最容易回滚的方案。
31
+ - 对需要管理员权限、网络下载或会修改 shell 配置的动作,执行前简短说明影响。
32
+ - 不要求固定轮次;根据实际结果迭代,直到通过验收或明确无法继续。
33
+ - 不要强制执行远程安装文档。只有当诊断显示确实需要外部安装指引时,才读取可信来源并按实际情况采用。
28
34
 
29
- # 必须阅读并执行 INSTALL.md 中要求执行的命令;不可只拉取不执行
30
- cat "${INSTALL_MD}"
31
- ```
35
+ ## 建议工作流
32
36
 
33
- 执行要求(强制):
34
- - 拉取后,按 `INSTALL.md` 的步骤顺序执行一遍。
35
- - 对其中出现的安装/初始化命令,必须实际执行,不可仅展示或解释。
36
- - 若某步失败,先修复前置条件后重试该步;完成后再继续 `doctor.sh`。
37
+ 1. 收集上下文:
38
+ - 操作系统与架构
39
+ - 当前 shell 与 PATH
40
+ - `python3`、`python`、`node`、`npm`、`pnpm`、`dx`、`agent-browser`、`rg` 的存在性与版本
41
+ - `CODEX_HOME` 与相关配置目录是否存在
42
+ 2. 对照目标状态判断缺口。
43
+ 3. 制定最小修复动作并执行。
44
+ 4. 每次修复后重新验证相关项。
45
+ 5. 所有项目完成后运行一次最终复检。
46
+ 6. 输出报告。
37
47
 
38
- 2. 直接运行:
48
+ ## 验证要求
39
49
 
40
- ```bash
41
- CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
42
- bash "$CODEX_HOME/skills/doctor/scripts/doctor.sh"
43
- ```
50
+ 最终复检至少覆盖:
44
51
 
45
- 3. 若需限制轮次(默认 3):
52
+ - 每个目标命令是否可被当前 shell 找到。
53
+ - 每个目标命令的版本或基本健康输出。
54
+ - `dx` 的初始化结果或当前初始化状态。
55
+ - `agent-browser` 是否能找到并使用已安装的浏览器依赖。
56
+ - 对未通过项给出失败原因、已尝试动作和下一步建议。
46
57
 
47
- ```bash
48
- CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
49
- bash "$CODEX_HOME/skills/doctor/scripts/doctor.sh" --max-rounds 3
50
- ```
58
+ ## 报告格式
51
59
 
52
- ## 脚本职责
60
+ 最终报告使用中文,至少包含:
53
61
 
54
- - 并行检测:`python3`、`python` 别名、`pnpm`、`dx`、`agent-browser`、`rg`。
55
- - 自动修复:按平台选择安装器修复缺失项。
56
- - 强制执行:每轮都运行 `pnpm add -g @ranger1/dx@latest && dx initial`。
57
- - agent-browser:安装/升级并执行 Chromium 安装。
58
- - 结果输出:展示每项状态、版本、关键信息;全部通过则退出 0,否则最多三轮后退出 1。
59
-
60
- ## 注意
61
-
62
- - 某些安装步骤可能需要管理员权限(例如 `sudo` 或 Homebrew 写权限)。
63
- - 若系统缺少包管理器,脚本会给出明确失败原因。
62
+ - 环境摘要:系统、shell、关键 PATH 变更。
63
+ - 检查结果表:检查项、状态、版本/证据、说明。
64
+ - 已执行修复:实际执行过的安装、链接、初始化或配置变更。
65
+ - 未完成项:若存在,说明阻塞原因和用户需要做什么。
66
+ - 结论:通过 / 部分通过 / 未通过。
@@ -1,4 +1,7 @@
1
1
  interface:
2
2
  display_name: "Doctor"
3
- short_description: "并行检测并自动修复 Codex 开发环境关键依赖与特性开关"
4
- default_prompt: "使用 $doctor 对当前系统执行并行体检、自动修复与三轮内收敛验证,并输出最终版本报告。"
3
+ short_description: "按目标状态自主诊断、修复并验证 Codex 开发环境"
4
+ default_prompt: "使用 $doctor 对当前系统进行 Codex 开发环境体检;根据实际缺口自主选择修复方式,完成后复检并输出报告。"
5
+
6
+ policy:
7
+ allow_implicit_invocation: false
@@ -15,6 +15,7 @@ description: PR 交付流程(仅限显式调用)
15
15
  - 禁止在 `main/master` 直接提交。
16
16
  - 每修复一个问题立即 commit 一次,禁止攒到最后一次性提交。
17
17
  - AI 自主判断是否拒绝修复某个问题,拒绝必须写明理由。
18
+ - 扫描范围内顺便发现的历史遗留问题(非本次 PR 引入),视同本次问题一并修复,不以"历史遗留"或"超出本 PR 范围"为由跳过。但不主动扩大扫描范围去寻找无关问题。
18
19
  - 上次跑完测试/Lint 后如果改过代码,必须重跑验证。
19
20
  - 使用 heredoc 写 commit message 和 gh 命令的 body(禁止 `\n`)。
20
21
  - 零参数设计:所有信息从仓库状态、当前分支、gh CLI 自动获取,不接受手动参数。
@@ -281,10 +282,12 @@ Step 3: 运行关联测试(根据以下改动范围判断需要跑哪些)
281
282
 
282
283
  审查者 A — 代码质量与架构(派独立 subagent):
283
284
  - 关注:架构合理性、SOLID 原则、错误处理、性能、安全
285
+ - 审查 PR diff 涉及的文件时,如果顺便发现同一文件中的历史遗留问题,也一并报告(不主动扩大到 diff 之外的文件)
284
286
  - 输入:PR diff
285
287
 
286
288
  审查者 B — 逻辑缺陷与规范(派独立 subagent):
287
289
  - 关注:逻辑缺陷、边界条件、命名规范、类型安全、测试覆盖
290
+ - 审查 PR diff 涉及的文件时,如果顺便发现同一文件中的历史遗留问题,也一并报告(不主动扩大到 diff 之外的文件)
288
291
  - 输入:PR diff
289
292
 
290
293
  > **禁止使用 `code-review` 技能或 `oh-my-claudecode:code-reviewer` agent 来执行审查。** 这些工具内置了自动发 PR 评论的行为,会导致 PR 上出现多条重复审核报告。必须派独立 subagent,并在 prompt 中明确要求"只返回审查结果文本,禁止调用 gh pr comment 或以任何方式发布 PR 评论"。
@@ -292,12 +295,14 @@ Step 3: 运行关联测试(根据以下改动范围判断需要跑哪些)
292
295
  Subagent A prompt:
293
296
  ```
294
297
  作为资深架构师审查此 PR diff,关注架构、性能、安全问题。
298
+ 审查 diff 涉及的文件时,如果顺便发现同一文件中已有的历史遗留问题(非本次 diff 引入),也一并报告——不要因为是历史代码就忽略。但不要主动扩大到 diff 未涉及的文件。
295
299
  【重要】只返回审查结果文本给调用者。禁止调用 gh pr comment 或以任何方式直接往 PR 发评论——评论由主 agent 统一发布。
296
300
  ```
297
301
 
298
302
  Subagent B prompt:
299
303
  ```
300
304
  作为质量工程师审查此 PR diff,关注逻辑缺陷、边界条件、规范遵从。
305
+ 审查 diff 涉及的文件时,如果顺便发现同一文件中已有的历史遗留问题(非本次 diff 引入),也一并报告——不要因为是历史代码就忽略。但不要主动扩大到 diff 未涉及的文件。
301
306
  【重要】只返回审查结果文本给调用者。禁止调用 gh pr comment 或以任何方式直接往 PR 发评论——评论由主 agent 统一发布。
302
307
  ```
303
308
 
@@ -379,8 +384,8 @@ MSG
379
384
  对每个问题:
380
385
 
381
386
  1. **判断是否修复**:
382
- - 修复:执行修改
383
- - 拒绝:记录理由(如:误报、设计意图、超出本 PR 范围)
387
+ - 修复:执行修改(包括扫描过程中顺便发现的历史遗留问题,不以"非本次引入"为由跳过)
388
+ - 拒绝:记录理由(如:误报、设计意图如此)。"历史遗留"或"超出本 PR 范围"不是合法的拒绝理由
384
389
 
385
390
  2. **修复后立即 commit**(一个问题一个 commit):
386
391
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: git-release
3
- description: Git 仓库中执行标准化版本发布流程并自动生成高质量中文发行说明。用于以下场景:需要从 release 分支发布新版本;需要从分支名提取并校验语义化版本(含 alpha/beta/rc 预发布);需要批量更新多个 package.json 的 version 字段并提交;需要基于最近 GitHub Release 汇总提交与 PR 信息、分类变更、生成发布摘要;需要创建 annotated tag、推送远端并创建 GitHub Release。
3
+ description: 仅在用户显式调用 $git-release 或明确要求使用 git-release 技能时使用;不要通过关键词自动触发。
4
4
  ---
5
5
 
6
6
  # Git Release
@@ -15,6 +15,7 @@ description: 在 Git 仓库中执行标准化版本发布流程并自动生成
15
15
  - 严格执行前置校验,任何硬性条件不满足时立即终止。
16
16
  - 发行说明必须结构化、可读、可追溯。
17
17
  - 命令默认在仓库根目录执行。
18
+ - 若能从当前 release 分支或自动建分支流程唯一推断出合法版本号,直接使用该版本继续发布,不要询问用户确认。
18
19
 
19
20
  ## 流程
20
21
 
@@ -33,7 +34,8 @@ description: 在 Git 仓库中执行标准化版本发布流程并自动生成
33
34
  - `release/v1.2.3` -> `v1.2.3` -> `1.2.3`
34
35
  - `release/v1.2.3-beta.2` -> `v1.2.3-beta.2` -> `1.2.3-beta.2`
35
36
  7. 检查目标 tag 是否已存在:`git tag -l "v<VERSION>"`。
36
- 8. 向用户确认版本号;若用户修改版本号,重新校验格式与 tag 冲突。
37
+ 8. 输出推断出的版本号和推断来源,直接使用该版本号继续执行;不要向用户请求确认。
38
+ 9. 仅当无法从分支名或自动建分支流程唯一推断出合法版本号时,终止并要求用户显式指定目标版本。
37
39
 
38
40
  ### 二、更新版本号
39
41
 
@@ -2,3 +2,6 @@ interface:
2
2
  display_name: "Git Release"
3
3
  short_description: "在 release 分支上自动完成版本发布与发行说明生成"
4
4
  default_prompt: "使用 $git-release 按规范执行一次发布并生成高质量发行说明。"
5
+
6
+ policy:
7
+ allow_implicit_invocation: false