@ranger1/dx 0.1.91 → 0.1.93

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.
Files changed (52) hide show
  1. package/README.md +2 -2
  2. package/lib/cli/help.js +1 -1
  3. package/lib/codex-initial.js +24 -211
  4. package/package.json +2 -2
  5. package/skills/backend-layering-audit-fixer/SKILL.md +180 -0
  6. package/{codex/skills → skills}/doctor/SKILL.md +2 -9
  7. package/{codex/skills → skills}/doctor/scripts/doctor.sh +2 -253
  8. package/skills/git-pr-ship/SKILL.md +481 -0
  9. package/skills/naming-audit-fixer/SKILL.md +149 -0
  10. package/skills/naming-audit-fixer/references/fix-guide.md +93 -0
  11. package/skills/naming-audit-fixer/scripts/audit_naming.py +534 -0
  12. package/codex/agents/fixer.toml +0 -37
  13. package/codex/agents/orchestrator.toml +0 -11
  14. package/codex/agents/reviewer.toml +0 -52
  15. package/codex/agents/spark.toml +0 -18
  16. package/codex/skills/pr-review-loop/SKILL.md +0 -209
  17. package/codex/skills/pr-review-loop/agents/openai.yaml +0 -4
  18. package/codex/skills/pr-review-loop/references/agents/pr-context.md +0 -73
  19. package/codex/skills/pr-review-loop/references/agents/pr-precheck.md +0 -161
  20. package/codex/skills/pr-review-loop/references/agents/pr-review-aggregate.md +0 -188
  21. package/codex/skills/pr-review-loop/references/skill-layout.md +0 -25
  22. package/codex/skills/pr-review-loop/scripts/gh_review_harvest.py +0 -292
  23. package/codex/skills/pr-review-loop/scripts/pr_context.py +0 -351
  24. package/codex/skills/pr-review-loop/scripts/pr_review_aggregate.py +0 -951
  25. package/codex/skills/pr-review-loop/scripts/test_pr_review_aggregate.py +0 -876
  26. package/codex/skills/pr-review-loop/scripts/test_validate_reviewer_prompts.py +0 -92
  27. package/codex/skills/pr-review-loop/scripts/validate_reviewer_prompts.py +0 -87
  28. /package/{codex/skills → skills}/doctor/agents/openai.yaml +0 -0
  29. /package/{codex/skills → skills}/e2e-audit-fixer/SKILL.md +0 -0
  30. /package/{codex/skills → skills}/e2e-audit-fixer/agents/openai.yaml +0 -0
  31. /package/{codex/skills → skills}/e2e-audit-fixer/scripts/e2e_e2e_audit.py +0 -0
  32. /package/{codex/skills → skills}/env-accessor-audit-fixer/SKILL.md +0 -0
  33. /package/{codex/skills → skills}/env-accessor-audit-fixer/agents/openai.yaml +0 -0
  34. /package/{codex/skills → skills}/env-accessor-audit-fixer/references/bootstrap-env-foundation.md +0 -0
  35. /package/{codex/skills → skills}/env-accessor-audit-fixer/scripts/env_accessor_audit.py +0 -0
  36. /package/{codex/skills → skills}/error-handling-audit-fixer/SKILL.md +0 -0
  37. /package/{codex/skills → skills}/error-handling-audit-fixer/agents/openai.yaml +0 -0
  38. /package/{codex/skills → skills}/error-handling-audit-fixer/references/error-handling-standard.md +0 -0
  39. /package/{codex/skills → skills}/error-handling-audit-fixer/references/foundation-bootstrap.md +0 -0
  40. /package/{codex/skills → skills}/error-handling-audit-fixer/scripts/error_handling_audit.py +0 -0
  41. /package/{codex/skills → skills}/gh-dependabot-cleanup/SKILL.md +0 -0
  42. /package/{codex/skills → skills}/gh-dependabot-cleanup/agents/openai.yaml +0 -0
  43. /package/{codex/skills → skills}/git-commit-and-pr/SKILL.md +0 -0
  44. /package/{codex/skills → skills}/git-commit-and-pr/agents/openai.yaml +0 -0
  45. /package/{codex/skills → skills}/git-release/SKILL.md +0 -0
  46. /package/{codex/skills → skills}/git-release/agents/openai.yaml +0 -0
  47. /package/{codex/skills → skills}/online-debug-guard/SKILL.md +0 -0
  48. /package/{codex/skills → skills}/online-debug-guard/agents/openai.yaml +0 -0
  49. /package/{codex/skills → skills}/pagination-dto-audit-fixer/SKILL.md +0 -0
  50. /package/{codex/skills → skills}/pagination-dto-audit-fixer/agents/openai.yaml +0 -0
  51. /package/{codex/skills → skills}/pagination-dto-audit-fixer/references/pagination-standard.md +0 -0
  52. /package/{codex/skills → skills}/pagination-dto-audit-fixer/scripts/pagination_dto_audit.py +0 -0
package/README.md CHANGED
@@ -148,8 +148,8 @@ target(端)不写死,由 `env-policy.jsonc.targets` 定义;`commands.jso
148
148
 
149
149
  技能入口与说明见:
150
150
 
151
- - `codex/skills/pr-review-loop/SKILL.md`
152
- - `codex/skills/pr-review-loop/references/agents/*.md`
151
+ - `skills/pr-review-loop/SKILL.md`
152
+ - `skills/pr-review-loop/references/agents/*.md`
153
153
 
154
154
  ### 工作流概览
155
155
 
package/lib/cli/help.js CHANGED
@@ -69,7 +69,7 @@ export function showHelp() {
69
69
  '',
70
70
  ' status 查看系统状态',
71
71
  '',
72
- ' initial 初始化 codex 模板到 ~/.codex(覆盖同名文件)',
72
+ ' initial 同步根目录 skills 到 ~/.codex/skills ~/.claude/skills(覆盖同名文件)',
73
73
  '',
74
74
  '选项:',
75
75
  ' --dev, --development 使用开发环境',
@@ -4,48 +4,7 @@ import os from 'node:os'
4
4
 
5
5
  import { logger } from './logger.js'
6
6
 
7
- const REQUIRED_CODEX_CONFIG = [
8
- {
9
- section: 'features',
10
- values: {
11
- multi_agent: 'true',
12
- },
13
- },
14
- {
15
- section: 'agents',
16
- values: {
17
- max_threads: '15',
18
- },
19
- },
20
- {
21
- section: 'agents.fixer',
22
- values: {
23
- description: '"bugfix 代理"',
24
- config_file: '"agents/fixer.toml"',
25
- },
26
- },
27
- {
28
- section: 'agents.orchestrator',
29
- values: {
30
- description: '"pr 修复流程编排代理"',
31
- config_file: '"agents/orchestrator.toml"',
32
- },
33
- },
34
- {
35
- section: 'agents.reviewer',
36
- values: {
37
- description: '"代码评审代理"',
38
- config_file: '"agents/reviewer.toml"',
39
- },
40
- },
41
- {
42
- section: 'agents.spark',
43
- values: {
44
- description: '"通用执行代理"',
45
- config_file: '"agents/spark.toml"',
46
- },
47
- },
48
- ]
7
+ const DEPRECATED_SKILL_DIRS = ['pr-review-loop', 'git-commit-and-pr']
49
8
 
50
9
  async function collectAllFiles(dir) {
51
10
  const out = []
@@ -68,6 +27,7 @@ async function collectAllFiles(dir) {
68
27
  continue
69
28
  }
70
29
  if (!entry.isFile()) continue
30
+
71
31
  const lowerName = entry.name.toLowerCase()
72
32
  if (lowerName.endsWith('.pyc') || lowerName.endsWith('.pyo') || lowerName.endsWith('.pyd')) continue
73
33
  out.push(full)
@@ -82,110 +42,6 @@ async function ensureDir(path) {
82
42
  await fs.mkdir(path, { recursive: true })
83
43
  }
84
44
 
85
- function escapeRegExp(input) {
86
- return String(input).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
87
- }
88
-
89
- function ensureTrailingNewline(text) {
90
- if (!text) return ''
91
- return text.endsWith('\n') ? text : `${text}\n`
92
- }
93
-
94
- function upsertTomlSection(content, { section, values }) {
95
- const header = `[${section}]`
96
- const sectionPattern = new RegExp(`^\\[${escapeRegExp(section)}\\]\\s*$`, 'm')
97
- const sectionHeaderMatch = content.match(sectionPattern)
98
- let nextContent = content
99
- let changedKeys = 0
100
- let createdSection = false
101
-
102
- if (!sectionHeaderMatch) {
103
- const blockLines = [header, ...Object.entries(values).map(([key, value]) => `${key} = ${value}`), '']
104
- nextContent = ensureTrailingNewline(content)
105
- if (nextContent.length > 0 && !nextContent.endsWith('\n\n')) {
106
- nextContent += '\n'
107
- }
108
- nextContent += `${blockLines.join('\n')}\n`
109
- return {
110
- content: nextContent,
111
- changedKeys: Object.keys(values).length,
112
- createdSection: true,
113
- }
114
- }
115
-
116
- const sectionStart = sectionHeaderMatch.index
117
- const sectionBodyStart = sectionStart + sectionHeaderMatch[0].length
118
- const remaining = content.slice(sectionBodyStart)
119
- const nextHeaderMatch = remaining.match(/\n(?=\[[^\]]+\]\s*$)/m)
120
- const sectionEnd =
121
- nextHeaderMatch && typeof nextHeaderMatch.index === 'number'
122
- ? sectionBodyStart + nextHeaderMatch.index + 1
123
- : content.length
124
-
125
- const beforeSection = content.slice(0, sectionStart)
126
- const originalSectionText = content.slice(sectionStart, sectionEnd)
127
- const trailing = content.slice(sectionEnd)
128
- const sectionLines = originalSectionText.split('\n')
129
-
130
- for (const [key, value] of Object.entries(values)) {
131
- const desiredLine = `${key} = ${value}`
132
- const keyPattern = new RegExp(`^${escapeRegExp(key)}\\s*=`, 'm')
133
- const lineIndex = sectionLines.findIndex(line => keyPattern.test(line.trim()))
134
-
135
- if (lineIndex === -1) {
136
- let insertIndex = sectionLines.length
137
- while (insertIndex > 1 && sectionLines[insertIndex - 1] === '') {
138
- insertIndex--
139
- }
140
- sectionLines.splice(insertIndex, 0, desiredLine)
141
- changedKeys++
142
- continue
143
- }
144
-
145
- if (sectionLines[lineIndex].trim() !== desiredLine) {
146
- sectionLines[lineIndex] = desiredLine
147
- changedKeys++
148
- }
149
- }
150
-
151
- const updatedSectionText = ensureTrailingNewline(sectionLines.join('\n'))
152
- nextContent = `${beforeSection}${updatedSectionText}${trailing}`
153
-
154
- return { content: nextContent, changedKeys, createdSection }
155
- }
156
-
157
- async function ensureCodexConfig({ codexDir }) {
158
- const configPath = join(codexDir, 'config.toml')
159
- let content = ''
160
-
161
- try {
162
- content = await fs.readFile(configPath, 'utf8')
163
- } catch (error) {
164
- if (error?.code !== 'ENOENT') throw error
165
- }
166
-
167
- let changedKeys = 0
168
- let createdSections = 0
169
- let nextContent = content
170
-
171
- for (const sectionConfig of REQUIRED_CODEX_CONFIG) {
172
- const result = upsertTomlSection(nextContent, sectionConfig)
173
- nextContent = result.content
174
- changedKeys += result.changedKeys
175
- if (result.createdSection) createdSections++
176
- }
177
-
178
- if (nextContent !== content || content === '') {
179
- await fs.writeFile(configPath, ensureTrailingNewline(nextContent), 'utf8')
180
- }
181
-
182
- return {
183
- configPath,
184
- changedKeys,
185
- createdSections,
186
- }
187
- }
188
-
189
45
  async function assertDirExists(path, label) {
190
46
  try {
191
47
  const st = await fs.stat(path)
@@ -198,35 +54,6 @@ async function assertDirExists(path, label) {
198
54
  }
199
55
  }
200
56
 
201
- async function copyTemplateTree({ srcDir, dstDir }) {
202
- const files = await collectTemplateFiles(srcDir)
203
- let mdCount = 0
204
- let pyCount = 0
205
- let jsonCount = 0
206
-
207
- for (const file of files) {
208
- const rel = relative(srcDir, file)
209
- const target = join(dstDir, rel)
210
- await ensureDir(dirname(target))
211
- await fs.copyFile(file, target)
212
-
213
- if (file.endsWith('.md')) {
214
- mdCount++
215
- } else if (file.endsWith('.py')) {
216
- pyCount++
217
- try {
218
- await fs.chmod(target, 0o755)
219
- } catch {
220
- // 忽略权限设置失败
221
- }
222
- } else if (file.endsWith('.json')) {
223
- jsonCount++
224
- }
225
- }
226
-
227
- return { total: files.length, md: mdCount, py: pyCount, json: jsonCount }
228
- }
229
-
230
57
  async function copyDirMerge({ srcDir, dstDir }) {
231
58
  const files = await collectAllFiles(srcDir)
232
59
 
@@ -240,22 +67,10 @@ async function copyDirMerge({ srcDir, dstDir }) {
240
67
  return { fileCount: files.length }
241
68
  }
242
69
 
243
- async function copySkillsDirectories({ srcSkillsDir, dstSkillsDir }) {
244
- const entries = await fs.readdir(srcSkillsDir, { withFileTypes: true })
245
- let copiedDirs = 0
246
- let copiedFiles = 0
247
-
248
- for (const entry of entries) {
249
- if (!entry.isDirectory()) continue
250
- const srcDir = join(srcSkillsDir, entry.name)
251
- const dstDir = join(dstSkillsDir, entry.name)
252
- await ensureDir(dstDir)
253
- const stats = await copyDirMerge({ srcDir, dstDir })
254
- copiedDirs++
255
- copiedFiles += stats.fileCount
70
+ async function removeDeprecatedSkillDirs(skillsDir) {
71
+ for (const dirName of DEPRECATED_SKILL_DIRS) {
72
+ await fs.rm(join(skillsDir, dirName), { recursive: true, force: true })
256
73
  }
257
-
258
- return { copiedDirs, copiedFiles }
259
74
  }
260
75
 
261
76
  export async function runCodexInitial(options = {}) {
@@ -263,26 +78,24 @@ export async function runCodexInitial(options = {}) {
263
78
  if (!packageRoot) throw new Error('runCodexInitial: 缺少 packageRoot')
264
79
 
265
80
  const homeDir = options.homeDir || os.homedir()
266
- const codexDir = join(homeDir, '.codex')
267
- const srcSkills = join(packageRoot, 'codex', 'skills')
268
- const dstSkills = join(codexDir, 'skills')
269
- const srcCodexAgents = join(packageRoot, 'codex', 'agents')
270
- const dstCodexAgents = join(codexDir, 'agents')
271
-
272
- await assertDirExists(srcSkills, '模板目录 codex/skills')
273
- await assertDirExists(srcCodexAgents, '模板目录 codex/agents')
274
-
275
- await ensureDir(dstSkills)
276
- await ensureDir(dstCodexAgents)
277
-
278
- const skillsStats = await copySkillsDirectories({ srcSkillsDir: srcSkills, dstSkillsDir: dstSkills })
279
- const codexAgentsStats = await copyDirMerge({ srcDir: srcCodexAgents, dstDir: dstCodexAgents })
280
- const configStats = await ensureCodexConfig({ codexDir })
81
+ const srcSkillsDir = join(packageRoot, 'skills')
82
+ const targets = [
83
+ { name: 'codex', dir: join(homeDir, '.codex', 'skills') },
84
+ { name: 'claude', dir: join(homeDir, '.claude', 'skills') },
85
+ ]
86
+
87
+ await assertDirExists(srcSkillsDir, '模板目录 skills')
88
+
89
+ const stats = []
90
+ for (const target of targets) {
91
+ await ensureDir(target.dir)
92
+ await removeDeprecatedSkillDirs(target.dir)
93
+ const copyStats = await copyDirMerge({ srcDir: srcSkillsDir, dstDir: target.dir })
94
+ stats.push({ ...target, ...copyStats })
95
+ }
281
96
 
282
- logger.success(`已初始化 Codex 模板到: ${codexDir}`)
283
- logger.info(`skills: ${skillsStats.copiedDirs} 个目录,覆盖复制 ${skillsStats.copiedFiles} 个文件 -> ${dstSkills}`)
284
- logger.info(`codex agents: 覆盖复制 ${codexAgentsStats.fileCount} 个文件 -> ${dstCodexAgents}`)
285
- logger.info(
286
- `config.toml: 修复 ${configStats.changedKeys} 个配置项,新增 ${configStats.createdSections} 个分组 -> ${configStats.configPath}`,
287
- )
97
+ logger.success('已初始化 skills 模板')
98
+ for (const target of stats) {
99
+ logger.info(`${target.name} skills: 覆盖复制 ${target.fileCount} 个文件 -> ${target.dir}`)
100
+ }
288
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.91",
3
+ "version": "0.1.93",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "files": [
19
19
  "bin/",
20
20
  "lib/",
21
- "codex/",
21
+ "skills/",
22
22
  "LICENSE",
23
23
  "README.md",
24
24
  "package.json"
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: backend-layering-audit-fixer
3
+ description: Use when backend、NestJS 分层架构或事务规范审查中,需要检查 Service 是否绕过 Repository 直接访问数据库、Controller 是否跨层注入 Repository、Service 是否伪装 Repository、事务装饰器是否匹配 afterCommit 使用、非 HTTP 场景事务模式是否正确、SSE 端点是否误加事务装饰器、以及是否存在直接 prisma.$transaction() 调用。
4
+ ---
5
+
6
+ # 后端三层架构与事务规范合规检查
7
+
8
+ ## 概览
9
+
10
+ 扫描后端代码是否遵守 **Controller → Service → Repository** 三层架构约束及事务规范。默认只输出审计结果和修复建议;用户明确要求时才自动修复。
11
+
12
+ 规则来源:`ruler/conventions.md` 第 4 节 "NestJS/Prisma 约定" + 第 5 节 "事务规范"。
13
+
14
+ ## 检查项
15
+
16
+ ### 分层架构
17
+
18
+ | 编号 | 违规类型 | 说明 |
19
+ |------|----------|------|
20
+ | L1 | Service 直接访问数据库 | Service 注入 `PrismaService` 并调用 `this.prisma.*` |
21
+ | L2 | Service 伪装为 Repository | Service 文件含 `getClient()` / `txHost.tx` 模式,实质是 Repository |
22
+ | L3 | Service 死依赖 | Service 注入 `PrismaService` 但从未使用 |
23
+ | L4 | Controller 跨层调用 | Controller 直接注入 Repository |
24
+
25
+ ### 事务规范
26
+
27
+ | 编号 | 违规类型 | 说明 |
28
+ |------|----------|------|
29
+ | T1 | afterCommit 未被排空 | Controller 用 `@Transactional()` 但调用链中存在 `txEvents.afterCommit()` 回调(应改用 `@TransactionalWithAfterCommit()`) |
30
+ | T2 | Service 违规传播类型 | Service 使用 `Propagation.Required` / `RequiresNew` / `Nested`(禁止 Service 自行创建事务) |
31
+ | T3 | 直接 `prisma.$transaction()` | 绕过 `TransactionHost` 抽象,直接调用 `prisma.$transaction()` |
32
+ | T4 | SSE/流式端点加事务装饰器 | `@Sse()` 或流式返回的方法上使用了 `@Transactional()` / `@TransactionalWithAfterCommit()`(会导致 afterCommit 提前排空) |
33
+ | T5 | 非 HTTP 场景事务模式错误 | Subscriber/Scheduler 使用了 `@Transactional()` 装饰器而非 `txHost.withTransaction()` 或 `txEvents.withAfterCommit()` |
34
+ | T6 | 非 HTTP 场景缺少 CLS 作用域 | Scheduler 调用 `txHost.withTransaction()` 前未用 `cls.run()` 创建 CLS 作用域 |
35
+
36
+ ## 审计命令
37
+
38
+ **分层架构扫描(并行执行):**
39
+
40
+ ```bash
41
+ # L1 + L2 + L3:Service 注入 PrismaService
42
+ rg "PrismaService" apps/backend/src/modules --glob '*.service.ts' -l
43
+
44
+ # L2:Service 含 Repository 模式
45
+ rg "getClient|txHost\.tx" apps/backend/src/modules --glob '*.service.ts' -l
46
+
47
+ # L4:Controller 注入 Repository
48
+ rg "Repository" apps/backend/src/modules --glob '*.controller.ts' -l
49
+ ```
50
+
51
+ **事务规范扫描(并行执行):**
52
+
53
+ ```bash
54
+ # T1:找出所有只用 @Transactional() 的 Controller 方法(排除 @TransactionalWithAfterCommit)
55
+ rg "@Transactional\(\)" apps/backend/src/modules --glob '*.controller.ts' -l
56
+
57
+ # T2:Service 违规传播类型
58
+ rg "Propagation\.(Required|RequiresNew|Nested)" apps/backend/src/modules --glob '*.service.ts' -l
59
+
60
+ # T3:直接 prisma.$transaction()
61
+ rg "prisma\.\$transaction" apps/backend/src/modules -l
62
+
63
+ # T4:SSE 端点是否带事务装饰器(需人工确认上下文)
64
+ rg "@Sse\(\)" apps/backend/src/modules --glob '*.controller.ts' -l
65
+
66
+ # T5:Subscriber/Scheduler 误用 @Transactional 装饰器
67
+ rg "@Transactional" apps/backend/src/modules --glob '*.subscriber.ts' --glob '*.task.ts' --glob '*.scheduler*.ts' -l
68
+
69
+ # T6:Scheduler 使用 txHost.withTransaction 但未包 cls.run
70
+ rg "txHost\.withTransaction" apps/backend/src/modules --glob '*.scheduler*.ts' --glob '*.task.ts' -l
71
+ ```
72
+
73
+ ## 执行流程
74
+
75
+ 1. 执行上述审计命令,收集命中文件列表
76
+ 2. **分层检查**:对每个命中文件,读取构造函数和使用处,判定违规类型:
77
+ - 注入了 `PrismaService` 且有 `this.prisma.*` 调用 → **L1**
78
+ - 注入了 `PrismaService`/`txHost` 且有 `getClient()` → **L2**
79
+ - 注入了 `PrismaService` 但无任何 `this.prisma.*` 调用 → **L3**
80
+ - Controller 文件 import/注入 Repository → **L4**
81
+ 3. **事务检查**:
82
+ - T1:对只用 `@Transactional()` 的 Controller,追踪其调用的 Service 方法,检查是否存在 `txEvents.afterCommit()` 调用
83
+ - T2:直接匹配即违规
84
+ - T3:直接匹配即违规
85
+ - T4:对 `@Sse()` 方法,检查同一方法上是否有事务装饰器
86
+ - T5:直接匹配即违规(Subscriber/Scheduler 不应用装饰器声明事务)
87
+ - T6:对命中文件,检查 `txHost.withTransaction()` 调用是否在 `cls.run()` 回调内部
88
+ 4. 输出审计报告(按模块分组,标注违规类型和行号)
89
+ 5. 仅当用户明确要求修复时,才执行修复
90
+
91
+ ## 修复策略
92
+
93
+ ### 分层架构
94
+
95
+ **L1:Service 直接访问数据库**
96
+ - 将数据库查询逻辑提取到对应 Repository(新建或复用已有)
97
+ - Service 改为注入 Repository 调用
98
+
99
+ **L2:Service 伪装为 Repository**
100
+ - 重命名文件为 `*.repository.ts`,类名改为 `*Repository`
101
+ - 更新所有引用(import、Module providers、注入处)
102
+ - 确认 Service 层消费者改为注入新 Repository
103
+
104
+ **L3:Service 死依赖**
105
+ - 移除构造函数中 `PrismaService` 注入
106
+ - 移除对应 import 语句
107
+ - 运行 lint 确认无残留
108
+
109
+ **L4:Controller 跨层调用**
110
+ - Controller 中的 Repository 调用下沉到 Service
111
+ - Controller 改为调用 Service 方法
112
+
113
+ ### 事务规范
114
+
115
+ **T1:afterCommit 未被排空**
116
+ - 将 Controller 方法的 `@Transactional()` 改为 `@TransactionalWithAfterCommit()`
117
+ - 或确认调用链中确实不存在 `afterCommit()` 回调(则无需修改)
118
+
119
+ **T2:Service 违规传播类型**
120
+ - 改为 `Propagation.Mandatory`(必须在事务中调用)或 `Propagation.Supports`(有事务则加入)
121
+ - 事务边界上移到 Controller 层
122
+
123
+ **T3:直接 `prisma.$transaction()`**
124
+ - 改用 `txHost.withTransaction()` 或由 Controller 层 `@Transactional()` 声明事务边界
125
+ - Repository 内通过 `txHost.tx` 自动参与事务
126
+
127
+ **T4:SSE/流式端点加事务装饰器**
128
+ - 移除事务装饰器
129
+ - 如需事务操作,在流式逻辑内部用 `cls.run()` + `txHost.withTransaction()` 局部处理
130
+
131
+ **T5:非 HTTP 场景误用 @Transactional 装饰器**
132
+ - Subscriber:改用 `txEvents.withAfterCommit()` 包裹处理逻辑
133
+ - Scheduler/Task:改用 `cls.run()` + `txHost.withTransaction()`
134
+
135
+ **T6:非 HTTP 场景缺少 CLS 作用域**
136
+ - 在 `txHost.withTransaction()` 外层包裹 `cls.run()`:
137
+ ```typescript
138
+ await this.cls.run(async () => {
139
+ await this.txHost.withTransaction(async () => { /* ... */ })
140
+ })
141
+ ```
142
+
143
+ ## 排除项
144
+
145
+ 以下场景不视为违规:
146
+ - `*.repository.ts` 文件中使用 `PrismaService` / `getClient()` / `txHost`(Repository 正常职责)
147
+ - `prisma/` 目录下的基础设施文件(`prisma.service.ts` 等)
148
+ - `common/` 目录下的事务基础设施(`TransactionEventsService`、`AfterCommitInterceptor` 等)
149
+ - `*.spec.ts` / `e2e/` 测试文件
150
+ - Subscriber / Scheduler 中通过 `txHost.withTransaction()` 管理事务(正确模式,但其中数据库查询仍应走 Repository)
151
+
152
+ ## 审计报告模板
153
+
154
+ ```
155
+ ## 后端分层架构与事务规范审计报告
156
+
157
+ ### 违规汇总
158
+
159
+ **分层架构:**
160
+ - L1(Service 直接访问 DB):N 处
161
+ - L2(Service 伪装 Repository):N 处
162
+ - L3(Service 死依赖):N 处
163
+ - L4(Controller 跨层调用):N 处
164
+
165
+ **事务规范:**
166
+ - T1(afterCommit 未被排空):N 处
167
+ - T2(Service 违规传播类型):N 处
168
+ - T3(直接 prisma.$transaction):N 处
169
+ - T4(SSE 端点加事务装饰器):N 处
170
+ - T5(非 HTTP 场景误用装饰器):N 处
171
+ - T6(非 HTTP 缺少 CLS 作用域):N 处
172
+
173
+ ### 详细列表
174
+
175
+ #### [模块名]
176
+ | 文件 | 违规类型 | 行号 | 说明 |
177
+ |------|----------|------|------|
178
+ | ... | L1 | 39 | `this.prisma.user.findMany()` |
179
+ | ... | T1 | 85 | `@Transactional()` 但调用链含 `afterCommit()` |
180
+ ```
@@ -52,18 +52,11 @@ bash "$CODEX_HOME/skills/doctor/scripts/doctor.sh" --max-rounds 3
52
52
 
53
53
  ## 脚本职责
54
54
 
55
- - 并行检测:`python3`、`python` 别名、`pnpm`、`dx`、`agent-browser`、`rg`、`multi_agent`、`~/.codex/config.toml` 关键配置。
55
+ - 并行检测:`python3`、`python` 别名、`pnpm`、`dx`、`agent-browser`、`rg`、`multi_agent`。
56
56
  - 自动修复:按平台选择安装器修复缺失项。
57
- - 自动修复:确保 `~/.codex/config.toml` 含以下目标值(缺失补齐、值不符覆盖):
58
- - `[features] multi_agent = true`
59
- - `[agents] max_threads = 15`
60
- - `[agents.fixer] description/config_file`
61
- - `[agents.orchestrator] description/config_file`
62
- - `[agents.reviewer] description/config_file`
63
- - `[agents.spark] description/config_file`
64
57
  - 强制执行:每轮都运行 `pnpm add -g @ranger1/dx@latest && dx initial`。
65
58
  - agent-browser:安装/升级并执行 Chromium 安装。
66
- - 结果输出:展示每项状态、版本、关键信息(含 `codex_config` 检测项);全部通过则退出 0,否则最多三轮后退出 1。
59
+ - 结果输出:展示每项状态、版本、关键信息;全部通过则退出 0,否则最多三轮后退出 1。
67
60
 
68
61
  ## 注意
69
62