@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.
package/lib/codex-initial.js
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
--
|
|
63
|
+
--src-dir apps/backend/src
|
|
21
64
|
```
|
|
22
65
|
|
|
23
|
-
|
|
66
|
+
多服务示例:
|
|
24
67
|
|
|
25
68
|
```bash
|
|
26
|
-
python "$
|
|
69
|
+
python "$SKILL_HOME/error-handling-audit-fixer/scripts/error_handling_audit.py" \
|
|
27
70
|
--workspace "$PWD" \
|
|
28
|
-
--
|
|
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.
|
|
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
|
-
|
|
114
|
+
以下 rg 命令用于手工复核,将 `<SRC_DIR>` 替换为步骤 0 探索到的实际路径:
|
|
62
115
|
|
|
63
116
|
```bash
|
|
64
117
|
rg "new (BadRequestException|UnauthorizedException|ForbiddenException|NotFoundException|HttpException|InternalServerErrorException)\(" \
|
|
65
|
-
|
|
66
|
-
--glob '!*spec.ts' \
|
|
118
|
+
<SRC_DIR> \
|
|
119
|
+
--glob '!*spec.ts' --glob '!*test.ts' \
|
|
67
120
|
--glob '!*exception.ts' \
|
|
68
|
-
--glob '
|
|
69
|
-
--glob '
|
|
121
|
+
--glob '!*/filters/**' \
|
|
122
|
+
--glob '!*main.ts'
|
|
70
123
|
```
|
|
71
124
|
|
|
72
125
|
```bash
|
|
73
|
-
rg "new Error\("
|
|
126
|
+
rg "new Error\(" <SRC_DIR> --glob '!*spec.ts' --glob '!*test.ts'
|
|
74
127
|
```
|
|
75
128
|
|
|
76
129
|
```bash
|
|
77
|
-
rg "new DomainException\([^)]*$" -A3
|
|
130
|
+
rg "new DomainException\([^)]*$" -A3 <SRC_DIR>
|
|
78
131
|
```
|
|
79
132
|
|
|
80
133
|
```bash
|
|
81
|
-
rg "DomainException\([^)]*[\u4e00-\u9fa5]"
|
|
134
|
+
rg "DomainException\([^)]*[\u4e00-\u9fa5]" <SRC_DIR> \
|
|
82
135
|
--glob '!*spec.ts' \
|
|
83
|
-
--glob '
|
|
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
|
-
"--
|
|
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="
|
|
72
|
+
help="测试代码目录(相对于 workspace,可重复),如 apps/backend/e2e",
|
|
67
73
|
)
|
|
68
74
|
parser.add_argument(
|
|
69
75
|
"--scope",
|
|
70
76
|
choices=["all", "src", "e2e"],
|
|
71
|
-
default="
|
|
72
|
-
help="
|
|
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
|
-
|
|
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
|
|
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
|
|
161
|
+
def build_globs(scope: str, src_dirs: list[str], e2e_dirs: list[str]) -> list[str]:
|
|
118
162
|
if scope == "src":
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|