@dommaker/harness 1.5.0 → 1.7.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 (209) hide show
  1. package/CHANGELOG.md +26 -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 +2 -127
  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 -32
  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 +68 -16
  87. package/src/__tests__/public-type-surface.test.ts +359 -0
  88. package/src/__tests__/trace-analyzer.test.ts +52 -2
  89. package/src/cli/commands/CONTEXT.md +3 -2
  90. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +158 -0
  91. package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
  92. package/src/cli/commands/__tests__/performance.test.ts +5 -16
  93. package/src/cli/commands/__tests__/registry.test.ts +46 -3
  94. package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
  95. package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
  96. package/src/cli/commands/__tests__/report.test.ts +0 -20
  97. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
  98. package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
  99. package/src/cli/commands/__tests__/status.test.ts +66 -148
  100. package/src/cli/commands/definitions.ts +1 -32
  101. package/src/cli/commands/passes-gate.ts +0 -3
  102. package/src/cli/commands/performance.ts +13 -27
  103. package/src/cli/commands/report.ts +3 -61
  104. package/src/cli/commands/status.ts +5 -10
  105. package/src/context/CONTEXT.md +2 -3
  106. package/src/context/__tests__/token-budget.test.ts +1 -81
  107. package/src/context/index.ts +2 -7
  108. package/src/context/token-budget.ts +0 -73
  109. package/src/context/types.ts +0 -9
  110. package/src/core/CONTEXT.md +5 -6
  111. package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
  112. package/src/core/constraints/checker.ts +33 -2
  113. package/src/core/constraints/git-evidence.ts +6 -3
  114. package/src/core/index.ts +0 -12
  115. package/src/core/spec/__tests__/validator.test.ts +24 -0
  116. package/src/core/spec/validator.ts +9 -10
  117. package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
  118. package/src/core/validators/index.ts +0 -2
  119. package/src/core/validators/passes-gate.ts +3 -152
  120. package/src/failure/CONTEXT.md +8 -6
  121. package/src/failure/index.ts +0 -10
  122. package/src/gates/CONTEXT.md +3 -2
  123. package/src/gates/__tests__/performance.test.ts +6 -74
  124. package/src/gates/definitions.ts +0 -4
  125. package/src/gates/performance.ts +9 -111
  126. package/src/gates/types.ts +2 -8
  127. package/src/index.ts +1 -37
  128. package/src/knowledge/CONTEXT.md +1 -2
  129. package/src/knowledge/index.ts +0 -1
  130. package/src/monitoring/CONTEXT.md +2 -2
  131. package/src/monitoring/trace-analyzer.ts +160 -126
  132. package/src/types/passes-gate.ts +0 -43
  133. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
  134. package/dist/cli/commands/analyze-sessions.d.ts +0 -20
  135. package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
  136. package/dist/cli/commands/analyze-sessions.js +0 -290
  137. package/dist/cli/commands/analyze-sessions.js.map +0 -1
  138. package/dist/cli/commands/update-user-model.d.ts +0 -30
  139. package/dist/cli/commands/update-user-model.d.ts.map +0 -1
  140. package/dist/cli/commands/update-user-model.js +0 -406
  141. package/dist/cli/commands/update-user-model.js.map +0 -1
  142. package/dist/cli/session-mining/corrections.d.ts +0 -17
  143. package/dist/cli/session-mining/corrections.d.ts.map +0 -1
  144. package/dist/cli/session-mining/corrections.js +0 -58
  145. package/dist/cli/session-mining/corrections.js.map +0 -1
  146. package/dist/cli/session-mining/index.d.ts +0 -13
  147. package/dist/cli/session-mining/index.d.ts.map +0 -1
  148. package/dist/cli/session-mining/index.js +0 -29
  149. package/dist/cli/session-mining/index.js.map +0 -1
  150. package/dist/cli/session-mining/text.d.ts +0 -31
  151. package/dist/cli/session-mining/text.d.ts.map +0 -1
  152. package/dist/cli/session-mining/text.js +0 -126
  153. package/dist/cli/session-mining/text.js.map +0 -1
  154. package/dist/cli/session-mining/transcript.d.ts +0 -34
  155. package/dist/cli/session-mining/transcript.d.ts.map +0 -1
  156. package/dist/cli/session-mining/transcript.js +0 -130
  157. package/dist/cli/session-mining/transcript.js.map +0 -1
  158. package/dist/context/compaction.d.ts +0 -57
  159. package/dist/context/compaction.d.ts.map +0 -1
  160. package/dist/context/compaction.js +0 -165
  161. package/dist/context/compaction.js.map +0 -1
  162. package/dist/core/session/clean-state.d.ts +0 -17
  163. package/dist/core/session/clean-state.d.ts.map +0 -1
  164. package/dist/core/session/clean-state.js +0 -192
  165. package/dist/core/session/clean-state.js.map +0 -1
  166. package/dist/core/session/index.d.ts +0 -6
  167. package/dist/core/session/index.d.ts.map +0 -1
  168. package/dist/core/session/index.js +0 -15
  169. package/dist/core/session/index.js.map +0 -1
  170. package/dist/core/session/startup.d.ts +0 -47
  171. package/dist/core/session/startup.d.ts.map +0 -1
  172. package/dist/core/session/startup.js +0 -263
  173. package/dist/core/session/startup.js.map +0 -1
  174. package/dist/failure/constraint-handler.d.ts +0 -83
  175. package/dist/failure/constraint-handler.d.ts.map +0 -1
  176. package/dist/failure/constraint-handler.js +0 -138
  177. package/dist/failure/constraint-handler.js.map +0 -1
  178. package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
  179. package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
  180. package/dist/knowledge/lifecycle-hooks.js +0 -143
  181. package/dist/knowledge/lifecycle-hooks.js.map +0 -1
  182. package/dist/types/session.d.ts +0 -113
  183. package/dist/types/session.d.ts.map +0 -1
  184. package/dist/types/session.js +0 -6
  185. package/dist/types/session.js.map +0 -1
  186. package/src/__tests__/clean-state-extra.test.ts +0 -301
  187. package/src/__tests__/clean-state.test.ts +0 -169
  188. package/src/__tests__/constraint-handler.test.ts +0 -132
  189. package/src/__tests__/passes-gate-extra.test.ts +0 -93
  190. package/src/__tests__/session-startup.test.ts +0 -334
  191. package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
  192. package/src/cli/commands/__tests__/update-user-model.test.ts +0 -268
  193. package/src/cli/commands/analyze-sessions.ts +0 -335
  194. package/src/cli/commands/update-user-model.ts +0 -476
  195. package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
  196. package/src/cli/session-mining/corrections.ts +0 -56
  197. package/src/cli/session-mining/index.ts +0 -13
  198. package/src/cli/session-mining/text.ts +0 -117
  199. package/src/cli/session-mining/transcript.ts +0 -125
  200. package/src/context/__tests__/compaction.test.ts +0 -169
  201. package/src/context/compaction.ts +0 -201
  202. package/src/core/session/__tests__/startup.test.ts +0 -281
  203. package/src/core/session/clean-state.ts +0 -178
  204. package/src/core/session/index.ts +0 -15
  205. package/src/core/session/startup.ts +0 -253
  206. package/src/failure/constraint-handler.ts +0 -171
  207. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
  208. package/src/knowledge/lifecycle-hooks.ts +0 -202
  209. package/src/types/session.ts +0 -132
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * status 命令测试
3
+ *
4
+ * ADR-0020 起不再 mock TraceAnalyzer/TraceCollector:统计与异常由 trace-analyzer
5
+ * 的模块级纯函数真算,用例喂合法 trace 行、断言真实输出。
3
6
  */
4
7
 
5
8
  import { status } from '../status';
6
9
  import { captureIO, type CapturingIO } from '../../command-contract';
7
10
  import * as fs from 'fs';
8
- import { TraceAnalyzer } from '../../../monitoring/trace-analyzer';
11
+ import type { ExecutionTrace } from '../../../types/trace';
9
12
 
10
13
  // Mock fs
11
14
  jest.mock('fs', () => ({
@@ -15,22 +18,6 @@ jest.mock('fs', () => ({
15
18
  writeFileSync: jest.fn(),
16
19
  }));
17
20
 
18
- // Mock TraceCollector
19
- jest.mock('../../../monitoring/traces', () => ({
20
- TraceCollector: jest.fn().mockImplementation(() => ({
21
- record: jest.fn(),
22
- read: jest.fn().mockReturnValue([]),
23
- })),
24
- }));
25
-
26
- // Mock TraceAnalyzer
27
- jest.mock('../../../monitoring/trace-analyzer', () => ({
28
- TraceAnalyzer: jest.fn().mockImplementation(() => ({
29
- summarize: jest.fn().mockReturnValue([]),
30
- detectAnomalies: jest.fn().mockReturnValue([]),
31
- })),
32
- }));
33
-
34
21
  // Mock chalk
35
22
  jest.mock('chalk', () => ({
36
23
  blue: jest.fn((str: string) => str),
@@ -41,7 +28,18 @@ jest.mock('chalk', () => ({
41
28
  }));
42
29
 
43
30
  const mockFs = fs as jest.Mocked<typeof fs>;
44
- const MockTraceAnalyzer = TraceAnalyzer as jest.MockedClass<typeof TraceAnalyzer>;
31
+
32
+ /** trace 文件内容:一 trace 一行,缺省字段补齐成合法 ExecutionTrace(timestamp 递增) */
33
+ function traceFile(traces: Array<Partial<ExecutionTrace>>): string {
34
+ return traces
35
+ .map((t, i) => JSON.stringify({ level: 'iron_law', timestamp: i + 1, result: 'pass', ...t }))
36
+ .join('\n');
37
+ }
38
+
39
+ /** n 条同一约束的 pass/fail 记录 */
40
+ function repeats(constraintId: string, n: number, result: ExecutionTrace['result']): Array<Partial<ExecutionTrace>> {
41
+ return Array.from({ length: n }, () => ({ constraintId, result }));
42
+ }
45
43
 
46
44
  let io: CapturingIO;
47
45
  beforeEach(() => {
@@ -80,98 +78,56 @@ describe('status command', () => {
80
78
 
81
79
  describe('正常状态显示', () => {
82
80
  it('应该显示记录数', async () => {
83
- const mockTraces = [
84
- { constraintId: 'test1', level: 'iron_law', result: 'pass' },
85
- { constraintId: 'test2', level: 'guideline', result: 'pass' },
86
- ];
87
- mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
88
-
89
- const mockSummarize = jest.fn().mockReturnValue([
90
- { constraintId: 'test1', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 1 },
91
- ]);
92
- const mockAnalyze = {
93
- summarize: mockSummarize,
94
- detectAnomalies: jest.fn().mockReturnValue([]),
95
- };
96
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
81
+ mockFs.readFileSync.mockReturnValue(traceFile([
82
+ { constraintId: 'test1' },
83
+ { constraintId: 'test2', level: 'guideline' },
84
+ ]));
97
85
 
98
86
  await status({}, io);
99
- expect(io.outText()).not.toBe('');
87
+ expect(io.outText()).toContain('记录数: 2 条');
88
+ expect(io.outText()).toContain('📈 约束统计:');
100
89
  });
101
90
 
102
91
  it('应该显示 Iron Laws 统计', async () => {
103
- const mockTraces = [
104
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', result: 'pass' },
105
- ];
106
- mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
107
-
108
- const mockSummarize = jest.fn().mockReturnValue([
109
- { constraintId: 'no_bypass_checkpoint', level: 'iron_law', passRate: 1, failRate: 0, totalChecks: 5 },
110
- ]);
111
- const mockAnalyze = {
112
- summarize: mockSummarize,
113
- detectAnomalies: jest.fn().mockReturnValue([]),
114
- };
115
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
92
+ mockFs.readFileSync.mockReturnValue(traceFile([
93
+ { constraintId: 'no_bypass_checkpoint' },
94
+ ]));
116
95
 
117
96
  await status({ detail: true }, io);
118
- expect(io.outText()).not.toBe('');
97
+ expect(io.outText()).toContain('🔴 Iron Laws:');
98
+ expect(io.outText()).toContain('✅ no_bypass_checkpoint');
99
+ expect(io.outText()).toContain('检查: 1 | 通过: 100% | 失败: 0%');
119
100
  });
120
101
  });
121
102
 
122
103
  describe('异常检测', () => {
123
104
  it('应该显示异常列表', async () => {
124
- const mockTraces = [
125
- { constraintId: 'test1', level: 'iron_law', result: 'fail' },
126
- ];
127
- mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
128
-
129
- const mockAnomalies = [
130
- { constraintId: 'test1', type: 'high_failure_rate', current: 0.8, threshold: 0.5 },
131
- ];
132
- const mockAnalyze = {
133
- summarize: jest.fn().mockReturnValue([
134
- { constraintId: 'test1', level: 'iron_law', passRate: 0.2, failRate: 0.8, totalChecks: 5 },
135
- ]),
136
- detectAnomalies: jest.fn().mockReturnValue(mockAnomalies),
137
- };
138
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
105
+ mockFs.readFileSync.mockReturnValue(traceFile([
106
+ { constraintId: 'test1', result: 'fail' },
107
+ ]));
139
108
 
140
109
  await status({ anomalies: true }, io);
141
- expect(io.outText()).toContain('异常');
110
+ expect(io.outText()).toContain('发现 1 个异常');
111
+ expect(io.outText()).toContain(' test1');
112
+ expect(io.outText()).toContain('类型: low_pass_rate');
142
113
  });
143
114
  });
144
115
 
145
116
  describe('详细模式', () => {
146
117
  it('应该显示详细统计', async () => {
147
- const mockTraces = [
148
- { constraintId: 'test1', level: 'iron_law', result: 'pass' },
149
- ];
150
- mockFs.readFileSync.mockReturnValue(mockTraces.map(t => JSON.stringify(t)).join('\n'));
151
-
152
- const mockSummarize = jest.fn().mockReturnValue([
153
- { constraintId: 'test1', level: 'iron_law', passRate: 0.8, failRate: 0.2, totalChecks: 10 },
154
- ]);
155
- const mockAnalyze = {
156
- summarize: mockSummarize,
157
- detectAnomalies: jest.fn().mockReturnValue([]),
158
- };
159
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
118
+ mockFs.readFileSync.mockReturnValue(traceFile([
119
+ ...repeats('test1', 4, 'pass'),
120
+ { constraintId: 'test1', result: 'fail' },
121
+ ]));
160
122
 
161
123
  await status({ detail: true }, io);
162
- expect(io.outText()).not.toBe('');
124
+ expect(io.outText()).toContain('检查: 5 | 通过: 80% | 失败: 20%');
163
125
  });
164
126
  });
165
127
 
166
128
  describe('状态文件更新', () => {
167
129
  it('应该更新 .state.json', async () => {
168
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test","level":"iron_law"}');
169
-
170
- const mockAnalyze = {
171
- summarize: jest.fn().mockReturnValue([]),
172
- detectAnomalies: jest.fn().mockReturnValue([]),
173
- };
174
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
130
+ mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
175
131
 
176
132
  await status({}, io);
177
133
  expect(mockFs.writeFileSync).toHaveBeenCalled();
@@ -180,110 +136,72 @@ describe('status command', () => {
180
136
 
181
137
  describe('Guidelines 统计', () => {
182
138
  it('应该显示 Guidelines 统计', async () => {
183
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
184
-
185
- const mockAnalyze = {
186
- summarize: jest.fn().mockReturnValue([
187
- { constraintId: 'test_guide', level: 'guideline', passRate: 0.9, failRate: 0.1, totalChecks: 10 },
188
- ]),
189
- detectAnomalies: jest.fn().mockReturnValue([]),
190
- };
191
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
139
+ mockFs.readFileSync.mockReturnValue(traceFile([
140
+ { constraintId: 'test_guide', level: 'guideline' },
141
+ ]));
192
142
 
193
143
  await status({}, io);
194
- expect(io.outText()).toContain('Guidelines');
144
+ expect(io.outText()).toContain('🟡 Guidelines:');
145
+ expect(io.outText()).toContain('✅ test_guide');
195
146
  });
196
147
  });
197
148
 
198
149
  describe('异常模式', () => {
199
150
  it('应该显示未发现异常当无异常', async () => {
200
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
201
-
202
- const mockAnalyze = {
203
- summarize: jest.fn().mockReturnValue([]),
204
- detectAnomalies: jest.fn().mockReturnValue([]),
205
- };
206
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
151
+ mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
207
152
 
208
153
  await status({ anomalies: true }, io);
209
- expect(io.outText()).toContain('未发现异常');
154
+ expect(io.outText()).toContain('未发现异常');
210
155
  });
211
156
 
212
157
  it('应该显示异常详情', async () => {
213
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
214
-
215
- const mockAnalyze = {
216
- summarize: jest.fn().mockReturnValue([]),
217
- detectAnomalies: jest.fn().mockReturnValue([
218
- { constraintId: 'test', type: 'high_failure_rate', data: { currentRate: 0.8, threshold: 0.5 } },
219
- ]),
220
- };
221
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
158
+ mockFs.readFileSync.mockReturnValue(traceFile([
159
+ { constraintId: 'test', result: 'fail' },
160
+ ]));
222
161
 
223
162
  await status({ anomalies: true }, io);
224
- expect(io.outText()).toContain('异常');
163
+ expect(io.outText()).toContain('当前值: 0');
164
+ expect(io.outText()).toContain('阈值: 0.3');
225
165
  expect(io.outText()).toContain('下一步建议');
226
166
  });
227
167
  });
228
168
 
229
169
  describe('建议', () => {
230
170
  it('应该显示良好建议当 trace >= 100', async () => {
231
- const traces = Array(100).fill('{"constraintId":"test"}').join('\n');
232
- mockFs.readFileSync.mockReturnValue(traces);
233
-
234
- const mockAnalyze = {
235
- summarize: jest.fn().mockReturnValue([]),
236
- detectAnomalies: jest.fn().mockReturnValue([]),
237
- };
238
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
171
+ mockFs.readFileSync.mockReturnValue(traceFile(repeats('test', 100, 'pass')));
239
172
 
240
173
  await status({}, io);
174
+ expect(io.outText()).toContain('记录数: 100 条');
241
175
  expect(io.outText()).toContain('状态良好');
242
176
  });
243
177
 
244
178
  it('应该显示积累数据建议当 trace < 100', async () => {
245
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
246
-
247
- const mockAnalyze = {
248
- summarize: jest.fn().mockReturnValue([]),
249
- detectAnomalies: jest.fn().mockReturnValue([]),
250
- };
251
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
179
+ mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
252
180
 
253
181
  await status({}, io);
254
182
  expect(io.outText()).toContain('继续积累数据');
255
183
  });
256
184
 
257
185
  it('应该显示诊断建议当有异常', async () => {
258
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
259
-
260
- const mockAnalyze = {
261
- summarize: jest.fn().mockReturnValue([]),
262
- detectAnomalies: jest.fn().mockReturnValue([
263
- { constraintId: 'test', type: 'high_failure_rate' },
264
- ]),
265
- };
266
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
186
+ mockFs.readFileSync.mockReturnValue(traceFile([
187
+ { constraintId: 'test', result: 'fail' },
188
+ ]));
267
189
 
268
- await status({}, io);
190
+ await status({ anomalies: true }, io);
269
191
  expect(io.outText()).toContain('harness status --detail');
270
192
  });
271
193
  });
272
194
 
273
- describe('详细模式', () => {
195
+ describe('详细模式扩展', () => {
274
196
  it('应该显示 Guidelines 详细统计', async () => {
275
- mockFs.readFileSync.mockReturnValue('{"constraintId":"test"}');
276
-
277
- const mockAnalyze = {
278
- summarize: jest.fn().mockReturnValue([
279
- { constraintId: 'test_guide', level: 'guideline', passRate: 0.6, failRate: 0.4, totalChecks: 10 },
280
- ]),
281
- detectAnomalies: jest.fn().mockReturnValue([]),
282
- };
283
- (MockTraceAnalyzer as any).mockImplementation(() => mockAnalyze);
197
+ mockFs.readFileSync.mockReturnValue(traceFile([
198
+ ...repeats('test_guide', 3, 'pass'),
199
+ ...repeats('test_guide', 2, 'fail'),
200
+ ].map(t => ({ ...t, level: 'guideline' as const }))));
284
201
 
285
202
  await status({ detail: true }, io);
286
- expect(io.outText()).toContain('检查:');
203
+ expect(io.outText()).toContain('⚠️ test_guide');
204
+ expect(io.outText()).toContain('检查: 5 | 通过: 60% | 失败: 40%');
287
205
  });
288
206
  });
289
207
  });
@@ -128,7 +128,6 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
128
128
  { flags: '-t, --test-command <command>', description: '测试命令' },
129
129
  { flags: '-p, --project-path <path>', description: '项目路径' },
130
130
  { flags: '--allow-partial', description: '允许部分测试通过', defaultValue: false },
131
- { flags: '--max-retries <n>', description: '最大重试次数', defaultValue: '2' },
132
131
  { flags: '--coverage', description: '检查测试覆盖率' },
133
132
  { flags: '--coverage-threshold <n>', description: '覆盖率阈值', defaultValue: '80' },
134
133
  ],
@@ -157,7 +156,7 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
157
156
  description: '生成检查报告',
158
157
  options: [
159
158
  { flags: '-o, --output <path>', description: '输出文件路径' },
160
- { flags: '-f, --format <format>', description: '输出格式 (json/markdown/html)', defaultValue: 'markdown' },
159
+ { flags: '-f, --format <format>', description: '输出格式 (json/markdown)', defaultValue: 'markdown' },
161
160
  { flags: '-p, --project-path <path>', description: '项目路径' },
162
161
  ],
163
162
  action: { module: 'report', export: 'report' },
@@ -298,22 +297,6 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
298
297
  action: { module: 'posteval-plan', export: 'postevalPlan' },
299
298
  mapActionArgs: (positionals) => [{ planPath: positionals[0] }],
300
299
  },
301
- {
302
- command: 'update-user-model',
303
- alias: 'uum',
304
- description: '从新对话中提取信号,更新用户思维模型(增量演化)',
305
- options: [
306
- { flags: '--json', description: 'JSON 格式输出', defaultValue: false },
307
- { flags: '--dry-run', description: '只显示变化,不更新状态', defaultValue: false },
308
- { flags: '--days <n>', description: '只处理最近 N 天的会话(自然日,含今天)' },
309
- ],
310
- action: { module: 'update-user-model', export: 'updateUserModel' },
311
- mapActionArgs: (_pos, options) => [{
312
- days: options.days !== undefined ? parseInt(String(options.days), 10) : undefined,
313
- json: options.json,
314
- dryRun: options.dryRun,
315
- }],
316
- },
317
300
  {
318
301
  command: 'release',
319
302
  description: 'npm 发布流水线:tsc → dist 验证 → npm version → git push → npm publish → gh release。不依赖 Studio API。',
@@ -324,20 +307,6 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
324
307
  action: { module: 'release', export: 'release' },
325
308
  mapActionArgs: (_pos, options) => [{ bumpType: options.bump, dryRun: options.dryRun }],
326
309
  },
327
- {
328
- command: 'analyze-sessions',
329
- alias: 'analyze',
330
- description: '分析 Claude Code 对话,挖掘纠正模式和高频概念,生成规则候选',
331
- options: [
332
- { flags: '-d, --days <n>', description: '分析最近 N 天的会话', defaultValue: '7' },
333
- { flags: '--json', description: 'JSON 格式输出', defaultValue: false },
334
- ],
335
- action: { module: 'analyze-sessions', export: 'analyzeSessions' },
336
- mapActionArgs: (_pos, options) => [{
337
- days: parseInt(String(options.days), 10),
338
- json: options.json,
339
- }],
340
- },
341
310
  {
342
311
  command: 'constraints',
343
312
  description: '输出约束集合元数据(版本、hash、计数、文本大小)',
@@ -19,8 +19,6 @@ export interface PassesGateOptions {
19
19
  projectPath?: string;
20
20
  /** 是否允许部分通过 */
21
21
  allowPartial?: boolean;
22
- /** 最大重试次数 */
23
- maxRetries?: number;
24
22
  }
25
23
 
26
24
  /**
@@ -54,7 +52,6 @@ export async function runPassesGate(
54
52
  testCommand,
55
53
  requireEvidence: false,
56
54
  allowPartialPass: options.allowPartial || false,
57
- maxRetries: options.maxRetries || 2,
58
55
  };
59
56
 
60
57
  // 执行测试
@@ -5,7 +5,8 @@
5
5
  */
6
6
 
7
7
  import chalk from 'chalk';
8
- import { PerformanceGate } from '../../gates/performance';
8
+ import { PerformanceGate, type ExtendedPerformanceGateConfig } from '../../gates/performance';
9
+ import type { GateContext, PerformanceThresholds } from '../../gates/types';
9
10
  import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
10
11
 
11
12
  export interface PerformanceOptions {
@@ -19,10 +20,6 @@ export interface PerformanceOptions {
19
20
  bundle?: boolean;
20
21
  /** 打包大小阈值(KB) */
21
22
  bundleThreshold?: number;
22
- /** 是否检查基准测试 */
23
- benchmark?: boolean;
24
- /** 基准测试超时(秒) */
25
- benchmarkTimeout?: number;
26
23
  }
27
24
 
28
25
  /**
@@ -36,34 +33,28 @@ export async function performance(
36
33
 
37
34
  const projectPath = options.projectPath || process.cwd();
38
35
 
39
- // 构建配置
40
- const config: any = {};
36
+ // 构建配置(架构评审候选2:键名对齐 PerformanceThresholds 真字段——
37
+ // thresholds.coverage / bundleSize 字节换算是错位键,旗帜恒不生效)
38
+ const config: Partial<ExtendedPerformanceGateConfig> = {};
39
+ const thresholds: PerformanceThresholds = {};
41
40
 
42
41
  if (options.coverage && options.coverageThreshold) {
43
- config.thresholds = {
44
- coverage: options.coverageThreshold,
45
- };
42
+ thresholds.minCoverage = options.coverageThreshold;
46
43
  }
47
44
 
48
45
  if (options.bundleThreshold) {
49
- config.thresholds = config.thresholds || {};
50
- config.thresholds.bundleSize = options.bundleThreshold * 1024; // KB to bytes
46
+ thresholds.maxBundleSize = options.bundleThreshold; // KB 直传,gate 按 KB 比较
51
47
  }
52
48
 
53
- if (options.benchmarkTimeout) {
54
- config.benchmarkTimeout = options.benchmarkTimeout * 1000; // seconds to ms
49
+ if (Object.keys(thresholds).length > 0) {
50
+ config.thresholds = thresholds;
55
51
  }
56
52
 
57
53
  // 创建性能门控实例
58
54
  const gate = new PerformanceGate(config);
59
55
 
60
56
  try {
61
- const result = await gate.check({
62
- projectPath,
63
- checkCoverage: options.coverage,
64
- checkBundle: options.bundle,
65
- checkBenchmark: options.benchmark,
66
- } as any);
57
+ const result = await gate.check({ projectPath } as GateContext);
67
58
 
68
59
  if (result.passed) {
69
60
  log(io);
@@ -83,13 +74,8 @@ export async function performance(
83
74
 
84
75
  if (metrics.bundleSize !== undefined) {
85
76
  const thresholdKB = options.bundleThreshold || 500;
86
- const actualKB = metrics.bundleSize / 1024;
87
- const status = actualKB <= thresholdKB ? '✅' : '❌';
88
- log(io, chalk.gray(` 打包大小: ${actualKB.toFixed(2)} KB ${status}`));
89
- }
90
-
91
- if (metrics.benchmarkTime !== undefined) {
92
- log(io, chalk.gray(` 基准测试: ${metrics.benchmarkTime}ms`));
77
+ const status = metrics.bundleSize <= thresholdKB ? '✅' : '❌';
78
+ log(io, chalk.gray(` 打包大小: ${metrics.bundleSize} KB ${status}`));
93
79
  }
94
80
  }
95
81
  } else {
@@ -8,7 +8,6 @@ import chalk from 'chalk';
8
8
  import * as fs from 'fs/promises';
9
9
  import { ConstraintChecker } from '../../core/constraints/checker';
10
10
  import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
11
- import { executeWithCollect } from '../../failure/constraint-handler';
12
11
  import { getTraceCollector } from '../../monitoring/traces';
13
12
  import type { ConstraintContext } from '../../types/constraint';
14
13
  import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
@@ -17,7 +16,7 @@ export interface ReportOptions {
17
16
  /** 输出文件路径 */
18
17
  output?: string;
19
18
  /** 输出格式 */
20
- format: 'json' | 'markdown' | 'html';
19
+ format: 'json' | 'markdown';
21
20
  /** 项目路径 */
22
21
  projectPath?: string;
23
22
  }
@@ -59,12 +58,8 @@ export async function report(options: ReportOptions, io: CommandIO = processIO):
59
58
  projectPath,
60
59
  };
61
60
 
62
- // S4: 使用 COLLECT 策略 — Iron Law 违规不抛异常,收集到结果中
63
- const { checkResult } = await executeWithCollect(() =>
64
- checker.checkConstraints(context)
65
- );
66
- // COLLECT 策略始终返回 checkResult
67
- const result = checkResult!;
61
+ // 收集模式(架构评审候选1):不抛、全量收集,铁律违规如实进报告
62
+ const result = await checker.collectConstraints(context);
68
63
 
69
64
  const failedIronLaws = result.ironLaws.filter(r => !r.satisfied);
70
65
  const failedGuidelines = result.guidelines.filter(r => !r.satisfied);
@@ -99,9 +94,6 @@ export async function report(options: ReportOptions, io: CommandIO = processIO):
99
94
  case 'markdown':
100
95
  content = generateMarkdownReport(reportData);
101
96
  break;
102
- case 'html':
103
- content = generateHtmlReport(reportData);
104
- break;
105
97
  default:
106
98
  content = JSON.stringify(reportData, null, 2);
107
99
  }
@@ -150,53 +142,3 @@ function generateMarkdownReport(data: ReportData): string {
150
142
  return lines.join('\n');
151
143
  }
152
144
 
153
- function generateHtmlReport(data: ReportData): string {
154
- const violationsHtml = data.constraints.violations.length > 0
155
- ? `<h3>违规项</h3><ul>${data.constraints.violations.map(v =>
156
- `<li><strong>${v.id}</strong> (${v.level}): ${v.message}</li>`
157
- ).join('')}</ul>`
158
- : '';
159
-
160
- return `<!DOCTYPE html>
161
- <html lang="zh-CN">
162
- <head>
163
- <meta charset="UTF-8">
164
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
165
- <title>Harness 检查报告</title>
166
- <style>
167
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
168
- h1 { color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }
169
- h2 { color: #555; margin-top: 30px; }
170
- table { width: 100%; border-collapse: collapse; margin: 15px 0; }
171
- th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
172
- th { background-color: #4CAF50; color: white; }
173
- tr:nth-child(even) { background-color: #f2f2f2; }
174
- .passed { color: #4CAF50; }
175
- .failed { color: #f44336; }
176
- .meta { color: #666; font-size: 0.9em; margin-bottom: 20px; }
177
- </style>
178
- </head>
179
- <body>
180
- <h1>Harness 检查报告</h1>
181
- <p class="meta">生成时间: ${data.timestamp}<br>项目路径: ${data.projectPath}</p>
182
-
183
- <h2>约束检查</h2>
184
- <table>
185
- <tr><th>指标</th><th>数值</th></tr>
186
- <tr><td>总约束</td><td>${data.constraints.total}</td></tr>
187
- <tr><td>Iron Laws</td><td>${data.constraints.ironLaws}</td></tr>
188
- <tr><td>Guidelines</td><td>${data.constraints.guidelines}</td></tr>
189
- <tr><td>Prompts</td><td>${data.constraints.prompts}</td></tr>
190
- <tr><td>通过</td><td class="passed">${data.constraints.passed}</td></tr>
191
- <tr><td>失败</td><td class="failed">${data.constraints.failed}</td></tr>
192
- <tr><td>警告</td><td>${data.constraints.warnings}</td></tr>
193
- </table>
194
-
195
- ${violationsHtml}
196
-
197
- <footer style="margin-top: 40px; color: #999; text-align: center;">
198
- 报告由 @dommaker/harness 生成
199
- </footer>
200
- </body>
201
- </html>`;
202
- }
@@ -7,8 +7,7 @@
7
7
  import chalk from 'chalk';
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
- import { TraceCollector } from '../../monitoring/traces';
11
- import { TraceAnalyzer } from '../../monitoring/trace-analyzer';
10
+ import { summarizeTraces, detectTraceAnomalies } from '../../monitoring/trace-analyzer';
12
11
  import { readJsonl } from '../../utils/jsonl';
13
12
  import { DEFAULT_TRACE_FILE } from '../../types/trace';
14
13
  import type { ExecutionTrace } from '../../types/trace';
@@ -57,18 +56,14 @@ export async function status(options: StatusOptions, io: CommandIO = processIO):
57
56
  // filter(Boolean) 沿用原语义:parse 成功但值为 falsy 的行(如 "null")不进分析
58
57
  // 计数去向:harness#100——skippedLines 另起一行走 stderr 告知(口径不改,只补告知)。
59
58
  // 不经 TraceCollector.readReport():本命令按 -p 的 projectPath 直读读链正本,且要的是
60
- // records + skippedLines 的原始行数口径;下面另建的 collector/analyzer 只用于 summarize/detectAnomalies
59
+ // records + skippedLines 的原始行数口径;下游只做纯判定(ADR-0020 直调模块级函数)
61
60
  const { records, skippedLines } = readJsonl<ExecutionTrace>(tracesPath, 'skip');
62
61
  const traces = records.filter(Boolean);
63
62
  const traceCount = records.length + skippedLines;
64
63
 
65
- // 创建收集器和分析器
66
- const collector = new TraceCollector({ traceFile: tracesPath });
67
- const analyzer = new TraceAnalyzer(collector);
68
-
69
- // 生成统计
70
- const summaries = analyzer.summarize(traces);
71
- const anomalies = analyzer.detectAnomalies(summaries);
64
+ // 生成统计(数据进数据出,无需 collector/分析器实例)
65
+ const summaries = summarizeTraces(traces);
66
+ const anomalies = detectTraceAnomalies(summaries);
72
67
 
73
68
  // 基本统计
74
69
  log(io, chalk.gray(`记录数: ${traceCount} 条`));
@@ -1,11 +1,10 @@
1
1
  # context/
2
2
 
3
3
  ## 职责
4
- 上下文管理:Token 预算(多级分配)、会话压缩、会话管理、知识注入。
4
+ 上下文管理:Token 预算(多级分配)、会话管理、知识注入。
5
5
 
6
6
  ## 核心导出
7
7
  - `TokenBudget` — 多级 token 预算(system/user/tool/reserve)
8
- - `Compaction` — 会话压缩(摘要/截断/滑动窗口)
9
8
  - `SessionManager` — 会话管理
10
9
  - `KnowledgeInjector` — 知识注入引擎
11
10
 
@@ -16,7 +15,7 @@
16
15
 
17
16
  ## 约定
18
17
  - Token 预算策略由配置文件控制
19
- - 会话压缩策略可组合
18
+ - 压缩只剩词汇不留引擎:`types.ts` 保留 `CompactionLevel`(被 `ContextUsageSnapshot.compactionLevel` 引用)与 `CompactionConfig`,但 `SessionCompaction` 引擎、`CompactionResult`、`DEFAULT_COMPACTION_CONFIG` 与 `AdaptiveTokenBudget` 已随 ADR-0022(零生产消费者)删除,本目录不再提供压缩策略组合点
20
19
 
21
20
  ## 注意事项
22
21
  - 零 Token 成本:纯计算,不调用 LLM