@ranger1/dx 0.1.94 → 0.1.95

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.
@@ -59,6 +59,8 @@ async function copyDirMerge({ srcDir, dstDir }) {
59
59
 
60
60
  for (const file of files) {
61
61
  const rel = relative(srcDir, file)
62
+ const topLevelDir = rel.split('/')[0]
63
+ if (DEPRECATED_SKILL_DIRS.includes(topLevelDir)) continue
62
64
  const target = join(dstDir, rel)
63
65
  await ensureDir(dirname(target))
64
66
  await fs.copyFile(file, target)
@@ -91,6 +93,7 @@ export async function runCodexInitial(options = {}) {
91
93
  await ensureDir(target.dir)
92
94
  await removeDeprecatedSkillDirs(target.dir)
93
95
  const copyStats = await copyDirMerge({ srcDir: srcSkillsDir, dstDir: target.dir })
96
+ await removeDeprecatedSkillDirs(target.dir)
94
97
  stats.push({ ...target, ...copyStats })
95
98
  }
96
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.94",
3
+ "version": "0.1.95",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,23 +9,76 @@ description: Use when backend、NestJS、领域异常治理或错误处理审查
9
9
 
10
10
  先判断项目是否已经具备统一错误处理基础设施,再扫描业务代码是否绕过 `DomainException` / `ErrorCode` 体系。默认只输出审计结果和修复建议,不自动改代码;只有用户明确要求时才进入自动修复。
11
11
 
12
+ ## 扫描范围
13
+
14
+ 脚本不预设任何项目路径,需要通过 `--src-dir` / `--e2e-dir` 显式传入。执行前必须先完成项目探索(见下方步骤 0)。
15
+
16
+ 以下路径/后缀始终排除,不会产生误报:
17
+
18
+ | 排除类别 | 路径/后缀 |
19
+ |----------|-----------|
20
+ | 单元测试 | `*.spec.ts`、`*.test.ts`、`*.e2e-spec.ts` |
21
+ | 测试辅助 | `*.mock.ts`、`*.stub.ts`、`*.fixture.ts`、`fixtures/`、`test-utils/`、`testing/`、`__tests__/`、`__test__/` |
22
+ | 基础设施 | `*/filters/`、`prisma/`、`scripts/` |
23
+ | 异常定义 | `*.exception.ts` |
24
+ | 入口文件 | `main.ts` |
25
+
26
+ 只有在用户**明确要求**评估测试债务时,才传 `--e2e-dir` + `--scope e2e` 扫描测试代码。
27
+
12
28
  ## 快速开始
13
29
 
14
- 1. 先审计生产代码:
30
+ ### 步骤 0:项目探索(每个项目首次执行时必须完成)
31
+
32
+ 在调用脚本前,先探索项目结构,识别出后端生产代码和测试代码的实际路径。方法:
33
+
34
+ 1. 查看项目根目录结构(`ls` 或 Glob)
35
+ 2. 识别后端应用目录(可能是 `apps/backend/src`、`src`、`server/src` 等)
36
+ 3. 识别测试目录(可能是 `apps/backend/e2e`、`test`、`e2e`、`__tests__` 等)
37
+ 4. 如果是 monorepo,可能有多个后端服务,每个都需要单独传 `--src-dir`
38
+
39
+ 典型示例:
40
+
41
+ | 项目类型 | 生产代码 | 测试代码 |
42
+ |----------|----------|----------|
43
+ | NestJS monorepo | `apps/backend/src` | `apps/backend/e2e` |
44
+ | 单体 NestJS | `src` | `test` 或 `e2e` |
45
+ | 多服务 monorepo | `apps/api/src`、`apps/worker/src` | `apps/api/e2e`、`apps/worker/e2e` |
46
+
47
+ ### 步骤 1:审计生产代码
48
+
49
+ 将探索到的路径传给脚本(`--src-dir` 可传多个):
50
+
51
+ ```bash
52
+ SKILL_HOME="${SKILL_HOME:-$HOME/.claude/skills}"
53
+ python "$SKILL_HOME/error-handling-audit-fixer/scripts/error_handling_audit.py" \
54
+ --workspace "$PWD" \
55
+ --src-dir <探索到的生产代码路径>
56
+ ```
57
+
58
+ 示例(当前项目):
15
59
 
16
60
  ```bash
17
- CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
18
- python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_audit.py" \
61
+ python "$SKILL_HOME/error-handling-audit-fixer/scripts/error_handling_audit.py" \
19
62
  --workspace "$PWD" \
20
- --scope src
63
+ --src-dir apps/backend/src
21
64
  ```
22
65
 
23
- 2. 再按需审计 E2E 或全量:
66
+ 多服务示例:
24
67
 
25
68
  ```bash
26
- python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_audit.py" \
69
+ python "$SKILL_HOME/error-handling-audit-fixer/scripts/error_handling_audit.py" \
27
70
  --workspace "$PWD" \
28
- --scope all \
71
+ --src-dir apps/api/src \
72
+ --src-dir apps/worker/src
73
+ ```
74
+
75
+ ### 步骤 2:(仅在用户明确要求时)审计测试代码
76
+
77
+ ```bash
78
+ python "$SKILL_HOME/error-handling-audit-fixer/scripts/error_handling_audit.py" \
79
+ --workspace "$PWD" \
80
+ --e2e-dir <探索到的测试代码路径> \
81
+ --scope e2e \
29
82
  --output-json /tmp/error-handling-audit.json
30
83
  ```
31
84
 
@@ -37,7 +90,7 @@ python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_aud
37
90
 
38
91
  ## 执行流程
39
92
 
40
- 1. 默认先扫描 `apps/backend/src`,只有在用户明确要求或需要评估测试债务时再扫描 `apps/backend/e2e`。
93
+ 1. 默认只扫描 `apps/backend/src`(生产代码)。测试代码、E2E、prisma seed、脚本等非生产路径全部排除。只有在用户明确要求评估测试债务时,才传 `--scope e2e` 扫描测试代码。
41
94
  2. 先判断基础设施状态:
42
95
  - 是否存在 `DomainException`
43
96
  - 是否存在 `ErrorCode`
@@ -56,47 +109,31 @@ python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_aud
56
109
  6. 只有在用户明确说“自动修复”“直接改”或等价表述时,才进入落代码阶段。
57
110
  7. 若脚本结果与实际代码不一致,必须抽样打开命中文件复核,不要把脚本结果当成绝对真相。
58
111
 
59
- ## 审计命令
112
+ ## 审计命令(rg 快速复核)
60
113
 
61
- 项目具备 ripgrep 时,可先用下列命令快速复核:
114
+ 以下 rg 命令用于手工复核,将 `<SRC_DIR>` 替换为步骤 0 探索到的实际路径:
62
115
 
63
116
  ```bash
64
117
  rg "new (BadRequestException|UnauthorizedException|ForbiddenException|NotFoundException|HttpException|InternalServerErrorException)\(" \
65
- apps/backend/src apps/backend/e2e \
66
- --glob '!*spec.ts' \
118
+ <SRC_DIR> \
119
+ --glob '!*spec.ts' --glob '!*test.ts' \
67
120
  --glob '!*exception.ts' \
68
- --glob '!apps/backend/src/common/filters/**' \
69
- --glob '!apps/backend/src/main.ts'
121
+ --glob '!*/filters/**' \
122
+ --glob '!*main.ts'
70
123
  ```
71
124
 
72
125
  ```bash
73
- rg "new Error\(" apps/backend/src apps/backend/e2e --glob '!*spec.ts'
126
+ rg "new Error\(" <SRC_DIR> --glob '!*spec.ts' --glob '!*test.ts'
74
127
  ```
75
128
 
76
129
  ```bash
77
- rg "new DomainException\([^)]*$" -A3 apps/backend/src
130
+ rg "new DomainException\([^)]*$" -A3 <SRC_DIR>
78
131
  ```
79
132
 
80
133
  ```bash
81
- rg "DomainException\([^)]*[\u4e00-\u9fa5]" apps/backend/src apps/backend/e2e \
134
+ rg "DomainException\([^)]*[\u4e00-\u9fa5]" <SRC_DIR> \
82
135
  --glob '!*spec.ts' \
83
- --glob '!apps/backend/src/common/exceptions/**'
84
- ```
85
-
86
- 如果只想快速看生产代码,可优先改成:
87
-
88
- ```bash
89
- python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_audit.py" \
90
- --workspace "$PWD" \
91
- --scope src
92
- ```
93
-
94
- 如果只想看 E2E 存量问题,可改成:
95
-
96
- ```bash
97
- python "$CODEX_HOME/skills/error-handling-audit-fixer/scripts/error_handling_audit.py" \
98
- --workspace "$PWD" \
99
- --scope e2e
136
+ --glob '!*/common/exceptions/**'
100
137
  ```
101
138
 
102
139
  ## 修复准则
@@ -60,16 +60,34 @@ def parse_args() -> argparse.Namespace:
60
60
  parser = argparse.ArgumentParser(description="审计后端错误处理是否绕过 DomainException / ErrorCode 体系")
61
61
  parser.add_argument("--workspace", required=True, help="仓库根目录")
62
62
  parser.add_argument(
63
- "--include-glob",
63
+ "--src-dir",
64
+ action="append",
65
+ default=None,
66
+ help="生产代码目录(相对于 workspace,可重复),如 apps/backend/src",
67
+ )
68
+ parser.add_argument(
69
+ "--e2e-dir",
64
70
  action="append",
65
71
  default=None,
66
- help="附加扫描 glob,可重复传入",
72
+ help="测试代码目录(相对于 workspace,可重复),如 apps/backend/e2e",
67
73
  )
68
74
  parser.add_argument(
69
75
  "--scope",
70
76
  choices=["all", "src", "e2e"],
71
- default="all",
72
- help="预设扫描范围:all=src+e2e,src=仅生产代码,e2e=仅测试代码",
77
+ default="src",
78
+ help="扫描范围:src=仅生产代码(默认),e2e=仅测试代码,all=src+e2e",
79
+ )
80
+ parser.add_argument(
81
+ "--include-glob",
82
+ action="append",
83
+ default=None,
84
+ help="附加扫描 glob(覆盖自动推导),可重复传入",
85
+ )
86
+ parser.add_argument(
87
+ "--exclude-pattern",
88
+ action="append",
89
+ default=None,
90
+ help="额外排除路径片段(可重复),如 prisma/ scripts/",
73
91
  )
74
92
  parser.add_argument("--output-json", help="输出 JSON 文件路径")
75
93
  parser.add_argument(
@@ -95,18 +113,44 @@ def iter_files(workspace: Path, globs: Iterable[str]) -> list[Path]:
95
113
  return sorted(files)
96
114
 
97
115
 
98
- def should_skip(path: Path) -> bool:
116
+ SKIP_SUFFIXES = (
117
+ ".spec.ts",
118
+ ".test.ts",
119
+ ".e2e-spec.ts",
120
+ ".exception.ts",
121
+ ".mock.ts",
122
+ ".stub.ts",
123
+ ".fixture.ts",
124
+ )
125
+
126
+ SKIP_DIR_FRAGMENTS = (
127
+ "__tests__/",
128
+ "__test__/",
129
+ "test-utils/",
130
+ "testing/",
131
+ "fixtures/",
132
+ "/filters/",
133
+ "prisma/",
134
+ "scripts/",
135
+ )
136
+
137
+ SKIP_FILENAMES = ("main.ts",)
138
+
139
+
140
+ def should_skip(path: Path, extra_excludes: list[str] | None = None) -> bool:
99
141
  path_text = path.as_posix()
100
- if path_text.endswith(".spec.ts"):
101
- return True
102
- if path_text.endswith(".exception.ts"):
103
- return True
104
- if path_text == "apps/backend/src/main.ts":
105
- return True
106
- if path_text.startswith("apps/backend/src/common/filters/"):
107
- return True
108
- if path_text.startswith("apps/backend/src/common/exceptions/"):
142
+ if path.name in SKIP_FILENAMES:
109
143
  return True
144
+ for suffix in SKIP_SUFFIXES:
145
+ if path_text.endswith(suffix):
146
+ return True
147
+ for fragment in SKIP_DIR_FRAGMENTS:
148
+ if fragment in path_text:
149
+ return True
150
+ if extra_excludes:
151
+ for pattern in extra_excludes:
152
+ if pattern in path_text:
153
+ return True
110
154
  return False
111
155
 
112
156
 
@@ -114,12 +158,14 @@ def line_no(content: str, index: int) -> int:
114
158
  return content.count("\n", 0, index) + 1
115
159
 
116
160
 
117
- def default_globs_for_scope(scope: str) -> list[str]:
161
+ def build_globs(scope: str, src_dirs: list[str], e2e_dirs: list[str]) -> list[str]:
118
162
  if scope == "src":
119
- return ["apps/backend/src/**/*.ts"]
120
- if scope == "e2e":
121
- return ["apps/backend/e2e/**/*.ts"]
122
- return ["apps/backend/src/**/*.ts", "apps/backend/e2e/**/*.ts"]
163
+ dirs = src_dirs
164
+ elif scope == "e2e":
165
+ dirs = e2e_dirs
166
+ else:
167
+ dirs = src_dirs + e2e_dirs
168
+ return [f"{d}/**/*.ts" for d in dirs]
123
169
 
124
170
 
125
171
  def safe_read_text(path: Path) -> str:
@@ -284,30 +330,32 @@ def find_constructor_calls(content: str, constructor_name: str) -> list[tuple[in
284
330
  return matches
285
331
 
286
332
 
287
- def collect_foundation_status(workspace: Path) -> FoundationStatus:
288
- backend_root = workspace / "apps/backend/src"
289
- all_ts_files = sorted(backend_root.glob("**/*.ts"))
333
+ def collect_foundation_status(workspace: Path, src_dirs: list[str]) -> FoundationStatus:
290
334
  has_domain_exception = False
291
335
  has_error_code = False
292
336
  has_exception_filters = False
293
337
  has_module_exceptions_dir = False
294
338
  has_structured_request_id_signal = False
295
339
 
296
- for path in all_ts_files:
297
- path_text = path.as_posix()
298
- content = safe_read_text(path)
299
- if not content:
340
+ for src_dir in src_dirs:
341
+ root = workspace / src_dir
342
+ if not root.is_dir():
300
343
  continue
301
- if re.search(r"\bclass\s+DomainException\b", content):
302
- has_domain_exception = True
303
- if re.search(r"\b(enum|const)\s+ErrorCode\b", content) or re.search(r"\bErrorCode\.[A-Z0-9_]+\b", content):
304
- has_error_code = True
305
- if "/filters/" in path_text and re.search(r"ExceptionFilter|Catch\s*\(", content):
306
- has_exception_filters = True
307
- if "/exceptions/" in path_text and not path_text.startswith("apps/backend/src/common/exceptions/"):
308
- has_module_exceptions_dir = True
309
- if "requestId" in content and re.search(r"\b(args|code)\b", content):
310
- has_structured_request_id_signal = True
344
+ for path in sorted(root.glob("**/*.ts")):
345
+ path_text = path.as_posix()
346
+ content = safe_read_text(path)
347
+ if not content:
348
+ continue
349
+ if re.search(r"\bclass\s+DomainException\b", content):
350
+ has_domain_exception = True
351
+ if re.search(r"\b(enum|const)\s+ErrorCode\b", content) or re.search(r"\bErrorCode\.[A-Z0-9_]+\b", content):
352
+ has_error_code = True
353
+ if "/filters/" in path_text and re.search(r"ExceptionFilter|Catch\s*\(", content):
354
+ has_exception_filters = True
355
+ if "/exceptions/" in path_text and "common/exceptions" not in path_text:
356
+ has_module_exceptions_dir = True
357
+ if "requestId" in content and re.search(r"\b(args|code)\b", content):
358
+ has_structured_request_id_signal = True
311
359
 
312
360
  return FoundationStatus(
313
361
  has_domain_exception=has_domain_exception,
@@ -506,9 +554,19 @@ def print_report(foundations: FoundationStatus, findings: list[Finding]) -> None
506
554
  def main() -> int:
507
555
  args = parse_args()
508
556
  workspace = Path(args.workspace).resolve()
509
- globs = args.include_glob or default_globs_for_scope(args.scope)
510
- files = [path for path in iter_files(workspace, globs) if not should_skip(path.relative_to(workspace))]
511
- foundations = collect_foundation_status(workspace)
557
+
558
+ src_dirs: list[str] = args.src_dir or []
559
+ e2e_dirs: list[str] = args.e2e_dir or []
560
+ extra_excludes: list[str] | None = args.exclude_pattern
561
+
562
+ if not src_dirs and not e2e_dirs and not args.include_glob:
563
+ print("错误:必须通过 --src-dir 或 --e2e-dir 指定扫描目录,或通过 --include-glob 指定 glob 模式")
564
+ print("示例:--src-dir apps/backend/src --e2e-dir apps/backend/e2e")
565
+ return 1
566
+
567
+ globs = args.include_glob or build_globs(args.scope, src_dirs, e2e_dirs)
568
+ files = [path for path in iter_files(workspace, globs) if not should_skip(path.relative_to(workspace), extra_excludes)]
569
+ foundations = collect_foundation_status(workspace, src_dirs or e2e_dirs)
512
570
 
513
571
  findings: list[Finding] = []
514
572
  for path in files:
@@ -442,7 +442,49 @@ MSG
442
442
 
443
443
  若继续:`ROUND += 1`,回到 4.1。
444
444
 
445
- 若结束且 `CODE_CHANGED_SINCE_LAST_CHECK=true`:执行最终验证——按串行顺序跑 `dx lint` -> `dx build affected --dev` -> 关联测试,有错即停。有失败则修复并 commit/push,无失败则完成。
445
+ 若结束且 `CODE_CHANGED_SINCE_LAST_CHECK=true`:执行最终验证——按串行顺序跑 `dx lint` -> `dx build affected --dev` -> 关联测试,有错即停。有失败则修复并 commit/push,无失败则进入 4.7 发布验证总结。
446
+
447
+ 若结束且 `CODE_CHANGED_SINCE_LAST_CHECK=false`(本轮零问题直接通过):同样进入 4.7,基于最近一次验证结果发布总结。
448
+
449
+ ### 4.7 发布验证总结报告到 PR
450
+
451
+ 最终验证通过后,在 PR 上发布一条验证总结,让审查者一眼看清本次交付的质量门禁结果。
452
+
453
+ 收集并汇总以下信息:
454
+ - **审查轮数**:共经历了几轮审查修复循环
455
+ - **Lint 结果**:通过/失败(最终状态)
456
+ - **构建结果**:通过/失败(最终状态)
457
+ - **测试执行明细**:列出实际执行的每条测试命令及其结果(如 `dx test e2e backend apps/backend/e2e/ai-model/virtual-model.e2e-spec.ts` -> 通过),未执行测试的类别注明"无相关改动,跳过"
458
+ - **代码审查**:共发现多少问题、修复多少、拒绝多少
459
+
460
+ ```bash
461
+ gh pr comment <PR_NUMBER> --body-file - <<'MSG'
462
+ ## ✅ 验证总结
463
+
464
+ ### 门禁结果
465
+
466
+ | 步骤 | 状态 | 备注 |
467
+ |------|------|------|
468
+ | Lint (`dx lint`) | ✅ 通过 | |
469
+ | 构建 (`dx build affected --dev`) | ✅ 通过 | |
470
+ | 后端 E2E | ✅ 通过 | `dx test e2e backend <实际执行的文件/目录>` |
471
+ | 前端单测 | ⏭️ 跳过 | 无相关改动 |
472
+ | 管理端单测 | ⏭️ 跳过 | 无相关改动 |
473
+
474
+ ### 审查统计
475
+
476
+ - 审查轮数:N
477
+ - 发现问题:X 个(Critical: a / Major: b / Minor: c)
478
+ - 已修复:Y 个
479
+ - 拒绝修复:Z 个(附理由见上方修复报告)
480
+
481
+ ### 结论
482
+
483
+ 所有质量门禁通过,PR 可合并。
484
+ MSG
485
+ ```
486
+
487
+ > **注意**:表格中的"状态"和"备注"列必须基于实际执行结果填写,不能编造。若某步骤失败后经修复再次通过,标注"✅ 通过(修复后重跑)"。测试命令列必须写出实际执行的完整命令,不能用占位符。
446
488
 
447
489
  ---
448
490