@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
@@ -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
  });
@@ -7,9 +7,11 @@
7
7
  *
8
8
  * 本文件是这条约定的机器可检面,三道闸:
9
9
  * 1. CLI 入口层:cwd 只能以 `xxx || process.cwd()` 的兜底形状出现,例外逐个点名并记理由
10
- * 2. 下游层(core / gates / context / monitoring / hooks):cwd 站点集合冻结,
11
- * 新增即失败——要么把根传下去,要么在此记下豁免理由
12
- * 3. `xxxPath: '相对路径'` 默认值同理冻结(acceptance 的 `tasksPath: './tasks.yml'` 就是这一类)
10
+ * 2. 下游层(= src 减 cli,含 core / gates / context / monitoring / hooks 等):cwd 站点**逐行**冻结
11
+ * (harness#98,不再只冻文件键集——豁免文件内新增站点/行变形同样失败),
12
+ * 要么把根传下去,要么在此记下豁免理由
13
+ * 3. 相对路径默认值同理冻结(harness#98 收紧:对象字面量 `xxxPath: '相对'` 之外,
14
+ * 参数默认值形 `xxxPath = '相对'`(含类型标注)与模板字面量同罪;扫描域 = 整个下游层)
13
15
  *
14
16
  * 冻结集合还要求**每条豁免仍然成立**(站点消失却不删条目 → 同样失败),豁免不会烂成化石。
15
17
  * 「根参数已传入却又取 cwd」这类静态看不出的漂移,由 project-path-anchoring.test.ts 的真实 IO 行为用例守。
@@ -83,28 +85,86 @@ const CLI_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> =
83
85
  * 下游豁免的共同理由:这些是**库层公开 API 的可选根参数默认值**(调用方没给根时的缺省),
84
86
  * 不是「根已经传进来了,取 IO 时又绕回 cwd」。core 不能 import cli,兜底只能留在各自入口,
85
87
  * 故冻结而非删除;新增站点必须在此逐个点名并单独说明。
88
+ *
89
+ * harness#98:冻结粒度从文件键集收紧到**行内容**——已豁免文件内悄悄新增 cwd 站点
90
+ * (不动键集)曾是绕过通道,现在新增行/行变形都会顶失败。
86
91
  */
87
- const DOWNSTREAM_CWD_EXEMPTIONS: Record<string, string> = {
88
- 'src/context/session-manager.ts': '构造参数 basePath 缺省(会话存储根,CLI 已显式传 projectPath)',
89
- 'src/core/constraints/checker.ts': 'check 上下文缺 projectPath 时的根兜底(3 处同一形状)',
90
- 'src/core/constraints/checkers/types.ts': 'checker 环境的根兜底(context.projectPath 可选)',
91
- 'src/core/constraints/context-builder.ts': 'buildCheckEnv 的 options.projectPath 缺省兜底(2 处)',
92
- 'src/core/constraints/usage-report.ts': 'projectRoot 形参默认值(库层可选根)',
93
- 'src/core/effective-constraints.ts': '生效集三函数 projectRoot 形参默认值(库层可选根,JSDoc 已声明)',
94
- 'src/core/project-config-loader.ts': 'config 加载器 projectPath 构造参数缺省兜底',
95
- 'src/core/spec/validator.ts': 'validateAll 的可选 projectPath 兜底(其相对 schemaPath 默认值见闸 3 豁免)',
96
- 'src/gates/checker-gate.ts': 'GateContext.projectPath 缺省兜底(门禁适配器的根入口)',
97
- 'src/gates/effective-gates.ts': 'projectRoot 形参默认值(库层可选根)',
98
- 'src/hooks/bootstrap.ts': 'bootstrapHarness 的可选 projectPath 兜底(组合根入口,2 处)',
99
- 'src/monitoring/context-tracker.ts': 'basePath 构造参数缺省兜底',
92
+ const DOWNSTREAM_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> = {
93
+ 'src/context/session-manager.ts': {
94
+ lines: ['this.basePath = basePath || process.cwd();'],
95
+ reason: '构造参数 basePath 缺省(会话存储根,CLI 已显式传 projectPath',
96
+ },
97
+ 'src/core/constraints/checker.ts': {
98
+ lines: [
99
+ 'const projectPath = context.projectPath || process.cwd();',
100
+ 'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
101
+ 'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
102
+ ],
103
+ reason: 'check 上下文缺 projectPath 时的根兜底(3 处同一形状)',
104
+ },
105
+ 'src/core/constraints/checkers/types.ts': {
106
+ lines: ['const projectPath = context.projectPath || process.cwd();'],
107
+ reason: 'checker 环境的根兜底(context.projectPath 可选)',
108
+ },
109
+ 'src/core/constraints/context-builder.ts': {
110
+ lines: [
111
+ 'const projectPath = options.projectPath || process.cwd();',
112
+ 'const projectPath = options.projectPath || process.cwd();',
113
+ ],
114
+ reason: 'buildCheckEnv 的 options.projectPath 缺省兜底(2 处)',
115
+ },
116
+ 'src/core/constraints/usage-report.ts': {
117
+ lines: ['projectRoot: string = process.cwd(),'],
118
+ reason: 'projectRoot 形参默认值(库层可选根)',
119
+ },
120
+ 'src/core/effective-constraints.ts': {
121
+ lines: [
122
+ 'projectRoot: string = process.cwd(),',
123
+ 'projectRoot: string = process.cwd(),',
124
+ 'export function lintEffectiveConfig(projectRoot: string = process.cwd()): EffectiveConfigLint {',
125
+ ],
126
+ reason: '生效集三函数 projectRoot 形参默认值(库层可选根,JSDoc 已声明)',
127
+ },
128
+ 'src/core/project-config-loader.ts': {
129
+ lines: ['this.projectPath = projectPath || process.cwd();'],
130
+ reason: 'config 加载器 projectPath 构造参数缺省兜底',
131
+ },
132
+ 'src/core/spec/validator.ts': {
133
+ lines: ['const cwd = projectPath || process.cwd();'],
134
+ reason: 'validateAll 的可选 projectPath 兜底(其相对 schemaPath 默认值见闸 3 豁免)',
135
+ },
136
+ 'src/gates/checker-gate.ts': {
137
+ lines: ['const projectPath = ctx.projectPath || process.cwd();'],
138
+ reason: 'GateContext.projectPath 缺省兜底(门禁适配器的根入口)',
139
+ },
140
+ 'src/gates/effective-gates.ts': {
141
+ lines: ['export function getEffectiveGates(projectRoot: string = process.cwd()): Gate[] {'],
142
+ reason: 'projectRoot 形参默认值(库层可选根)',
143
+ },
144
+ 'src/hooks/bootstrap.ts': {
145
+ lines: [
146
+ 'const resolvedPath = projectPath || process.cwd();',
147
+ 'const resolvedPath = projectPath || process.cwd();',
148
+ ],
149
+ reason: 'bootstrapHarness 的可选 projectPath 兜底(组合根入口,2 处)',
150
+ },
151
+ 'src/monitoring/context-tracker.ts': {
152
+ lines: ['const base = basePath || process.cwd();'],
153
+ reason: 'basePath 构造参数缺省兜底',
154
+ },
100
155
  };
101
156
 
102
157
  // ========================================
103
- // 闸 3:core/gates 的相对路径默认值
158
+ // 闸 3:下游层的相对路径默认值
104
159
  // ========================================
105
160
 
106
- /** key 必须 camelCase 以 Path 结尾,值为相对字面量——这类默认值按 cwd 解析,即 #95 的病根形状 */
107
- const RELATIVE_PATH_DEFAULT = /\b\w+Path\s*:\s*'(?!\/)([^']+)'/;
161
+ /**
162
+ * key 必须 camelCase 以 Path 结尾,值为相对字面量——这类默认值按 cwd 解析,即 #95 的病根形状。
163
+ * harness#98 收紧匹配形状(原为只认对象字面量 `xxxPath: '相对'`):
164
+ * 参数默认值形 `xxxPath = '相对'`、带类型标注的 `xxxPath: string = '相对'`、模板字面量、
165
+ * 双引号形同罪(仓内 lint 不强制引号风格,三种引号都得认)。
166
+ */
167
+ const RELATIVE_PATH_DEFAULT = /\b\w+Path\s*(?::\s*[^=,)]+?)?[:=]\s*['"`](?!\/)/;
108
168
 
109
169
  const RELATIVE_PATH_DEFAULT_EXEMPTIONS: Record<string, string> = {
110
170
  'src/core/spec/validator.ts':
@@ -133,11 +193,13 @@ describe('projectPath 传递约定(harness#95)', () => {
133
193
  });
134
194
 
135
195
  describe('闸 2:下游层不得二次取 cwd', () => {
136
- it('下游 cwd 站点集合 = 冻结豁免表(新增即失败;站点消失却不删条目也失败)', () => {
196
+ it('下游 cwd 站点 = 冻结豁免表且逐行一致(新增文件/新增行/行变形/豁免化石都失败)', () => {
137
197
  const sites = cwdSites(downstreamFiles);
138
198
 
139
199
  expect([...sites.keys()].sort()).toEqual(Object.keys(DOWNSTREAM_CWD_EXEMPTIONS).sort());
140
- expect(sites.size).toBe(Object.keys(DOWNSTREAM_CWD_EXEMPTIONS).length);
200
+ for (const [rel, exemption] of Object.entries(DOWNSTREAM_CWD_EXEMPTIONS)) {
201
+ expect({ rel, lines: sites.get(rel) }).toEqual({ rel, lines: exemption.lines });
202
+ }
141
203
  });
142
204
 
143
205
  it('PassesGate 执行侧不再取 cwd(站点 1 的收口形状:workDir 由调用方传入)', () => {
@@ -149,9 +211,9 @@ describe('projectPath 传递约定(harness#95)', () => {
149
211
  });
150
212
 
151
213
  describe('闸 3:相对路径默认值不再按 cwd 解析', () => {
152
- it('core/gates xxxPath: 相对字面量 的站点 = 冻结豁免表', () => {
214
+ it('下游层(= src cli)的相对字面量路径默认值 = 冻结豁免表', () => {
153
215
  const hits = new Set<string>();
154
- for (const file of listTsFiles(path.join(SRC_ROOT, 'core')).concat(listTsFiles(path.join(SRC_ROOT, 'gates')))) {
216
+ for (const file of downstreamFiles) {
155
217
  if (codeLines(file).some(line => RELATIVE_PATH_DEFAULT.test(line))) hits.add(repoPath(file));
156
218
  }
157
219
 
@@ -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);
@@ -175,6 +218,12 @@ function runWithModuleProbe(argv: string[]): { status: number | null; implModule
175
218
  }
176
219
  }
177
220
 
221
+ // CI 上 dist 缺失时显式失败而非静默 skip(harness#99):bin 懒加载/路由的
222
+ // 端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则证据隐形。
223
+ if (!hasDist && process.env.CI) {
224
+ throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
225
+ }
226
+
178
227
  const smoke = hasDist ? describe : describe.skip;
179
228
 
180
229
  smoke('bin/harness.js 端到端(dist 存在时)', () => {
@@ -52,18 +52,6 @@ describe('report command - 补充覆盖', () => {
52
52
  });
53
53
  });
54
54
 
55
- describe('HTML 报告完整内容', () => {
56
- it('HTML 报告应该包含所有区块', async () => {
57
- await report({ format: 'html' }, io);
58
- const output = io.outText();
59
-
60
- expect(output).toContain('<!DOCTYPE html>');
61
- expect(output).toContain('<title>Harness 检查报告</title>');
62
- expect(output).toContain('约束检查');
63
- expect(output).toContain('Iron Laws');
64
- });
65
- });
66
-
67
55
  describe('文件输出', () => {
68
56
  it('Markdown 输出到文件', async () => {
69
57
  mockFs.writeFile.mockResolvedValue(undefined);
@@ -71,13 +59,6 @@ describe('report command - 补充覆盖', () => {
71
59
  await report({ format: 'markdown', output: 'report.md' }, io);
72
60
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
73
61
  });
74
-
75
- it('HTML 输出到文件', async () => {
76
- mockFs.writeFile.mockResolvedValue(undefined);
77
-
78
- await report({ format: 'html', output: 'report.html' }, io);
79
- expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
80
- });
81
62
  });
82
63
 
83
64
  describe('JSON 报告内容', () => {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * report 假绿回归(架构评审候选1)
3
+ *
4
+ * 历史 bug:report 经 executeWithCollect 调 checkConstraints,checker 在首个
5
+ * 铁律违规即 throw,handler 的 catch 丢弃 error.result 并合成空结果 ——
6
+ * 铁律违规被报成 violations: [] / passed = 全量(假绿)。
7
+ * 修复后 report 直调 checker.collectConstraints(不抛、全量收集),违规如实上报。
8
+ *
9
+ * fixture:CAPABILITIES.md 登记幽灵文件 → docs_freshness(file_modification
10
+ * 触发域内唯一铁律)必违规。
11
+ */
12
+
13
+ import { describe, it, expect } from '@jest/globals';
14
+ import { report } from '../report';
15
+ import { captureIO } from '../../command-contract';
16
+ import { createProjectFixture } from '../../../test-setup/project-fixture';
17
+
18
+ // 与 three-state-consistency.test.ts 相同的表格正本形状
19
+ const GHOST_CAPABILITIES =
20
+ '# Capabilities\n\n' +
21
+ '| 模块 | 文件 | 说明 |\n' +
22
+ '|------|------|------|\n' +
23
+ '| 遗魂 | src/gone.ts | 已删 |\n';
24
+
25
+ function parseJsonReport(output: string) {
26
+ const start = output.indexOf('{');
27
+ const end = output.lastIndexOf('}');
28
+ return JSON.parse(output.slice(start, end + 1));
29
+ }
30
+
31
+ describe('report 铁律违规如实上报(假绿回归)', () => {
32
+ it('docs_freshness 违规 → failed ≥ 1、violations 点名、passed < total', async () => {
33
+ const root = createProjectFixture({
34
+ name: 'report-false-green',
35
+ files: { 'CAPABILITIES.md': GHOST_CAPABILITIES },
36
+ });
37
+ const io = captureIO();
38
+
39
+ await report({ format: 'json', projectPath: root }, io);
40
+
41
+ const data = parseJsonReport(io.outText());
42
+ expect(data.constraints.failed).toBeGreaterThanOrEqual(1);
43
+ expect(data.constraints.violations.map((v: { id: string }) => v.id)).toContain('docs_freshness');
44
+ expect(data.constraints.passed).toBeLessThan(data.constraints.total);
45
+ });
46
+ });
@@ -44,11 +44,6 @@ describe('report command', () => {
44
44
  expect(io.outText()).toContain('Harness 检查报告');
45
45
  });
46
46
 
47
- it('应该生成 HTML 格式报告', async () => {
48
- await report({ format: 'html' }, io);
49
- expect(io.outText()).toContain('<!DOCTYPE html>');
50
- });
51
-
52
47
  it('应该保存报告到文件', async () => {
53
48
  mockFs.writeFile.mockResolvedValue(undefined);
54
49
 
@@ -70,15 +65,6 @@ describe('report command', () => {
70
65
  expect(output).toContain('报告由 @dommaker/harness 生成');
71
66
  });
72
67
 
73
- it('HTML 报告应包含完整结构', async () => {
74
- await report({ format: 'html' }, io);
75
- const output = io.outText();
76
- expect(output).toContain('<html lang="zh-CN">');
77
- expect(output).toContain('</html>');
78
- expect(output).toContain('Harness 检查报告');
79
- expect(output).toContain('<table>');
80
- });
81
-
82
68
  it('JSON 报告应包含约束统计', async () => {
83
69
  await report({ format: 'json' }, io);
84
70
  const output = io.outText();
@@ -99,11 +85,5 @@ describe('report command', () => {
99
85
  await report({ format: 'markdown', output: 'report.md' }, io);
100
86
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
101
87
  });
102
-
103
- it('应该支持输出到文件 (html)', async () => {
104
- mockFs.writeFile.mockResolvedValue(undefined);
105
- await report({ format: 'html', output: 'report.html' }, io);
106
- expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
107
- });
108
88
  });
109
89
  });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * status 的分析接线(ADR-0020)
3
+ *
4
+ * `status` 要的是「对已读出的 traces 做纯判定」,此前却为此构造
5
+ * `new TraceCollector({traceFile})`(构造函数带 mkdir 副作用)+ `new TraceAnalyzer(c)`,
6
+ * 测试端再叠一层 TraceAnalyzer mock 才能断言。ADR-0020 把判定下放为模块级纯函数后,
7
+ * 本命令直调函数,构造仪式退出这条路径。
8
+ *
9
+ * 用真实临时工程根跑一遍(不 mock fs),顺带钉住「换成直调后统计与异常输出口径不变」。
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { createProjectFixture } from '../../../test-setup/project-fixture';
15
+ import { captureIO, type CapturingIO } from '../../command-contract';
16
+ import { status } from '../status';
17
+
18
+ const STATUS_SOURCE = fs.readFileSync(path.join(__dirname, '..', 'status.ts'), 'utf-8');
19
+
20
+ /** 1 pass + 1 fail:failRate 0.5、passRate 0.5 → 无异常;2 fail:passRate 0 → low_pass_rate */
21
+ const PASS = { constraintId: 'alpha', level: 'iron_law' as const, result: 'pass' as const };
22
+ const FAIL = { constraintId: 'beta', level: 'guideline' as const, result: 'fail' as const };
23
+
24
+ let io: CapturingIO;
25
+ beforeEach(() => {
26
+ io = captureIO();
27
+ });
28
+
29
+ describe('status 分析接线(ADR-0020)', () => {
30
+ it('不再构造 TraceCollector / TraceAnalyzer', () => {
31
+ expect(STATUS_SOURCE).not.toMatch(/new\s+TraceCollector\s*\(/);
32
+ expect(STATUS_SOURCE).not.toMatch(/new\s+TraceAnalyzer\s*\(/);
33
+ expect(STATUS_SOURCE).not.toMatch(/from\s+'[^']*monitoring\/traces'/);
34
+ });
35
+
36
+ it('直调 trace-analyzer 的模块级纯函数取统计与异常', () => {
37
+ expect(STATUS_SOURCE).toMatch(/summarizeTraces\s*\(/);
38
+ expect(STATUS_SOURCE).toMatch(/detectTraceAnomalies\s*\(/);
39
+ });
40
+
41
+ it('真实工程根跑通:按纯函数口径出统计,全过数据零异常', async () => {
42
+ const root = createProjectFixture({
43
+ name: 'status-pure-analysis',
44
+ config: 'project:\n name: status-pure-analysis\n',
45
+ traces: [PASS, { ...PASS, timestamp: 2 }],
46
+ });
47
+
48
+ await status({ projectPath: root, detail: true }, io);
49
+
50
+ expect(io.outText()).toContain('🔴 Iron Laws:');
51
+ expect(io.outText()).toContain('alpha');
52
+ expect(io.outText()).toContain('检查: 2 | 通过: 100% | 失败: 0%');
53
+ expect(io.outText()).not.toContain('个异常');
54
+ expect(io.outText()).toContain('继续积累数据');
55
+ });
56
+
57
+ it('真实工程根跑通:低通过率经纯函数判为异常并列出', async () => {
58
+ const root = createProjectFixture({
59
+ name: 'status-pure-analysis',
60
+ traces: [FAIL, { ...FAIL, timestamp: 2 }],
61
+ });
62
+
63
+ await status({ projectPath: root, anomalies: true }, io);
64
+
65
+ expect(io.outText()).toContain('发现 1 个异常');
66
+ expect(io.outText()).toContain('类型: low_pass_rate');
67
+ });
68
+ });