@dommaker/harness 1.5.0 → 1.6.0

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 (208) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +0 -2
  3. package/dist/cli/commands/definitions.d.ts.map +1 -1
  4. package/dist/cli/commands/definitions.js +1 -32
  5. package/dist/cli/commands/definitions.js.map +1 -1
  6. package/dist/cli/commands/passes-gate.d.ts +0 -2
  7. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  8. package/dist/cli/commands/passes-gate.js +0 -1
  9. package/dist/cli/commands/passes-gate.js.map +1 -1
  10. package/dist/cli/commands/performance.d.ts +0 -4
  11. package/dist/cli/commands/performance.d.ts.map +1 -1
  12. package/dist/cli/commands/performance.js +10 -20
  13. package/dist/cli/commands/performance.js.map +1 -1
  14. package/dist/cli/commands/report.d.ts +1 -1
  15. package/dist/cli/commands/report.d.ts.map +1 -1
  16. package/dist/cli/commands/report.js +2 -55
  17. package/dist/cli/commands/report.js.map +1 -1
  18. package/dist/cli/commands/status.d.ts.map +1 -1
  19. package/dist/cli/commands/status.js +4 -8
  20. package/dist/cli/commands/status.js.map +1 -1
  21. package/dist/context/index.d.ts +2 -5
  22. package/dist/context/index.d.ts.map +1 -1
  23. package/dist/context/index.js +2 -8
  24. package/dist/context/index.js.map +1 -1
  25. package/dist/context/token-budget.d.ts +0 -29
  26. package/dist/context/token-budget.d.ts.map +1 -1
  27. package/dist/context/token-budget.js +1 -65
  28. package/dist/context/token-budget.js.map +1 -1
  29. package/dist/context/types.d.ts +0 -1
  30. package/dist/context/types.d.ts.map +1 -1
  31. package/dist/context/types.js +0 -9
  32. package/dist/context/types.js.map +1 -1
  33. package/dist/core/constraints/checker.d.ts +14 -0
  34. package/dist/core/constraints/checker.d.ts.map +1 -1
  35. package/dist/core/constraints/checker.js +22 -2
  36. package/dist/core/constraints/checker.js.map +1 -1
  37. package/dist/core/constraints/git-evidence.d.ts +6 -3
  38. package/dist/core/constraints/git-evidence.d.ts.map +1 -1
  39. package/dist/core/constraints/git-evidence.js +6 -3
  40. package/dist/core/constraints/git-evidence.js.map +1 -1
  41. package/dist/core/index.d.ts +1 -2
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +1 -9
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/spec/validator.d.ts +2 -0
  46. package/dist/core/spec/validator.d.ts.map +1 -1
  47. package/dist/core/spec/validator.js +9 -11
  48. package/dist/core/spec/validator.js.map +1 -1
  49. package/dist/core/validators/index.d.ts +1 -1
  50. package/dist/core/validators/index.d.ts.map +1 -1
  51. package/dist/core/validators/passes-gate.d.ts +1 -15
  52. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  53. package/dist/core/validators/passes-gate.js +0 -125
  54. package/dist/core/validators/passes-gate.js.map +1 -1
  55. package/dist/failure/index.d.ts +0 -1
  56. package/dist/failure/index.d.ts.map +1 -1
  57. package/dist/failure/index.js +1 -7
  58. package/dist/failure/index.js.map +1 -1
  59. package/dist/gates/definitions.d.ts.map +1 -1
  60. package/dist/gates/definitions.js +0 -4
  61. package/dist/gates/definitions.js.map +1 -1
  62. package/dist/gates/performance.d.ts +7 -22
  63. package/dist/gates/performance.d.ts.map +1 -1
  64. package/dist/gates/performance.js +7 -89
  65. package/dist/gates/performance.js.map +1 -1
  66. package/dist/gates/types.d.ts +2 -8
  67. package/dist/gates/types.d.ts.map +1 -1
  68. package/dist/index.d.ts +6 -8
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -22
  71. package/dist/index.js.map +1 -1
  72. package/dist/knowledge/index.d.ts +0 -1
  73. package/dist/knowledge/index.d.ts.map +1 -1
  74. package/dist/knowledge/index.js +1 -3
  75. package/dist/knowledge/index.js.map +1 -1
  76. package/dist/monitoring/trace-analyzer.d.ts +25 -10
  77. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  78. package/dist/monitoring/trace-analyzer.js +147 -116
  79. package/dist/monitoring/trace-analyzer.js.map +1 -1
  80. package/dist/types/passes-gate.d.ts +0 -19
  81. package/dist/types/passes-gate.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/__tests__/passes-gate-check.test.ts +20 -4
  84. package/src/__tests__/passes-gate.test.ts +5 -99
  85. package/src/__tests__/performance-gate.test.ts +3 -11
  86. package/src/__tests__/public-exports.test.ts +46 -16
  87. package/src/__tests__/trace-analyzer.test.ts +52 -2
  88. package/src/cli/commands/CONTEXT.md +3 -2
  89. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
  90. package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
  91. package/src/cli/commands/__tests__/performance.test.ts +5 -16
  92. package/src/cli/commands/__tests__/registry.test.ts +46 -3
  93. package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
  94. package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
  95. package/src/cli/commands/__tests__/report.test.ts +0 -20
  96. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
  97. package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
  98. package/src/cli/commands/__tests__/status.test.ts +66 -148
  99. package/src/cli/commands/definitions.ts +1 -32
  100. package/src/cli/commands/passes-gate.ts +0 -3
  101. package/src/cli/commands/performance.ts +13 -27
  102. package/src/cli/commands/report.ts +3 -61
  103. package/src/cli/commands/status.ts +5 -10
  104. package/src/context/CONTEXT.md +2 -3
  105. package/src/context/__tests__/token-budget.test.ts +1 -81
  106. package/src/context/index.ts +2 -7
  107. package/src/context/token-budget.ts +0 -73
  108. package/src/context/types.ts +0 -9
  109. package/src/core/CONTEXT.md +5 -6
  110. package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
  111. package/src/core/constraints/checker.ts +33 -2
  112. package/src/core/constraints/git-evidence.ts +6 -3
  113. package/src/core/index.ts +0 -11
  114. package/src/core/spec/__tests__/validator.test.ts +24 -0
  115. package/src/core/spec/validator.ts +9 -10
  116. package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
  117. package/src/core/validators/index.ts +0 -1
  118. package/src/core/validators/passes-gate.ts +1 -149
  119. package/src/failure/CONTEXT.md +8 -6
  120. package/src/failure/index.ts +0 -10
  121. package/src/gates/CONTEXT.md +3 -2
  122. package/src/gates/__tests__/performance.test.ts +6 -74
  123. package/src/gates/definitions.ts +0 -4
  124. package/src/gates/performance.ts +9 -111
  125. package/src/gates/types.ts +2 -8
  126. package/src/index.ts +1 -36
  127. package/src/knowledge/CONTEXT.md +1 -2
  128. package/src/knowledge/index.ts +0 -1
  129. package/src/monitoring/CONTEXT.md +2 -2
  130. package/src/monitoring/trace-analyzer.ts +160 -126
  131. package/src/types/passes-gate.ts +0 -26
  132. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
  133. package/dist/cli/commands/analyze-sessions.d.ts +0 -20
  134. package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
  135. package/dist/cli/commands/analyze-sessions.js +0 -290
  136. package/dist/cli/commands/analyze-sessions.js.map +0 -1
  137. package/dist/cli/commands/update-user-model.d.ts +0 -30
  138. package/dist/cli/commands/update-user-model.d.ts.map +0 -1
  139. package/dist/cli/commands/update-user-model.js +0 -406
  140. package/dist/cli/commands/update-user-model.js.map +0 -1
  141. package/dist/cli/session-mining/corrections.d.ts +0 -17
  142. package/dist/cli/session-mining/corrections.d.ts.map +0 -1
  143. package/dist/cli/session-mining/corrections.js +0 -58
  144. package/dist/cli/session-mining/corrections.js.map +0 -1
  145. package/dist/cli/session-mining/index.d.ts +0 -13
  146. package/dist/cli/session-mining/index.d.ts.map +0 -1
  147. package/dist/cli/session-mining/index.js +0 -29
  148. package/dist/cli/session-mining/index.js.map +0 -1
  149. package/dist/cli/session-mining/text.d.ts +0 -31
  150. package/dist/cli/session-mining/text.d.ts.map +0 -1
  151. package/dist/cli/session-mining/text.js +0 -126
  152. package/dist/cli/session-mining/text.js.map +0 -1
  153. package/dist/cli/session-mining/transcript.d.ts +0 -34
  154. package/dist/cli/session-mining/transcript.d.ts.map +0 -1
  155. package/dist/cli/session-mining/transcript.js +0 -130
  156. package/dist/cli/session-mining/transcript.js.map +0 -1
  157. package/dist/context/compaction.d.ts +0 -57
  158. package/dist/context/compaction.d.ts.map +0 -1
  159. package/dist/context/compaction.js +0 -165
  160. package/dist/context/compaction.js.map +0 -1
  161. package/dist/core/session/clean-state.d.ts +0 -17
  162. package/dist/core/session/clean-state.d.ts.map +0 -1
  163. package/dist/core/session/clean-state.js +0 -192
  164. package/dist/core/session/clean-state.js.map +0 -1
  165. package/dist/core/session/index.d.ts +0 -6
  166. package/dist/core/session/index.d.ts.map +0 -1
  167. package/dist/core/session/index.js +0 -15
  168. package/dist/core/session/index.js.map +0 -1
  169. package/dist/core/session/startup.d.ts +0 -47
  170. package/dist/core/session/startup.d.ts.map +0 -1
  171. package/dist/core/session/startup.js +0 -263
  172. package/dist/core/session/startup.js.map +0 -1
  173. package/dist/failure/constraint-handler.d.ts +0 -83
  174. package/dist/failure/constraint-handler.d.ts.map +0 -1
  175. package/dist/failure/constraint-handler.js +0 -138
  176. package/dist/failure/constraint-handler.js.map +0 -1
  177. package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
  178. package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
  179. package/dist/knowledge/lifecycle-hooks.js +0 -143
  180. package/dist/knowledge/lifecycle-hooks.js.map +0 -1
  181. package/dist/types/session.d.ts +0 -113
  182. package/dist/types/session.d.ts.map +0 -1
  183. package/dist/types/session.js +0 -6
  184. package/dist/types/session.js.map +0 -1
  185. package/src/__tests__/clean-state-extra.test.ts +0 -301
  186. package/src/__tests__/clean-state.test.ts +0 -169
  187. package/src/__tests__/constraint-handler.test.ts +0 -132
  188. package/src/__tests__/passes-gate-extra.test.ts +0 -93
  189. package/src/__tests__/session-startup.test.ts +0 -334
  190. package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
  191. package/src/cli/commands/__tests__/update-user-model.test.ts +0 -268
  192. package/src/cli/commands/analyze-sessions.ts +0 -335
  193. package/src/cli/commands/update-user-model.ts +0 -476
  194. package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
  195. package/src/cli/session-mining/corrections.ts +0 -56
  196. package/src/cli/session-mining/index.ts +0 -13
  197. package/src/cli/session-mining/text.ts +0 -117
  198. package/src/cli/session-mining/transcript.ts +0 -125
  199. package/src/context/__tests__/compaction.test.ts +0 -169
  200. package/src/context/compaction.ts +0 -201
  201. package/src/core/session/__tests__/startup.test.ts +0 -281
  202. package/src/core/session/clean-state.ts +0 -178
  203. package/src/core/session/index.ts +0 -15
  204. package/src/core/session/startup.ts +0 -253
  205. package/src/failure/constraint-handler.ts +0 -171
  206. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
  207. package/src/knowledge/lifecycle-hooks.ts +0 -202
  208. package/src/types/session.ts +0 -132
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dommaker/harness",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "通用工程约束框架 - 铁律系统、检查点验证、测试门控",
5
5
  "keywords": [
6
6
  "harness",
@@ -95,12 +95,28 @@ describe('PassesGate.check()', () => {
95
95
  });
96
96
 
97
97
  /**
98
- * AC-007:保留原有 setPasses() 方法(向后兼容)
98
+ * AC-007(ADR-0022 改写):零消费者方法已从公共面删除
99
+ *
100
+ * 原用例断言「保留 setPasses() 向后兼容」——兼容对象经 harness+studio 双仓
101
+ * 核实不存在,ADR-0022 明确推翻该 AC。`@ts-expect-error` 在编译期钉住删除,
102
+ * 运行期断言同时钉住形状(方法不存在于原型)。
99
103
  */
100
- it('AC-007: should keep setPasses() method for backward compatibility', () => {
104
+ it('AC-007: setPasses()/getTestResult()/checkTestFileChanges() are removed from the public surface', () => {
101
105
  const gate = new PassesGate();
102
- expect(gate.setPasses).toBeDefined();
103
- expect(typeof gate.setPasses).toBe('function');
106
+
107
+ // @ts-expect-error setPasses 已随 ADR-0022 删除
108
+ expect(gate.setPasses).toBeUndefined();
109
+ // @ts-expect-error getTestResult 已随 ADR-0022 删除
110
+ expect(gate.getTestResult).toBeUndefined();
111
+ // @ts-expect-error checkTestFileChanges 已随 ADR-0022 删除
112
+ expect(gate.checkTestFileChanges).toBeUndefined();
113
+ expect(PassesGate.prototype).not.toHaveProperty('setPasses');
114
+ expect(PassesGate.prototype).not.toHaveProperty('getTestResult');
115
+ expect(PassesGate.prototype).not.toHaveProperty('checkTestFileChanges');
116
+
117
+ // 保留面:真实消费的方法不动
118
+ expect(typeof gate.check).toBe('function');
119
+ expect(typeof gate.runTests).toBe('function');
104
120
  });
105
121
 
106
122
  /**
@@ -33,34 +33,6 @@ describe('PassesGate', () => {
33
33
  }
34
34
  });
35
35
 
36
- describe('setPasses', () => {
37
- it('设置为 false 应该直接允许', async () => {
38
- const result = await gate.setPasses('task-1', false, tempDir);
39
-
40
- expect(result.allowed).toBe(true);
41
- expect(result.testResult?.passed).toBe(false);
42
- });
43
-
44
- it('设置为 true 应该运行测试', async () => {
45
- const result = await gate.setPasses('task-2', true, tempDir, {
46
- id: 'task-2',
47
- });
48
-
49
- // 测试命令是 echo "1 passed",所以应该通过
50
- expect(result.allowed).toBe(true);
51
- expect(result.testResult?.command).toContain('npm test');
52
- });
53
-
54
- it('禁用时应该允许所有设置', async () => {
55
- const disabledGate = createPassesGate({ enabled: false });
56
-
57
- const result = await disabledGate.setPasses('task-3', true, tempDir);
58
-
59
- expect(result.allowed).toBe(true);
60
- expect(result.attempts).toBe(0);
61
- });
62
- });
63
-
64
36
  describe('runTests', () => {
65
37
  // 执行根由调用方传入(#95):tempDir 的 package.json 测试命令是 echo,不再跑真实套件
66
38
  it('应该返回测试结果', async () => {
@@ -72,15 +44,6 @@ describe('PassesGate', () => {
72
44
  });
73
45
  });
74
46
 
75
- describe('detectTestCommand', () => {
76
- it('应该检测 npm test', async () => {
77
- // package.json 有 test script
78
- const result = await gate.setPasses('task-4', true, tempDir);
79
-
80
- expect(result.testResult?.command).toContain('npm');
81
- });
82
- });
83
-
84
47
  // 探测正本行为钉住(架构评审 A2:CLI/core 双版收口后的统一语义,
85
48
  // 此前 e2e 优先级、echo 排除、兜底均无测试钉住)
86
49
  describe('detectTestCommand 正本(统一探测顺序)', () => {
@@ -153,70 +116,13 @@ describe('PassesGate', () => {
153
116
  expect(await detectTestCommand(emptyDir)).toBeUndefined();
154
117
  });
155
118
 
156
- it('探测失败时 runTests 与 setPasses 双路径 fail-closed', async () => {
119
+ it('探测失败时 runTests fail-closed', async () => {
157
120
  const emptyDir = join(detectDir, 'fail-closed');
158
121
  mkdirSync(emptyDir, { recursive: true });
159
122
 
160
123
  const runResult = await gate.runTests(emptyDir);
161
124
  expect(runResult.passed).toBe(false);
162
125
  expect(runResult.message).toBe('未检测到测试命令');
163
-
164
- const setResult = await gate.setPasses('task-nocmd', true, emptyDir);
165
- expect(setResult.allowed).toBe(false);
166
- expect(setResult.testResult?.passed).toBe(false);
167
- expect(setResult.testResult?.output).toBe('未检测到测试命令');
168
- });
169
- });
170
-
171
- describe('checkTestFileChanges', () => {
172
- it('应该检测测试文件变更', async () => {
173
- // 无 git 变更时返回空数组
174
- const changes = await gate.checkTestFileChanges(tempDir);
175
-
176
- expect(Array.isArray(changes)).toBe(true);
177
- });
178
- });
179
-
180
- describe('getTestResult', () => {
181
- it('应该返回已存储的测试结果', async () => {
182
- const result = await gate.setPasses('stored-task', true, tempDir);
183
-
184
- if (result.allowed && result.testResult) {
185
- const stored = gate.getTestResult('stored-task');
186
- expect(stored).toBeDefined();
187
- expect(stored?.command).toContain('npm');
188
- }
189
- });
190
-
191
- it('未存储任务应返回 undefined', () => {
192
- const stored = gate.getTestResult('nonexistent-task');
193
- expect(stored).toBeUndefined();
194
- });
195
- });
196
-
197
- describe('测试失败重试', () => {
198
- it('测试失败后应重试', async () => {
199
- const retryDir = join(process.cwd(), 'temp-test-retry');
200
- mkdirSync(retryDir, { recursive: true });
201
-
202
- writeFileSync(join(retryDir, 'package.json'), JSON.stringify({
203
- name: 'retry-project',
204
- scripts: { test: 'exit 1' }, // 总是失败
205
- }));
206
-
207
- const retryGate = createPassesGate({
208
- enabled: true,
209
- maxRetries: 1,
210
- retryDelay: 100,
211
- });
212
-
213
- const result = await retryGate.setPasses('retry-task', true, retryDir);
214
-
215
- expect(result.allowed).toBe(false);
216
- expect(result.attempts).toBeGreaterThan(1);
217
- expect(result.error).toBeDefined();
218
-
219
- rmSync(retryDir, { recursive: true, force: true });
220
126
  });
221
127
  });
222
128
 
@@ -237,13 +143,13 @@ describe('PassesGate', () => {
237
143
  },
238
144
  });
239
145
 
240
- it('setPasses 结果不带 coverage 字段', async () => {
146
+ it('runTests 结果不带 coverage 字段', async () => {
241
147
  const covGate = createPassesGate({ enabled: true, requireEvidence: false });
242
148
 
243
- const result = await covGate.setPasses('cov-task', true, covDir);
149
+ const result = await covGate.runTests(covDir);
244
150
 
245
- expect(result.testResult?.passed).toBe(true);
246
- expect(result.testResult).not.toHaveProperty('coverage');
151
+ expect(result.passed).toBe(true);
152
+ expect(result).not.toHaveProperty('coverage');
247
153
  });
248
154
 
249
155
  it('TaskTestResult 公开类型面不再接受 coverage 字段', () => {
@@ -38,7 +38,7 @@ describe('PerformanceGate', () => {
38
38
  const gate = new PerformanceGate({
39
39
  enabled: true,
40
40
  thresholds: {
41
- maxResponseTime: 1000,
41
+ maxBundleSize: 1000,
42
42
  },
43
43
  });
44
44
 
@@ -55,20 +55,12 @@ describe('PerformanceGate', () => {
55
55
  it('应该支持自定义阈值', () => {
56
56
  const gate = new PerformanceGate({
57
57
  thresholds: {
58
- maxResponseTime: 500,
59
- maxMemoryUsage: 1000,
58
+ minCoverage: 80,
59
+ maxBundleSize: 1000,
60
60
  },
61
61
  });
62
62
 
63
63
  expect(gate).toBeDefined();
64
64
  });
65
-
66
- it('应该支持基准测试命令', () => {
67
- const gate = new PerformanceGate({
68
- benchmarkCommand: 'npm run benchmark',
69
- });
70
-
71
- expect(gate).toBeDefined();
72
- });
73
65
  });
74
66
  });
@@ -5,29 +5,28 @@
5
5
  * 改动 src/index.ts 导出清单时同步更新本文件的 EXPECTED_RUNTIME_EXPORTS,
6
6
  * diff 即 PR 评审材料。
7
7
  *
8
- * 注意:Object.keys 只能看到运行时(值)导出;纯类型导出由
9
- * src/index.ts 显式清单在编译期把关,不在本测试断言范围内。
8
+ * 两个面两道闸:运行时(值)导出由 Object.keys 逐字冻结;纯类型导出在运行时不可见,
9
+ * 由下方「包根类型面」闸把关——编译期 `@ts-expect-error` 钉已删类型 + 源形状钉整条 barrel 链。
10
+ * 类型面曾有此空缺时 `PassesGateResult` 漏删未被抓到(review A2),钉子据此补上。
10
11
  */
11
12
 
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+
12
16
  const EXPECTED_RUNTIME_EXPORTS = [
13
- 'AdaptiveTokenBudget',
14
17
  'AgentLifecycle',
15
18
  'CONSTRAINTS_END_MARKER',
16
19
  'CONSTRAINTS_START_MARKER',
17
20
  'CSOValidator',
18
21
  'CheckCache',
19
22
  'CheckpointValidator',
20
- 'CleanStateManager',
21
23
  'ColdStartImporter',
22
24
  'CommandGate',
23
25
  'ConstraintViolationError',
24
- 'ConstraintViolationHandler',
25
26
  'ContextTracker',
26
27
  'ContractGate',
27
28
  'DEFAULT_CLASSIFICATION_RULES',
28
- 'DEFAULT_CODE_CHECKPOINTS',
29
29
  'DEFAULT_COMMAND_BLACKLIST',
30
- 'DEFAULT_COMPACTION_CONFIG',
31
30
  'DEFAULT_DECAY_CONFIG',
32
31
  'DEFAULT_FAILURE_LOG_FILE',
33
32
  'DEFAULT_LEVEL_MAPPING',
@@ -49,10 +48,8 @@ const EXPECTED_RUNTIME_EXPORTS = [
49
48
  'KnowledgeIngest',
50
49
  'KnowledgeInjector',
51
50
  'KnowledgeLifecycle',
52
- 'KnowledgeLifecycleHooks',
53
51
  'KnowledgeLinter',
54
52
  'KnowledgeQuery',
55
- 'MINIMAL_CHECKPOINTS',
56
53
  'PHASE_SUBJECT_RE',
57
54
  'PROMPTS',
58
55
  'PassesGate',
@@ -60,9 +57,7 @@ const EXPECTED_RUNTIME_EXPORTS = [
60
57
  'ReferenceTracker',
61
58
  'ReviewGate',
62
59
  'SecurityGate',
63
- 'SessionCompaction',
64
60
  'SessionManager',
65
- 'SessionStartup',
66
61
  'SpecAcceptanceGate',
67
62
  'SpecValidator',
68
63
  'TESTED_BY_RE',
@@ -83,7 +78,6 @@ const EXPECTED_RUNTIME_EXPORTS = [
83
78
  'configureTraceCollector',
84
79
  'createAnalyzer',
85
80
  'createCheckerGate',
86
- 'createCleanStateManager',
87
81
  'createCommandGate',
88
82
  'createContractGate',
89
83
  'createErrorClassifier',
@@ -92,12 +86,8 @@ const EXPECTED_RUNTIME_EXPORTS = [
92
86
  'createPerformanceGate',
93
87
  'createReviewGate',
94
88
  'createSecurityGate',
95
- 'createSessionStartup',
96
89
  'createSpecAcceptanceGate',
97
90
  'decisionFromResult',
98
- 'executeWithBlock',
99
- 'executeWithCollect',
100
- 'executeWithSafeBoolean',
101
91
  'extractCodeStructure',
102
92
  'findConstraintsByTrigger',
103
93
  'getAllConstraints',
@@ -139,3 +129,43 @@ describe('公共导出清单(ADR-0003)', () => {
139
129
  expect(Object.keys(mod).sort()).toEqual([...EXPECTED_RUNTIME_EXPORTS].sort());
140
130
  });
141
131
  });
132
+
133
+ /**
134
+ * 包根类型面闸(review A2)
135
+ *
136
+ * 上一闸的 `Object.keys(mod)` 只看得到运行时(值)导出,类型面历史上无对等闸——
137
+ * `PassesGateResult`(已删 `setPasses` 的返回形状)正是这样从 #125 的「关联类型导出随迁」
138
+ * 里漏掉、经四级 barrel 存活为公开类型。手法同 #125 的 AC-007:编译期 `@ts-expect-error`
139
+ * 钉住删除 + 源形状钉住整条链,活类型正钉防删多。
140
+ */
141
+ describe('包根类型面(ADR-0022 关联类型随迁)', () => {
142
+ const BARREL_CHAIN = [
143
+ '../types/passes-gate.ts',
144
+ '../core/validators/index.ts',
145
+ '../core/index.ts',
146
+ '../index.ts',
147
+ ];
148
+
149
+ it('PassesGateResult 在整条 barrel 链四级都已消失', () => {
150
+ for (const rel of BARREL_CHAIN) {
151
+ const source = fs.readFileSync(path.join(__dirname, rel), 'utf-8');
152
+ expect({ file: rel, mentions: /PassesGateResult/.test(source) }).toEqual({
153
+ file: rel,
154
+ mentions: false,
155
+ });
156
+ }
157
+ });
158
+
159
+ it('PassesGateResult 不再是包根可导入类型(编译期钉)', () => {
160
+ // @ts-expect-error 该类型是已删 setPasses 的返回形状,随 ADR-0022 关联类型口径删除
161
+ const removedType: import('../index').PassesGateResult | undefined = undefined;
162
+ expect(removedType).toBeUndefined();
163
+ });
164
+
165
+ it('活类型仍可经包根导入(防止删多)', () => {
166
+ const liveCheck: import('../index').PassesGateCheckResult = { allowed: true };
167
+ const liveTask: import('../index').TaskTestResult = { passed: true, command: 'npm test' };
168
+ expect(liveCheck.allowed).toBe(true);
169
+ expect(liveTask.command).toBe('npm test');
170
+ });
171
+ });
@@ -3,11 +3,15 @@
3
3
  */
4
4
 
5
5
  import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
6
- import { TraceAnalyzer } from '../monitoring/trace-analyzer';
6
+ import {
7
+ TraceAnalyzer,
8
+ summarizeTraces,
9
+ detectTraceAnomalies,
10
+ } from '../monitoring/trace-analyzer';
7
11
  import { TraceCollector } from '../monitoring/traces';
8
12
  import * as fs from 'fs';
9
13
  import * as path from 'path';
10
- import type { ExecutionTrace } from '../types/trace';
14
+ import type { ExecutionTrace, TraceSummary } from '../types/trace';
11
15
 
12
16
  describe('TraceAnalyzer', () => {
13
17
  const tempDir = path.join(process.cwd(), 'temp-test-analyzer');
@@ -127,4 +131,50 @@ describe('TraceAnalyzer', () => {
127
131
  expect(customAnalyzer).toBeDefined();
128
132
  });
129
133
  });
134
+
135
+ // ADR-0020:summarize / detectAnomalies 的判定本体住在模块级纯函数上,
136
+ // 类壳只是转发。下面两枚用例直调模块级函数,钉住这个 interface——
137
+ // 经类壳的既有覆盖(上文)删不掉它,反之它也不依赖任何 collector/文件 IO。
138
+ describe('模块级纯函数', () => {
139
+ it('summarizeTraces 不构造 collector 即可汇总,skip 不进 pass/fail 分母', () => {
140
+ const traces: ExecutionTrace[] = [
141
+ { constraintId: 'pure', level: 'iron_law', timestamp: 1000, result: 'pass' },
142
+ { constraintId: 'pure', level: 'iron_law', timestamp: 2000, result: 'fail' },
143
+ { constraintId: 'pure', level: 'iron_law', timestamp: 3000, result: 'skip' },
144
+ ];
145
+
146
+ const summaries = summarizeTraces(traces);
147
+
148
+ expect(summaries).toHaveLength(1);
149
+ const summary = summaries[0];
150
+ expect(summary.constraintId).toBe('pure');
151
+ expect(summary.totalChecks).toBe(3);
152
+ expect(summary.skipCount).toBe(1);
153
+ expect(summary.passRate).toBeCloseTo(0.5);
154
+ expect(summary.failRate).toBeCloseTo(0.5);
155
+ });
156
+
157
+ it('detectTraceAnomalies 的阈值经参数传入,默认与自定义各判一次', () => {
158
+ // failRate 6/14 ≈ 0.43 且趋势上升:默认阈值 0.5 不触发,0.3 触发
159
+ const summary: TraceSummary = {
160
+ constraintId: 'param_threshold',
161
+ level: 'iron_law',
162
+ timeRange: { start: 1000, end: 2400 },
163
+ totalChecks: 14,
164
+ passCount: 8,
165
+ failCount: 6,
166
+ ignoreCount: 0,
167
+ passRate: 8 / 14,
168
+ failRate: 6 / 14,
169
+ recentTrend: 'rising',
170
+ };
171
+
172
+ expect(detectTraceAnomalies([summary])).toEqual([]);
173
+
174
+ const anomalies = detectTraceAnomalies([summary], { thresholds: { failRate: 0.3 } });
175
+ const rising = anomalies.find(a => a.type === 'rising_fail_rate');
176
+ expect(rising?.constraintId).toBe('param_threshold');
177
+ expect(rising?.data.threshold).toBe(0.3);
178
+ });
179
+ });
130
180
  });
@@ -1,7 +1,7 @@
1
1
  # cli/commands/
2
2
 
3
3
  ## 职责
4
- CLI 子命令实现:23 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布/会话分析等。
4
+ CLI 子命令实现:21 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布等。
5
5
 
6
6
  H5(#44)起:
7
7
  - **命令定义即注册**:`definitions.ts` 的 `COMMAND_DEFINITIONS` 是命令形状(名称/别名/位置参数/选项/子命令/实现引用)的单一来源;bin/harness.js 遍历本表注册表驱动生成,不再手写 commander 命令块,不再有 index.ts barrel 两处同步(R6)
@@ -10,7 +10,7 @@ H5(#44)起:
10
10
  ## 核心导出
11
11
  - `COMMAND_DEFINITIONS`(definitions.ts)— 全部非门禁命令定义(纯数据模块、零闭包,禁止 import 命令实现;ADR-0010)
12
12
  - 命令契约(`src/cli/command-contract.ts`,上层目录)— `CommandResult` / `CommandKind` / `CommandIO` / `processIO` / `captureIO` / `lastJsonOutput` / `log` / `logError`
13
- - 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release / analyze-sessions / update-user-model / constraints / spec-baseline-check
13
+ - 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / posteval-plan / release / constraints / spec-baseline-check
14
14
  - 6 门禁命令实现在 `acceptance` / `command` / `contract` / `performance` / `review` / `security`(其 CLI 元数据在 `src/gates/definitions.ts`,形状同为 `CommandDefinition`,ADR-0007)
15
15
 
16
16
  `constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康 + 注入漂移,`--export` 脱敏)、`constraints retire`(交互选择 + 人确认退役;带 id 直达需显式 `--yes`(#24 人确认闸门),无 `--yes` 报错 + 非零退出码且不落盘;内置落 config.yml retired 元数据,custom 落 custom-constraints.yml 条目 retired 段(studio#82 D6 一处真相)+ KnowledgeStore 沉淀 + 治理注入段同步)。
@@ -29,6 +29,7 @@ H5(#44)起:
29
29
  - **definitions.ts 是纯数据模块(ADR-0010 后零闭包)**:禁止 import 任何命令实现/运行时依赖(保 --help/--version 懒加载);子命令别名是数据(条目 `aliases: string[]`,不复印整块),实参编组(缺参闸门/强转/兜底)归各命令模块的具名导出(如 `knowledgeSearchCommand`/`coverageCheck`);实现引用可解析性由 `__tests__/registry.test.ts` 构建/测试期断言
30
30
  - 命令选项类型命名规范:XxxOptions
31
31
  - **本层是上行数据的注入方(harness#88)**:core 对 cli/gates/monitoring 零值导入,故 `check`/`report` 自己 `new ConstraintChecker(getTraceCollector())`(组合根接线 trace 记录器;用 `constraintChecker`/`getInstance()` 拿到的默认实例不写 trace),`sync-docs` 把 `COMMAND_DEFINITIONS` + `GATE_DEFINITIONS` 组装成 `CapabilityDefinitionSource` 注入 `capabilities-parser` 供能力清单计数
32
+ - **数据已在手就不再构造带 IO 副作用的实例(ADR-0020)**:`status` 按 projectPath 直读出 traces 后,直调 `monitoring/trace-analyzer` 的模块级纯函数 `summarizeTraces()`/`detectTraceAnomalies()`,不再 `new TraceCollector({traceFile})` + `new TraceAnalyzer(c)`——collector 构造函数带 mkdir 副作用,而这条路径上它的读方法从未被调用,实例纯属构造仪式。机器可检:`__tests__/status-analysis-wiring.test.ts`(源形状闸:命令体不得出现 `new TraceCollector`/`new TraceAnalyzer`,也不 import `monitoring/traces`)
32
33
  - **projectPath 只在入口兜底一次,并传到每个 IO/执行点(harness#95)**:`-p/--project-path` 的 cwd 兜底唯一落点是命令模块入口(`const projectPath = options.projectPath || process.cwd()`);往下每层只能收参数,**禁止再取一次 `process.cwd()`、也禁止用相对路径默认值**——两者都让 `-p` 半失效:执行/读写位置悄悄回到调用方 cwd,而 acceptance 的「无 tasks.yml 即跳过」本身就是 `passed:true`,于是失效表现为假绿。落地形状:执行/IO 根用**必传形参**表达(`PassesGate.runTests(workDir)`),门禁内的相对子路径锚到 projectPath:`ContractGate` 一直是 `path.join(context.projectPath, contractPath)`,`SpecAcceptanceGate` 的 tasksPath 随 #95 对齐成 `path.resolve(projectPath, 相对值)`(绝对值原样,缺省 `<projectPath>/tasks.yml`)
33
34
  - 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()` 兜底形状、下游层(= src 减 cli)cwd 站点**逐行**冻结(#98,豁免文件内新增行/行变形同样失败)、相对路径默认值冻结(#98 收紧:`xxxPath: '相对值'` 对象字面量之外,参数默认值形/模板字面量/双引号形同罪,扫描域 = 整个下游层);豁免逐条带理由,站点消失却不删条目同样失败
34
35
  - 行为可检:`__tests__/project-path-anchoring.test.ts` 前提统一 **cwd ≠ projectPath**、不 mock 任何 IO,断言测试真在 B 执行、证据落 `B/.harness/evidence/`、acceptance 读 `B/tasks.yml`。「根已传入却又取 cwd」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
@@ -0,0 +1,70 @@
1
+ /**
2
+ * passes-gate 重试选项死旗帜闸(code-review A1)
3
+ *
4
+ * 重试实现原住 `setPasses`,随 ADR-0022 删除后 `PassesGateConfig.maxRetries` /
5
+ * `retryDelay` 整条装配链只剩写入方、全仓无读取方——`harness passes-gate
6
+ * --max-retries N` 是恒不生效选项。ADR-0018 删 performance 幽灵旗帜的判据
7
+ * (只执法有真实现的维度)据此收口。
8
+ *
9
+ * 手法同 #125 的 AC-007:编译期 `@ts-expect-error` 钉字段不存在 + 运行期钉定义表与源形状。
10
+ * `src/agents/types.ts` 的 `maxRetries` 是 AgentLifecycle 自有字段(`lifecycle.ts` 真读取),
11
+ * 不在本闸范围内。
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import { COMMAND_DEFINITIONS } from '../definitions';
17
+ import type { PassesGateConfig } from '../../../types/passes-gate';
18
+ import type { PassesGateOptions } from '../passes-gate';
19
+
20
+ const passesGateCommand = COMMAND_DEFINITIONS.find(d => d.command === 'passes-gate');
21
+ const cliSource = fs.readFileSync(path.join(__dirname, '..', 'passes-gate.ts'), 'utf-8');
22
+ const validatorSource = fs.readFileSync(
23
+ path.join(__dirname, '..', '..', '..', 'core', 'validators', 'passes-gate.ts'),
24
+ 'utf-8',
25
+ );
26
+
27
+ describe('passes-gate 重试选项已删除(A1)', () => {
28
+ it('定义表已注册 passes-gate(前提)', () => {
29
+ expect(passesGateCommand).toBeDefined();
30
+ });
31
+
32
+ it('passes-gate 旗帜面逐字冻结,不含 --max-retries', () => {
33
+ expect((passesGateCommand?.options || []).map(o => o.flags)).toEqual([
34
+ '-t, --test-command <command>',
35
+ '-p, --project-path <path>',
36
+ '--allow-partial',
37
+ '--coverage',
38
+ '--coverage-threshold <n>',
39
+ ]);
40
+ });
41
+
42
+ it('命令定义表任意层级都不含 retry 旗帜(防换个命令位复活)', () => {
43
+ expect(JSON.stringify(COMMAND_DEFINITIONS)).not.toMatch(/retry/i);
44
+ });
45
+
46
+ it('CLI 装配链与 validator 默认配置都不再写重试字段', () => {
47
+ expect(cliSource).not.toMatch(/maxRetries|retryDelay/);
48
+ expect(validatorSource).not.toMatch(/maxRetries|retryDelay/);
49
+ });
50
+
51
+ it('PassesGateConfig / PassesGateOptions 的重试字段已从类型面消失(编译期钉)', () => {
52
+ // @ts-expect-error maxRetries 只写不读,随 A1 从 PassesGateConfig 删除
53
+ const removedMaxRetries: PassesGateConfig = { maxRetries: 2 };
54
+ // @ts-expect-error retryDelay 只写不读,随 A1 从 PassesGateConfig 删除
55
+ const removedRetryDelay: PassesGateConfig = { retryDelay: 1000 };
56
+ // @ts-expect-error CLI 侧同名字段随旗帜与装配一并删除
57
+ const removedOption: PassesGateOptions = { maxRetries: 2 };
58
+ expect([removedMaxRetries, removedRetryDelay, removedOption]).toHaveLength(3);
59
+ });
60
+
61
+ it('活字段仍可赋值(防止删多)', () => {
62
+ const live: PassesGateConfig = {
63
+ enabled: true,
64
+ testCommand: 'npm test',
65
+ requireEvidence: false,
66
+ allowPartialPass: true,
67
+ };
68
+ expect(Object.keys(live)).toHaveLength(4);
69
+ });
70
+ });
@@ -130,9 +130,13 @@ describe('passes-gate command', () => {
130
130
  }));
131
131
 
132
132
  await runPassesGate({ testCommand: 'pytest' }, io);
133
- expect(MockPassesGate).toHaveBeenCalledWith(
134
- expect.objectContaining({ testCommand: 'pytest' })
135
- );
133
+ // 精确形状(A1):装配面只剩四个活字段,只写不读的重试字段已随旗帜一并删除
134
+ expect(MockPassesGate).toHaveBeenCalledWith({
135
+ enabled: true,
136
+ testCommand: 'pytest',
137
+ requireEvidence: false,
138
+ allowPartialPass: false,
139
+ });
136
140
  });
137
141
  });
138
142
 
@@ -35,7 +35,7 @@ describe('performance command', () => {
35
35
  passed: true,
36
36
  message: 'ok',
37
37
  details: {
38
- metrics: { coverage: 85.5, bundleSize: 204800, benchmarkTime: 150 },
38
+ metrics: { coverage: 85.5, bundleSize: 204800 },
39
39
  },
40
40
  });
41
41
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
@@ -76,7 +76,7 @@ describe('performance command', () => {
76
76
  });
77
77
  });
78
78
 
79
- it('should convert bundleThreshold from KB to bytes', async () => {
79
+ it('bundleThreshold 直传 KB maxBundleSize(不再按字节错位换算)', async () => {
80
80
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
81
81
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
82
82
 
@@ -84,23 +84,12 @@ describe('performance command', () => {
84
84
 
85
85
  expect(MockGate).toHaveBeenCalledWith(
86
86
  expect.objectContaining({
87
- thresholds: expect.objectContaining({ bundleSize: 500 * 1024 }),
87
+ thresholds: expect.objectContaining({ maxBundleSize: 500 }),
88
88
  }),
89
89
  );
90
90
  });
91
91
 
92
- it('should convert benchmarkTimeout from seconds to ms', async () => {
93
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
94
- MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
95
-
96
- await performance({ benchmarkTimeout: 30 }, io);
97
-
98
- expect(MockGate).toHaveBeenCalledWith(
99
- expect.objectContaining({ benchmarkTimeout: 30000 }),
100
- );
101
- });
102
-
103
- it('should set coverage threshold when both flags provided', async () => {
92
+ it('coverage 旗帜对齐 minCoverage 字段(原 coverage 错位键名恒不生效)', async () => {
104
93
  const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'ok' });
105
94
  MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
106
95
 
@@ -108,7 +97,7 @@ describe('performance command', () => {
108
97
 
109
98
  expect(MockGate).toHaveBeenCalledWith(
110
99
  expect.objectContaining({
111
- thresholds: expect.objectContaining({ coverage: 90 }),
100
+ thresholds: expect.objectContaining({ minCoverage: 90 }),
112
101
  }),
113
102
  );
114
103
  });
@@ -4,7 +4,7 @@
4
4
  * - 定义表内每个实现引用(module+export)都必须可解析(构建/测试期断言,
5
5
  * 取代 commands barrel 的编译期保护——R6 消灭手工同步后的闭环兜底)
6
6
  * - definitions 是纯数据模块:require 它不加载任何命令实现(保 --help/--version 懒加载)
7
- * - 命令面与 CAPABILITIES.md 的 24 顶层命令一致(防误删回归)
7
+ * - 命令面与 CAPABILITIES.md 的 21 顶层命令一致(防误删回归)
8
8
  * - bin/harness.js 端到端冒烟(dist 存在时):--version/--help 零命令实现模块,
9
9
  * 单命令执行只加载该命令模块(O2 per-command 懒加载)
10
10
  */
@@ -35,7 +35,7 @@ function collectRefs(defs: CommandDefinition[]): CommandImplRef[] {
35
35
  const EXPECTED_TOP_LEVEL_COMMANDS = [
36
36
  'check', 'validate', 'passes-gate', 'init', 'report', 'status', 'spec',
37
37
  'sync-docs', 'knowledge', 'sdd', 'failure', 'posteval-plan',
38
- 'update-user-model', 'release', 'analyze-sessions', 'constraints',
38
+ 'release', 'constraints',
39
39
  'spec-baseline-check',
40
40
  // 6 门禁命令(GATE_DEFINITIONS 驱动)
41
41
  'acceptance', 'command', 'contract', 'performance', 'review', 'security',
@@ -88,7 +88,7 @@ describe('命令注册表闭环', () => {
88
88
  }
89
89
  });
90
90
 
91
- it('顶层命令面与预期 24 命令一致(含 6 门禁)', () => {
91
+ it('顶层命令面与预期 21 命令一致(含 6 门禁)', () => {
92
92
  const names = [
93
93
  ...COMMAND_DEFINITIONS.map(d => d.command.split(' ')[0]),
94
94
  ...GATE_DEFINITIONS.map(d => d.cli.command.split(' ')[0]),
@@ -97,6 +97,49 @@ describe('命令注册表闭环', () => {
97
97
  expect(names.sort()).toEqual([...EXPECTED_TOP_LEVEL_COMMANDS].sort());
98
98
  });
99
99
 
100
+ /**
101
+ * 文档手抄面根因闸(review A5)
102
+ *
103
+ * `CLAUDE.md` 的「N CLI subcommands (…)」与 `cli/commands/CONTEXT.md` 的命令清单/计数
104
+ * 都是注册表实面的人工抄本,无闸时必然漂移:#122 机械 −2 后 CLAUDE.md 仍列着早已按
105
+ * ADR-0009 删除的 `doc-freshness-check`,声明 22 而实面 21。三向钉死——计数等于条数、
106
+ * 无幽灵条目、无漏列条目。
107
+ */
108
+ it('CLAUDE.md 与 cli/commands/CONTEXT.md 的命令抄本与定义表实面双向一致', () => {
109
+ const actual = [
110
+ ...COMMAND_DEFINITIONS.map(d => d.command.split(' ')[0]),
111
+ ...GATE_DEFINITIONS.map(d => d.cli.command.split(' ')[0]),
112
+ ].sort();
113
+
114
+ const claudeLine = fs
115
+ .readFileSync(path.join(repoRoot, 'CLAUDE.md'), 'utf-8')
116
+ .split('\n')
117
+ .find(line => line.includes('| `src/cli/commands/` |'));
118
+ expect(claudeLine).toBeDefined();
119
+ const claudeMatch = (claudeLine || '').match(/(\d+) CLI subcommands \(([^)]*)\)/);
120
+ expect(claudeMatch).toBeDefined();
121
+ const claudeNames = claudeMatch![2]
122
+ .split(',')
123
+ .map(name => name.trim())
124
+ .filter(Boolean)
125
+ .sort();
126
+ expect(Number(claudeMatch![1])).toBe(actual.length);
127
+ expect(claudeNames).toEqual(actual);
128
+
129
+ const contextDoc = fs.readFileSync(path.join(repoCommandsDir, 'CONTEXT.md'), 'utf-8');
130
+ const contextCount = contextDoc.match(/(\d+) 个顶层命令/);
131
+ expect(contextCount).toBeDefined();
132
+ expect(Number(contextCount![1])).toBe(actual.length);
133
+ const contextNames = [
134
+ ...(contextDoc.match(/各命令文件:([^\n]+)/)?.[1] || '').split('/'),
135
+ ...(contextDoc.match(/门禁命令实现在 ([^(]+)(/)?.[1] || '').split('/'),
136
+ ]
137
+ .map(name => name.replace(/`/g, '').trim())
138
+ .filter(Boolean)
139
+ .sort();
140
+ expect(contextNames).toEqual(actual);
141
+ });
142
+
100
143
  it('命令定义无重复注册名', () => {
101
144
  const names = COMMAND_DEFINITIONS.map(d => d.command);
102
145
  expect(new Set(names).size).toBe(names.length);