@dommaker/harness 1.4.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 (258) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +0 -2
  3. package/dist/cli/commands/check.d.ts.map +1 -1
  4. package/dist/cli/commands/check.js +28 -0
  5. package/dist/cli/commands/check.js.map +1 -1
  6. package/dist/cli/commands/definitions.d.ts.map +1 -1
  7. package/dist/cli/commands/definitions.js +1 -39
  8. package/dist/cli/commands/definitions.js.map +1 -1
  9. package/dist/cli/commands/knowledge.d.ts +0 -19
  10. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  11. package/dist/cli/commands/knowledge.js +0 -105
  12. package/dist/cli/commands/knowledge.js.map +1 -1
  13. package/dist/cli/commands/passes-gate.d.ts +0 -2
  14. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  15. package/dist/cli/commands/passes-gate.js +0 -1
  16. package/dist/cli/commands/passes-gate.js.map +1 -1
  17. package/dist/cli/commands/performance.d.ts +0 -4
  18. package/dist/cli/commands/performance.d.ts.map +1 -1
  19. package/dist/cli/commands/performance.js +10 -20
  20. package/dist/cli/commands/performance.js.map +1 -1
  21. package/dist/cli/commands/report.d.ts +1 -1
  22. package/dist/cli/commands/report.d.ts.map +1 -1
  23. package/dist/cli/commands/report.js +2 -55
  24. package/dist/cli/commands/report.js.map +1 -1
  25. package/dist/cli/commands/status.d.ts.map +1 -1
  26. package/dist/cli/commands/status.js +4 -8
  27. package/dist/cli/commands/status.js.map +1 -1
  28. package/dist/context/index.d.ts +2 -5
  29. package/dist/context/index.d.ts.map +1 -1
  30. package/dist/context/index.js +2 -8
  31. package/dist/context/index.js.map +1 -1
  32. package/dist/context/token-budget.d.ts +0 -29
  33. package/dist/context/token-budget.d.ts.map +1 -1
  34. package/dist/context/token-budget.js +1 -65
  35. package/dist/context/token-budget.js.map +1 -1
  36. package/dist/context/types.d.ts +0 -1
  37. package/dist/context/types.d.ts.map +1 -1
  38. package/dist/context/types.js +0 -9
  39. package/dist/context/types.js.map +1 -1
  40. package/dist/core/constraints/checker.d.ts +14 -0
  41. package/dist/core/constraints/checker.d.ts.map +1 -1
  42. package/dist/core/constraints/checker.js +27 -5
  43. package/dist/core/constraints/checker.js.map +1 -1
  44. package/dist/core/constraints/checkers/capability-sync.d.ts +17 -6
  45. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  46. package/dist/core/constraints/checkers/capability-sync.js +48 -19
  47. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  48. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  49. package/dist/core/constraints/checkers/docs-freshness.js +15 -6
  50. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  51. package/dist/core/constraints/checkers/index.d.ts +2 -2
  52. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  53. package/dist/core/constraints/checkers/index.js +2 -1
  54. package/dist/core/constraints/checkers/index.js.map +1 -1
  55. package/dist/core/constraints/checkers/types.d.ts +41 -2
  56. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  57. package/dist/core/constraints/checkers/types.js +33 -0
  58. package/dist/core/constraints/checkers/types.js.map +1 -1
  59. package/dist/core/constraints/git-evidence.d.ts +6 -3
  60. package/dist/core/constraints/git-evidence.d.ts.map +1 -1
  61. package/dist/core/constraints/git-evidence.js +6 -3
  62. package/dist/core/constraints/git-evidence.js.map +1 -1
  63. package/dist/core/index.d.ts +1 -2
  64. package/dist/core/index.d.ts.map +1 -1
  65. package/dist/core/index.js +1 -9
  66. package/dist/core/index.js.map +1 -1
  67. package/dist/core/spec/validator.d.ts +2 -0
  68. package/dist/core/spec/validator.d.ts.map +1 -1
  69. package/dist/core/spec/validator.js +9 -11
  70. package/dist/core/spec/validator.js.map +1 -1
  71. package/dist/core/validators/index.d.ts +1 -1
  72. package/dist/core/validators/index.d.ts.map +1 -1
  73. package/dist/core/validators/passes-gate.d.ts +1 -15
  74. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  75. package/dist/core/validators/passes-gate.js +0 -125
  76. package/dist/core/validators/passes-gate.js.map +1 -1
  77. package/dist/failure/index.d.ts +0 -1
  78. package/dist/failure/index.d.ts.map +1 -1
  79. package/dist/failure/index.js +1 -7
  80. package/dist/failure/index.js.map +1 -1
  81. package/dist/gates/checker-gate.d.ts +2 -1
  82. package/dist/gates/checker-gate.d.ts.map +1 -1
  83. package/dist/gates/checker-gate.js +10 -7
  84. package/dist/gates/checker-gate.js.map +1 -1
  85. package/dist/gates/definitions.d.ts.map +1 -1
  86. package/dist/gates/definitions.js +0 -4
  87. package/dist/gates/definitions.js.map +1 -1
  88. package/dist/gates/performance.d.ts +7 -22
  89. package/dist/gates/performance.d.ts.map +1 -1
  90. package/dist/gates/performance.js +7 -89
  91. package/dist/gates/performance.js.map +1 -1
  92. package/dist/gates/types.d.ts +2 -8
  93. package/dist/gates/types.d.ts.map +1 -1
  94. package/dist/index.d.ts +6 -8
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +4 -22
  97. package/dist/index.js.map +1 -1
  98. package/dist/knowledge/index.d.ts +0 -1
  99. package/dist/knowledge/index.d.ts.map +1 -1
  100. package/dist/knowledge/index.js +1 -3
  101. package/dist/knowledge/index.js.map +1 -1
  102. package/dist/monitoring/trace-analyzer.d.ts +25 -10
  103. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  104. package/dist/monitoring/trace-analyzer.js +147 -116
  105. package/dist/monitoring/trace-analyzer.js.map +1 -1
  106. package/dist/types/constraint.d.ts +8 -0
  107. package/dist/types/constraint.d.ts.map +1 -1
  108. package/dist/types/constraint.js +6 -1
  109. package/dist/types/constraint.js.map +1 -1
  110. package/dist/types/passes-gate.d.ts +0 -19
  111. package/dist/types/passes-gate.d.ts.map +1 -1
  112. package/dist/types/trace.d.ts +9 -0
  113. package/dist/types/trace.d.ts.map +1 -1
  114. package/package.json +1 -1
  115. package/src/CONTEXT.md +1 -0
  116. package/src/__tests__/passes-gate-check.test.ts +20 -4
  117. package/src/__tests__/passes-gate.test.ts +18 -119
  118. package/src/__tests__/performance-gate.test.ts +3 -11
  119. package/src/__tests__/public-exports.test.ts +46 -16
  120. package/src/__tests__/trace-analyzer.test.ts +52 -2
  121. package/src/cli/commands/CONTEXT.md +5 -4
  122. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +6 -0
  123. package/src/cli/commands/__tests__/check.test.ts +74 -0
  124. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
  125. package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
  126. package/src/cli/commands/__tests__/performance.test.ts +5 -16
  127. package/src/cli/commands/__tests__/project-path-convention.test.ts +85 -23
  128. package/src/cli/commands/__tests__/registry.test.ts +52 -3
  129. package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
  130. package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
  131. package/src/cli/commands/__tests__/report.test.ts +0 -20
  132. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
  133. package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
  134. package/src/cli/commands/__tests__/status.test.ts +66 -148
  135. package/src/cli/commands/check.ts +39 -1
  136. package/src/cli/commands/definitions.ts +1 -39
  137. package/src/cli/commands/knowledge.ts +0 -119
  138. package/src/cli/commands/passes-gate.ts +0 -3
  139. package/src/cli/commands/performance.ts +13 -27
  140. package/src/cli/commands/report.ts +3 -61
  141. package/src/cli/commands/status.ts +5 -10
  142. package/src/context/CONTEXT.md +2 -3
  143. package/src/context/__tests__/token-budget.test.ts +1 -81
  144. package/src/context/index.ts +2 -7
  145. package/src/context/token-budget.ts +0 -73
  146. package/src/context/types.ts +0 -9
  147. package/src/core/CONTEXT.md +6 -6
  148. package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
  149. package/src/core/constraints/checker.ts +41 -6
  150. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +90 -31
  151. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  152. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +18 -9
  153. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +12 -4
  154. package/src/core/constraints/checkers/capability-sync.ts +56 -18
  155. package/src/core/constraints/checkers/docs-freshness.ts +18 -5
  156. package/src/core/constraints/checkers/index.ts +9 -2
  157. package/src/core/constraints/checkers/types.ts +58 -2
  158. package/src/core/constraints/git-evidence.ts +6 -3
  159. package/src/core/index.ts +0 -11
  160. package/src/core/spec/__tests__/validator.test.ts +24 -0
  161. package/src/core/spec/validator.ts +9 -10
  162. package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
  163. package/src/core/validators/index.ts +0 -1
  164. package/src/core/validators/passes-gate.ts +1 -149
  165. package/src/failure/CONTEXT.md +8 -6
  166. package/src/failure/index.ts +0 -10
  167. package/src/gates/CONTEXT.md +4 -3
  168. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  169. package/src/gates/__tests__/performance.test.ts +6 -74
  170. package/src/gates/checker-gate.ts +11 -15
  171. package/src/gates/definitions.ts +0 -4
  172. package/src/gates/performance.ts +9 -111
  173. package/src/gates/types.ts +2 -8
  174. package/src/index.ts +1 -36
  175. package/src/knowledge/CONTEXT.md +1 -2
  176. package/src/knowledge/index.ts +0 -1
  177. package/src/monitoring/CONTEXT.md +2 -2
  178. package/src/monitoring/trace-analyzer.ts +160 -126
  179. package/src/types/constraint.ts +16 -2
  180. package/src/types/passes-gate.ts +0 -26
  181. package/src/types/trace.ts +10 -0
  182. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
  183. package/dist/cli/commands/analyze-sessions.d.ts +0 -20
  184. package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
  185. package/dist/cli/commands/analyze-sessions.js +0 -290
  186. package/dist/cli/commands/analyze-sessions.js.map +0 -1
  187. package/dist/cli/commands/update-user-model.d.ts +0 -25
  188. package/dist/cli/commands/update-user-model.d.ts.map +0 -1
  189. package/dist/cli/commands/update-user-model.js +0 -397
  190. package/dist/cli/commands/update-user-model.js.map +0 -1
  191. package/dist/cli/session-mining/corrections.d.ts +0 -17
  192. package/dist/cli/session-mining/corrections.d.ts.map +0 -1
  193. package/dist/cli/session-mining/corrections.js +0 -58
  194. package/dist/cli/session-mining/corrections.js.map +0 -1
  195. package/dist/cli/session-mining/index.d.ts +0 -13
  196. package/dist/cli/session-mining/index.d.ts.map +0 -1
  197. package/dist/cli/session-mining/index.js +0 -29
  198. package/dist/cli/session-mining/index.js.map +0 -1
  199. package/dist/cli/session-mining/text.d.ts +0 -31
  200. package/dist/cli/session-mining/text.d.ts.map +0 -1
  201. package/dist/cli/session-mining/text.js +0 -126
  202. package/dist/cli/session-mining/text.js.map +0 -1
  203. package/dist/cli/session-mining/transcript.d.ts +0 -34
  204. package/dist/cli/session-mining/transcript.d.ts.map +0 -1
  205. package/dist/cli/session-mining/transcript.js +0 -130
  206. package/dist/cli/session-mining/transcript.js.map +0 -1
  207. package/dist/context/compaction.d.ts +0 -57
  208. package/dist/context/compaction.d.ts.map +0 -1
  209. package/dist/context/compaction.js +0 -165
  210. package/dist/context/compaction.js.map +0 -1
  211. package/dist/core/session/clean-state.d.ts +0 -17
  212. package/dist/core/session/clean-state.d.ts.map +0 -1
  213. package/dist/core/session/clean-state.js +0 -192
  214. package/dist/core/session/clean-state.js.map +0 -1
  215. package/dist/core/session/index.d.ts +0 -6
  216. package/dist/core/session/index.d.ts.map +0 -1
  217. package/dist/core/session/index.js +0 -15
  218. package/dist/core/session/index.js.map +0 -1
  219. package/dist/core/session/startup.d.ts +0 -47
  220. package/dist/core/session/startup.d.ts.map +0 -1
  221. package/dist/core/session/startup.js +0 -263
  222. package/dist/core/session/startup.js.map +0 -1
  223. package/dist/failure/constraint-handler.d.ts +0 -83
  224. package/dist/failure/constraint-handler.d.ts.map +0 -1
  225. package/dist/failure/constraint-handler.js +0 -138
  226. package/dist/failure/constraint-handler.js.map +0 -1
  227. package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
  228. package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
  229. package/dist/knowledge/lifecycle-hooks.js +0 -143
  230. package/dist/knowledge/lifecycle-hooks.js.map +0 -1
  231. package/dist/types/session.d.ts +0 -113
  232. package/dist/types/session.d.ts.map +0 -1
  233. package/dist/types/session.js +0 -6
  234. package/dist/types/session.js.map +0 -1
  235. package/src/__tests__/clean-state-extra.test.ts +0 -301
  236. package/src/__tests__/clean-state.test.ts +0 -169
  237. package/src/__tests__/constraint-handler.test.ts +0 -132
  238. package/src/__tests__/passes-gate-extra.test.ts +0 -93
  239. package/src/__tests__/session-startup.test.ts +0 -334
  240. package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
  241. package/src/cli/commands/__tests__/update-user-model.test.ts +0 -234
  242. package/src/cli/commands/analyze-sessions.ts +0 -335
  243. package/src/cli/commands/update-user-model.ts +0 -461
  244. package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
  245. package/src/cli/session-mining/corrections.ts +0 -56
  246. package/src/cli/session-mining/index.ts +0 -13
  247. package/src/cli/session-mining/text.ts +0 -117
  248. package/src/cli/session-mining/transcript.ts +0 -125
  249. package/src/context/__tests__/compaction.test.ts +0 -169
  250. package/src/context/compaction.ts +0 -201
  251. package/src/core/session/__tests__/startup.test.ts +0 -281
  252. package/src/core/session/clean-state.ts +0 -178
  253. package/src/core/session/index.ts +0 -15
  254. package/src/core/session/startup.ts +0 -253
  255. package/src/failure/constraint-handler.ts +0 -171
  256. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
  257. package/src/knowledge/lifecycle-hooks.ts +0 -202
  258. package/src/types/session.ts +0 -132
@@ -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
  /**
@@ -5,6 +5,7 @@
5
5
  import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
6
6
  import { PassesGate, createPassesGate, detectTestCommand } from '../core/validators/passes-gate';
7
7
  import type { TaskTestResult } from '../types/passes-gate';
8
+ import { createProjectFixture } from '../test-setup/project-fixture';
8
9
  import { mkdirSync, rmSync, writeFileSync } from 'fs';
9
10
  import { join } from 'path';
10
11
 
@@ -32,34 +33,6 @@ describe('PassesGate', () => {
32
33
  }
33
34
  });
34
35
 
35
- describe('setPasses', () => {
36
- it('设置为 false 应该直接允许', async () => {
37
- const result = await gate.setPasses('task-1', false, tempDir);
38
-
39
- expect(result.allowed).toBe(true);
40
- expect(result.testResult?.passed).toBe(false);
41
- });
42
-
43
- it('设置为 true 应该运行测试', async () => {
44
- const result = await gate.setPasses('task-2', true, tempDir, {
45
- id: 'task-2',
46
- });
47
-
48
- // 测试命令是 echo "1 passed",所以应该通过
49
- expect(result.allowed).toBe(true);
50
- expect(result.testResult?.command).toContain('npm test');
51
- });
52
-
53
- it('禁用时应该允许所有设置', async () => {
54
- const disabledGate = createPassesGate({ enabled: false });
55
-
56
- const result = await disabledGate.setPasses('task-3', true, tempDir);
57
-
58
- expect(result.allowed).toBe(true);
59
- expect(result.attempts).toBe(0);
60
- });
61
- });
62
-
63
36
  describe('runTests', () => {
64
37
  // 执行根由调用方传入(#95):tempDir 的 package.json 测试命令是 echo,不再跑真实套件
65
38
  it('应该返回测试结果', async () => {
@@ -71,15 +44,6 @@ describe('PassesGate', () => {
71
44
  });
72
45
  });
73
46
 
74
- describe('detectTestCommand', () => {
75
- it('应该检测 npm test', async () => {
76
- // package.json 有 test script
77
- const result = await gate.setPasses('task-4', true, tempDir);
78
-
79
- expect(result.testResult?.command).toContain('npm');
80
- });
81
- });
82
-
83
47
  // 探测正本行为钉住(架构评审 A2:CLI/core 双版收口后的统一语义,
84
48
  // 此前 e2e 优先级、echo 排除、兜底均无测试钉住)
85
49
  describe('detectTestCommand 正本(统一探测顺序)', () => {
@@ -152,70 +116,13 @@ describe('PassesGate', () => {
152
116
  expect(await detectTestCommand(emptyDir)).toBeUndefined();
153
117
  });
154
118
 
155
- it('探测失败时 runTests 与 setPasses 双路径 fail-closed', async () => {
119
+ it('探测失败时 runTests fail-closed', async () => {
156
120
  const emptyDir = join(detectDir, 'fail-closed');
157
121
  mkdirSync(emptyDir, { recursive: true });
158
122
 
159
123
  const runResult = await gate.runTests(emptyDir);
160
124
  expect(runResult.passed).toBe(false);
161
125
  expect(runResult.message).toBe('未检测到测试命令');
162
-
163
- const setResult = await gate.setPasses('task-nocmd', true, emptyDir);
164
- expect(setResult.allowed).toBe(false);
165
- expect(setResult.testResult?.passed).toBe(false);
166
- expect(setResult.testResult?.output).toBe('未检测到测试命令');
167
- });
168
- });
169
-
170
- describe('checkTestFileChanges', () => {
171
- it('应该检测测试文件变更', async () => {
172
- // 无 git 变更时返回空数组
173
- const changes = await gate.checkTestFileChanges(tempDir);
174
-
175
- expect(Array.isArray(changes)).toBe(true);
176
- });
177
- });
178
-
179
- describe('getTestResult', () => {
180
- it('应该返回已存储的测试结果', async () => {
181
- const result = await gate.setPasses('stored-task', true, tempDir);
182
-
183
- if (result.allowed && result.testResult) {
184
- const stored = gate.getTestResult('stored-task');
185
- expect(stored).toBeDefined();
186
- expect(stored?.command).toContain('npm');
187
- }
188
- });
189
-
190
- it('未存储任务应返回 undefined', () => {
191
- const stored = gate.getTestResult('nonexistent-task');
192
- expect(stored).toBeUndefined();
193
- });
194
- });
195
-
196
- describe('测试失败重试', () => {
197
- it('测试失败后应重试', async () => {
198
- const retryDir = join(process.cwd(), 'temp-test-retry');
199
- mkdirSync(retryDir, { recursive: true });
200
-
201
- writeFileSync(join(retryDir, 'package.json'), JSON.stringify({
202
- name: 'retry-project',
203
- scripts: { test: 'exit 1' }, // 总是失败
204
- }));
205
-
206
- const retryGate = createPassesGate({
207
- enabled: true,
208
- maxRetries: 1,
209
- retryDelay: 100,
210
- });
211
-
212
- const result = await retryGate.setPasses('retry-task', true, retryDir);
213
-
214
- expect(result.allowed).toBe(false);
215
- expect(result.attempts).toBeGreaterThan(1);
216
- expect(result.error).toBeDefined();
217
-
218
- rmSync(retryDir, { recursive: true, force: true });
219
126
  });
220
127
  });
221
128
 
@@ -224,33 +131,25 @@ describe('PassesGate', () => {
224
131
  * (extractCoverage → TaskTestResult.coverage)整体删除,结果面不再有 coverage 字段。
225
132
  */
226
133
  describe('覆盖率取数路删除(harness#94)', () => {
227
- const covDir = join(process.cwd(), 'temp-test-no-coverage-field');
228
-
229
- beforeAll(() => {
230
- mkdirSync(covDir, { recursive: true });
231
-
232
- // 输出里放一份 jest 覆盖率表:删除前 extractCoverage 会从中抠出 80.5 填进结果字段
233
- writeFileSync(join(covDir, 'package.json'), JSON.stringify({
234
- name: 'coverage-project',
235
- scripts: { test: 'echo "All files | 80.50 | 70.21 | 83.33 | 79.59 |"' },
236
- }));
237
- });
238
-
239
- afterAll(() => {
240
- try {
241
- rmSync(covDir, { recursive: true, force: true });
242
- } catch {
243
- // ignore
244
- }
245
- });
246
-
247
- it('setPasses 结果不带 coverage 字段', async () => {
134
+ // 临时根走 createProjectFixture 正本(tmpdir,mkdtemp 劫持统一回收),不建在仓内
135
+ const covDir = createProjectFixture({
136
+ name: 'pg-no-coverage',
137
+ files: {
138
+ // 输出里放一份 jest 覆盖率表:删除前 extractCoverage 会从中抠出 80.5 填进结果字段
139
+ 'package.json': JSON.stringify({
140
+ name: 'coverage-project',
141
+ scripts: { test: 'echo "All files | 80.50 | 70.21 | 83.33 | 79.59 |"' },
142
+ }),
143
+ },
144
+ });
145
+
146
+ it('runTests 结果不带 coverage 字段', async () => {
248
147
  const covGate = createPassesGate({ enabled: true, requireEvidence: false });
249
148
 
250
- const result = await covGate.setPasses('cov-task', true, covDir);
149
+ const result = await covGate.runTests(covDir);
251
150
 
252
- expect(result.testResult?.passed).toBe(true);
253
- expect(result.testResult).not.toHaveProperty('coverage');
151
+ expect(result.passed).toBe(true);
152
+ expect(result).not.toHaveProperty('coverage');
254
153
  });
255
154
 
256
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,8 +29,9 @@ 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
- - 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()` 兜底形状、下游层(core/gates/context/monitoring/hooks)cwd 站点集合冻结、`xxxPath: '相对值'` 默认值冻结;豁免逐条带理由,站点消失却不删条目同样失败
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」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
35
36
  - **读到部分结果必须告知(harness#100)**:命令面以 `skip` 策略读 JSONL(traces.log / failures.log)时,坏行计数要到用户可见输出,**不允许默默丢弃**。共同约束:无损坏零噪声(计数为 0 不出行)、不改任何既有计数口径(`status` 的 `记录数` 仍是原始非空行数,逐字节不变)、计数一律取自 `src/utils/jsonl` 读链返回的 `skippedLines`(经报告入口透传或在读点就地取用),**禁止**用「原始行数 − 统计条数」反推(窗过滤掉的合法行与坏行分不开)。三处落点各自对得上一行代码:
36
37
  - `status`:告知**走 stderr**(`logError(io, …)`),因为 stdout 是报告体、其字节已被 `记录数` 那一行定死,挤进去等于改输出
@@ -43,7 +44,7 @@ H5(#44)起:
43
44
  - init 治理约束段写入落点(studio #302,ADR 2026-08-21 落点模型):新仓 → AGENTS.md `PRESERVE:governance` 段(sync-docs 重新生成时保留);旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记或 `## Governance Rules` 块)→ 续写 CLAUDE.md,幂等重跑不制造双份正本。治理段「在场守护」由 core 侧 governance_presence checker 承担(段缺失/为空时 check 报警)
44
45
  - PRESERVE:governance 段内写入纪律:机器管理的只有 HARNESS_CONSTRAINTS 标记区间——有标记只换标记区间,无标记(纯手写段)在段尾追加注入段;段内其余手写内容(治理契约引言/流程/纪律等)必须原样保留(曾整段替换清空手写内容的回归,init-injection.test 有防回归用例)
45
46
  - 注入段落点路由与 marker-range 替换收口在 `core/constraints/injection-writer`(ADR-0011):读侧 `resolveInjectionTarget`(漂移检测/retire 同步共用「CLAUDE.md 有标记优先,否则 AGENTS.md」)+ 写侧 `resolveGovernanceLanding`(init 落点选择);init 三个治理段 writer 与 retire 注入同步均为「渲染 body + 调 writer」,半标记(单边/乱序)一律拒写告警;未注入 = 两处均无完整标记段
46
- - knowledge 命令包含 13 个子操作(list/search/import/decay/stats/upsert/sync-status/sync-rag/audit/snapshot/migrate/index/health
47
+ - knowledge 命令包含 11 个子操作(list/search/import/decay/stats/sync-rag/audit/snapshot/migrate/index/health);upsert/sync-status 已迁至 studio CLI(harness#110,二者硬编码 localhost Studio 内部端点,不属 harness「通用框架、文件驱动」定位)
47
48
  - `stats` / `health` 的飞轮数字不在 CLI 内计算:分子口径唯一实现是 `knowledge/flywheel-metrics.ts`(`evaluateFlywheel`,ADR-0013),本层只做百分比取整/一位小数与字段名映射;`.consumption-stats.json` 的读取留在各命令(module 零 IO)
48
49
  - 特殊路由(选项条件、子命令兜底、裸跑语义)表达在定义表的 optionRoutes / subcommands / subcommandStrict / bareRunsAction 字段,bin 是纯通用引擎、不含单命令知识
49
50
  - **命令 interface = `CommandResult` + 注入 io(架构评审候选7)**:命令实现一律声明 `Promise<CommandResult>`(判别联合 `ok|skip|fail|usage-error`,`fail`/`usage-error` 必附可定位的 `reason`,多闸门命令 reason 含 `gate <id>`),末位可选形参 `io: CommandIO = processIO`;类型与写入面在 `src/cli/command-contract.ts`
@@ -67,6 +67,12 @@ describe('命令实现/定义表目录零 process.exit(唯一出口在 bin)'
67
67
  });
68
68
  });
69
69
 
70
+ // CI 上 dist 缺失时显式失败而非静默 skip(harness#99):#86 退出语义的
71
+ // 唯一端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则主证据隐形。
72
+ if (!hasDist && process.env.CI) {
73
+ throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
74
+ }
75
+
70
76
  const smoke = hasDist ? describe : describe.skip;
71
77
 
72
78
  smoke('端到端:真实命令经 bin 映射后的对外退出码', () => {
@@ -200,6 +200,8 @@ describe('check command(真 git fixture)', () => {
200
200
  stageChange(
201
201
  dir,
202
202
  'src/existing.ts',
203
+ // 'pass'+'word' 拆串是躲本仓 no_hardcoded_credentials 静态扫描:测试源文件里出现完整
204
+ // `password = "..."` 字面量会撞自己的 pre-commit 门禁;运行时拼接值不变,断言不受影响
203
205
  'export const a = 1;\nconst pass' + 'word = "correcthorsebattery";\n'
204
206
  );
205
207
  passTraces(dir);
@@ -240,6 +242,78 @@ describe('check command(真 git fixture)', () => {
240
242
  });
241
243
  });
242
244
 
245
+ describe('判定证据外显(harness#119)', () => {
246
+ /** 带 CAPABILITIES.md 的 git 仓库:登记列表外的源文件即为仓库级漂移 */
247
+ function capRepo(registered: string[]): string {
248
+ const dir = gitRepo();
249
+ const rows = registered
250
+ .map((rel) => `| ${path.basename(rel, '.ts')} | ${rel} | fixture |\n`)
251
+ .join('');
252
+ write(dir, 'CAPABILITIES.md', `# C\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`);
253
+ git(dir, 'add', 'CAPABILITIES.md');
254
+ git(dir, 'commit', '-q', '-m', 'capabilities');
255
+ return dir;
256
+ }
257
+
258
+ /** 从本用例 trace 文件里取某条约束的记录 */
259
+ function traceOf(constraintId: string) {
260
+ return fs
261
+ .readFileSync(traceFile, 'utf-8')
262
+ .split('\n')
263
+ .filter(Boolean)
264
+ .map((line) => JSON.parse(line) as { constraintId: string; result: string; evidence?: string[] })
265
+ .find((l) => l.constraintId === constraintId);
266
+ }
267
+
268
+ it('变更文件未登记 → 警告随附证据点名该文件', async () => {
269
+ const dir = capRepo(['src/existing.ts', 'src/nested/deep.ts']);
270
+ stageChange(dir, 'src/nested/extra.ts', 'export const e = 1;\n');
271
+ passTraces(dir);
272
+
273
+ const result = await check(
274
+ { preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
275
+ io
276
+ );
277
+
278
+ expect(io.outText()).toContain('- capability_sync');
279
+ expect(io.outText()).toContain('src/nested/extra.ts');
280
+ expect(result).toEqual({ kind: 'ok' });
281
+ });
282
+
283
+ it('仓库级漂移(与本次变更无关)→ 提示块露出,不判违规', async () => {
284
+ const dir = capRepo(['src/existing.ts']);
285
+ passTraces(dir);
286
+
287
+ const result = await check(
288
+ { preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
289
+ io
290
+ );
291
+
292
+ const out = io.outText();
293
+ expect(out).toContain('✅ 指导原则: 1/1 通过');
294
+ expect(out).not.toContain('指导原则警告');
295
+ expect(out).toContain('💡 提示: 1 条(不判违规,供参考)');
296
+ expect(out).toContain('- capability_sync:');
297
+ expect(out).toContain('src/nested/deep.ts');
298
+ expect(out).toContain('✅ 约束检查通过');
299
+ expect(result).toEqual({ kind: 'ok' });
300
+ });
301
+
302
+ it('提示与违规都写进 trace 的 evidence 字段(统计侧可诊断)', async () => {
303
+ const dir = capRepo(['src/existing.ts']);
304
+ passTraces(dir);
305
+
306
+ await check(
307
+ { preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
308
+ io
309
+ );
310
+
311
+ const record = traceOf('capability_sync');
312
+ expect(record?.result).toBe('pass');
313
+ expect(record?.evidence?.join('\n')).toContain('src/nested/deep.ts');
314
+ });
315
+ });
316
+
243
317
  describe('git 证据单一来源(#87 验收)', () => {
244
318
  it('两个 checker 请求同一份 staged diff → 一次 run 内只 spawn 一次', async () => {
245
319
  // 新增(非删除)测试行:no_test_simplification 请求 stagedDiff 后放行,
@@ -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
+ });