@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
@@ -1,334 +0,0 @@
1
- /**
2
- * SessionStartup 测试
3
- */
4
-
5
- import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
6
- import { SessionStartup, createSessionStartup, DEFAULT_CODE_CHECKPOINTS, MINIMAL_CHECKPOINTS } from '../core/session/startup';
7
- import * as fs from 'fs';
8
- import * as path from 'path';
9
- import * as yaml from 'js-yaml';
10
-
11
- describe('SessionStartup', () => {
12
- const tempDir = path.join(process.cwd(), 'temp-test-session');
13
-
14
- beforeAll(() => {
15
- fs.mkdirSync(tempDir, { recursive: true });
16
- });
17
-
18
- afterAll(() => {
19
- try {
20
- fs.rmSync(tempDir, { recursive: true, force: true });
21
- } catch {
22
- // ignore
23
- }
24
- });
25
-
26
- describe('run', () => {
27
- it('应该运行 pwd 检查点', async () => {
28
- const startup = new SessionStartup(tempDir, {
29
- required: ['pwd'],
30
- optional: [],
31
- });
32
-
33
- const result = await startup.run();
34
-
35
- expect(result.success).toBe(true);
36
- expect(result.results.length).toBe(1);
37
- expect(result.results[0].type).toBe('pwd');
38
- });
39
-
40
- it('应该运行 git_status 检查点', async () => {
41
- const startup = new SessionStartup(tempDir, {
42
- required: ['git_status'],
43
- optional: [],
44
- });
45
-
46
- const result = await startup.run();
47
-
48
- expect(result.results.length).toBe(1);
49
- expect(result.results[0].type).toBe('git_status');
50
- });
51
-
52
- it('可选检查点失败不应该影响 success', async () => {
53
- const startup = new SessionStartup(tempDir, {
54
- required: ['pwd'],
55
- optional: ['git_log'],
56
- });
57
-
58
- const result = await startup.run();
59
-
60
- expect(result.success).toBe(true);
61
- expect(result.results.length).toBe(2);
62
- });
63
-
64
- it('必要检查点失败应该影响 success', async () => {
65
- const startup = new SessionStartup('/nonexistent/path', {
66
- required: ['pwd'],
67
- optional: [],
68
- });
69
-
70
- const result = await startup.run();
71
-
72
- expect(result.results.length).toBe(1);
73
- });
74
- });
75
-
76
- describe('检查点数据', () => {
77
- it('pwd 应该返回当前目录', async () => {
78
- const startup = new SessionStartup(tempDir, {
79
- required: ['pwd'],
80
- optional: [],
81
- });
82
-
83
- const result = await startup.run();
84
- const pwdResult = result.results.find(r => r.type === 'pwd');
85
-
86
- expect(pwdResult?.data).toBeDefined();
87
- });
88
- });
89
-
90
- describe('git_log 检查点', () => {
91
- it('应该返回 git log 数据', async () => {
92
- const startup = new SessionStartup(tempDir, { required: ['git_log'], optional: [] });
93
- const result = await startup.run();
94
- expect(result.results[0].type).toBe('git_log');
95
- expect(result.results[0].success).toBe(true);
96
- });
97
- });
98
-
99
- describe('read_task_list 检查点', () => {
100
- it('tasks.yml 存在应该解析 YAML', async () => {
101
- const tasksPath = path.join(tempDir, 'tasks.yml');
102
- const taskData = {
103
- tasks: [
104
- { id: 't1', name: 'task 1', passes: true },
105
- { id: 't2', name: 'task 2', passes: false },
106
- ],
107
- };
108
- fs.writeFileSync(tasksPath, yaml.dump(taskData));
109
-
110
- const startup = new SessionStartup(tempDir, { required: ['read_task_list'], optional: [] });
111
- const result = await startup.run();
112
- expect(result.results[0].success).toBe(true);
113
- expect(result.results[0].data).toBeDefined();
114
- expect(result.results[0].data.tasks.length).toBe(2);
115
-
116
- fs.unlinkSync(tasksPath);
117
- });
118
-
119
- it('tasks.json 存在应该回退到 JSON', async () => {
120
- const tasksPath = path.join(tempDir, 'tasks.json');
121
- const taskData = {
122
- tasks: [
123
- { id: 't1', name: 'task 1', passes: true },
124
- ],
125
- };
126
- fs.writeFileSync(tasksPath, JSON.stringify(taskData));
127
-
128
- const startup = new SessionStartup(tempDir, { required: ['read_task_list'], optional: [] });
129
- const result = await startup.run();
130
- expect(result.results[0].success).toBe(true);
131
- expect(result.results[0].data.tasks.length).toBe(1);
132
-
133
- fs.unlinkSync(tasksPath);
134
- });
135
-
136
- it('无任务文件应该返回 null', async () => {
137
- const noTasksDir = path.join(tempDir, 'no-tasks');
138
- fs.mkdirSync(noTasksDir, { recursive: true });
139
-
140
- const startup = new SessionStartup(noTasksDir, { required: ['read_task_list'], optional: [] });
141
- const result = await startup.run();
142
- expect(result.results[0].success).toBe(true);
143
- expect(result.results[0].data).toBeNull();
144
-
145
- fs.rmSync(noTasksDir, { recursive: true, force: true });
146
- });
147
- });
148
-
149
- describe('read_progress 检查点', () => {
150
- it('progress.yml 存在应该解析', async () => {
151
- const harnessDir = path.join(tempDir, '.harness');
152
- fs.mkdirSync(harnessDir, { recursive: true });
153
- const progressPath = path.join(harnessDir, 'progress.yml');
154
- fs.writeFileSync(progressPath, yaml.dump({ step: 3, total: 10 }));
155
-
156
- const startup = new SessionStartup(tempDir, { required: ['read_progress'], optional: [] });
157
- const result = await startup.run();
158
- expect(result.results[0].success).toBe(true);
159
- expect(result.results[0].data).toEqual({ step: 3, total: 10 });
160
-
161
- fs.unlinkSync(progressPath);
162
- fs.rmdirSync(harnessDir);
163
- });
164
-
165
- it('无 progress.yml 应该返回 null', async () => {
166
- const noProgressDir = path.join(tempDir, 'no-progress');
167
- fs.mkdirSync(noProgressDir, { recursive: true });
168
-
169
- const startup = new SessionStartup(noProgressDir, { required: ['read_progress'], optional: [] });
170
- const result = await startup.run();
171
- expect(result.results[0].success).toBe(true);
172
- expect(result.results[0].data).toBeNull();
173
-
174
- fs.rmSync(noProgressDir, { recursive: true, force: true });
175
- });
176
- });
177
-
178
- describe('init_sh 检查点', () => {
179
- it('init.sh 存在应该返回 true', async () => {
180
- const initPath = path.join(tempDir, 'init.sh');
181
- fs.writeFileSync(initPath, '#!/bin/bash\necho "init"');
182
-
183
- const startup = new SessionStartup(tempDir, { required: ['init_sh'], optional: [] });
184
- const result = await startup.run();
185
- expect(result.results[0].success).toBe(true);
186
- expect(result.results[0].data).toBe(true);
187
-
188
- fs.unlinkSync(initPath);
189
- });
190
-
191
- it('init.sh 不存在应该返回 false', async () => {
192
- const noInitDir = path.join(tempDir, 'no-init');
193
- fs.mkdirSync(noInitDir, { recursive: true });
194
-
195
- const startup = new SessionStartup(noInitDir, { required: ['init_sh'], optional: [] });
196
- const result = await startup.run();
197
- expect(result.results[0].success).toBe(true);
198
- expect(result.results[0].data).toBe(false);
199
-
200
- fs.rmSync(noInitDir, { recursive: true, force: true });
201
- });
202
- });
203
-
204
- describe('basic_verification 检查点', () => {
205
- it('应该检测 package.json 和 README.md', async () => {
206
- const verDir = path.join(tempDir, 'verification');
207
- fs.mkdirSync(verDir, { recursive: true });
208
- fs.writeFileSync(path.join(verDir, 'package.json'), '{}');
209
- fs.writeFileSync(path.join(verDir, 'README.md'), '# Test');
210
-
211
- const startup = new SessionStartup(verDir, { required: ['basic_verification'], optional: [] });
212
- const result = await startup.run();
213
- expect(result.results[0].success).toBe(true);
214
- expect(result.results[0].data.hasPackageJson).toBe(true);
215
- expect(result.results[0].data.hasReadme).toBe(true);
216
-
217
- fs.rmSync(verDir, { recursive: true, force: true });
218
- });
219
- });
220
-
221
- describe('load_context 检查点', () => {
222
- it('应该加载存在的上下文文件', async () => {
223
- const ctxDir = path.join(tempDir, 'context-test');
224
- fs.mkdirSync(ctxDir, { recursive: true });
225
- fs.writeFileSync(path.join(ctxDir, 'AGENTS.md'), '# Agents');
226
- fs.writeFileSync(path.join(ctxDir, 'USER.md'), '# User');
227
-
228
- const startup = new SessionStartup(ctxDir, { required: ['load_context'], optional: [], contextFiles: ['AGENTS.md', 'USER.md'] });
229
- const result = await startup.run();
230
- expect(result.results[0].success).toBe(true);
231
- expect(result.results[0].data['AGENTS.md']).toBe('# Agents');
232
- expect(result.results[0].data['USER.md']).toBe('# User');
233
-
234
- fs.rmSync(ctxDir, { recursive: true, force: true });
235
- });
236
- });
237
-
238
- describe('未知检查点类型', () => {
239
- it('应该返回失败结果', async () => {
240
- const startup = new SessionStartup(tempDir, { required: ['unknown_type' as any], optional: [] });
241
- const result = await startup.run();
242
- expect(result.success).toBe(false);
243
- expect(result.results[0].success).toBe(false);
244
- expect(result.results[0].error).toContain('Unknown checkpoint type');
245
- });
246
- });
247
-
248
- describe('getCurrentTask', () => {
249
- it('应该返回第一个未通过的任务', async () => {
250
- const tasksPath = path.join(tempDir, 'tasks.yml');
251
- const taskData = {
252
- tasks: [
253
- { id: 't1', name: 'done', passes: true },
254
- { id: 't2', name: 'current', passes: false },
255
- { id: 't3', name: 'future', passes: false },
256
- ],
257
- };
258
- fs.writeFileSync(tasksPath, yaml.dump(taskData));
259
-
260
- const startup = new SessionStartup(tempDir, { required: [], optional: [] });
261
- const current = await startup.getCurrentTask();
262
- expect(current).not.toBeNull();
263
- expect(current!.task.id).toBe('t2');
264
- expect(current!.index).toBe(1);
265
-
266
- fs.unlinkSync(tasksPath);
267
- });
268
-
269
- it('所有任务通过应该返回 null', async () => {
270
- const tasksPath = path.join(tempDir, 'tasks.yml');
271
- const taskData = {
272
- tasks: [
273
- { id: 't1', name: 'done', passes: true },
274
- ],
275
- };
276
- fs.writeFileSync(tasksPath, yaml.dump(taskData));
277
-
278
- const startup = new SessionStartup(tempDir, { required: [], optional: [] });
279
- const current = await startup.getCurrentTask();
280
- expect(current).toBeNull();
281
-
282
- fs.unlinkSync(tasksPath);
283
- });
284
-
285
- it('无任务文件应该返回 null', async () => {
286
- const noTasksDir = path.join(tempDir, 'no-tasks-get');
287
- fs.mkdirSync(noTasksDir, { recursive: true });
288
-
289
- const startup = new SessionStartup(noTasksDir, { required: [], optional: [] });
290
- const current = await startup.getCurrentTask();
291
- expect(current).toBeNull();
292
-
293
- fs.rmSync(noTasksDir, { recursive: true, force: true });
294
- });
295
- });
296
-
297
- describe('generateReport', () => {
298
- it('应该生成报告', () => {
299
- const startup = new SessionStartup(tempDir, { required: [], optional: [] });
300
- const report = startup.generateReport([
301
- { type: 'pwd', success: true, data: '/tmp', duration: 5 },
302
- { type: 'git_log', success: false, error: 'not a git repo', duration: 10 },
303
- ]);
304
-
305
- expect(report).toContain('Session Startup Report');
306
- expect(report).toContain('pwd');
307
- expect(report).toContain('git_log');
308
- expect(report).toContain('not a git repo');
309
- });
310
- });
311
-
312
- describe('createSessionStartup 工厂函数', () => {
313
- it('应该创建 SessionStartup 实例', () => {
314
- const startup = createSessionStartup(tempDir, { required: ['pwd'], optional: [] });
315
- expect(startup).toBeInstanceOf(SessionStartup);
316
- });
317
- });
318
-
319
- describe('常量', () => {
320
- it('DEFAULT_CODE_CHECKPOINTS 应该有正确的结构', () => {
321
- expect(DEFAULT_CODE_CHECKPOINTS.required).toContain('pwd');
322
- expect(DEFAULT_CODE_CHECKPOINTS.required).toContain('git_log');
323
- expect(DEFAULT_CODE_CHECKPOINTS.required).toContain('read_task_list');
324
- expect(DEFAULT_CODE_CHECKPOINTS.optional).toContain('read_progress');
325
- expect(DEFAULT_CODE_CHECKPOINTS.timeout).toBe(30000);
326
- });
327
-
328
- it('MINIMAL_CHECKPOINTS 应该有正确的结构', () => {
329
- expect(MINIMAL_CHECKPOINTS.required).toContain('pwd');
330
- expect(MINIMAL_CHECKPOINTS.required).toContain('read_task_list');
331
- expect(MINIMAL_CHECKPOINTS.optional).toEqual([]);
332
- });
333
- });
334
- });
@@ -1,184 +0,0 @@
1
- /**
2
- * analyze-sessions 命令测试(O6)
3
- *
4
- * Seam:analyzeSessions(options) 公开入口。
5
- * 隔离面:
6
- * - os.homedir → 测试临时目录(memory 规则库落 TEST_HOME)
7
- * - readTranscriptSessions → fixture 会话(extractCorrectionMatches/tokenize 等纯函数保持真实)
8
- * - CLAUDE_TRANSCRIPTS_DIR → 测试临时目录(决定 transcripts 目录是否存在)
9
- */
10
-
11
- import * as fs from 'fs';
12
- import { captureIO, lastJsonOutput, type CapturingIO } from '../../command-contract';
13
- import * as path from 'path';
14
- import { analyzeSessions } from '../analyze-sessions';
15
- import { readTranscriptSessions, type MinedSession } from '../../session-mining';
16
-
17
- const TEST_HOME = '/tmp/harness-analyze-test-home';
18
-
19
- jest.mock('os', () => ({
20
- ...jest.requireActual('os'),
21
- homedir: () => '/tmp/harness-analyze-test-home',
22
- }));
23
-
24
- jest.mock('../../session-mining', () => ({
25
- ...jest.requireActual('../../session-mining'),
26
- readTranscriptSessions: jest.fn(),
27
- }));
28
-
29
- jest.mock('chalk', () => {
30
- const id = (s: string) => s;
31
- const chalkFn = Object.assign(id, {
32
- gray: id,
33
- blue: id,
34
- green: id,
35
- yellow: id,
36
- cyan: id,
37
- bold: id,
38
- red: id,
39
- });
40
- return {
41
- __esModule: true,
42
- default: chalkFn,
43
- gray: id,
44
- blue: id,
45
- green: id,
46
- yellow: id,
47
- cyan: id,
48
- bold: id,
49
- red: id,
50
- };
51
- });
52
-
53
- const mockReadTranscriptSessions = readTranscriptSessions as jest.MockedFunction<
54
- typeof readTranscriptSessions
55
- >;
56
-
57
- const TRANSCRIPTS_DIR = path.join(TEST_HOME, 'transcripts');
58
-
59
- function mkSession(partial: Partial<MinedSession> = {}): MinedSession {
60
- return {
61
- id: 'session-1',
62
- date: '2026-08-15',
63
- mtimeMs: Date.now(),
64
- turns: [
65
- { role: 'user', content: '我不是说要用中文吗' },
66
- { role: 'assistant', content: '收到,改用中文' },
67
- ],
68
- toolCalls: [],
69
- ...partial,
70
- };
71
- }
72
-
73
- let io: CapturingIO;
74
- beforeEach(() => {
75
- io = captureIO();
76
- });
77
-
78
- describe('analyze-sessions command', () => {
79
-
80
- beforeEach(() => {
81
- jest.clearAllMocks();
82
- fs.rmSync(TEST_HOME, { recursive: true, force: true });
83
- fs.mkdirSync(path.join(TEST_HOME, '.claude', 'projects', '-root-projects', 'memory'), { recursive: true });
84
- fs.mkdirSync(TRANSCRIPTS_DIR, { recursive: true });
85
- process.env.CLAUDE_TRANSCRIPTS_DIR = TRANSCRIPTS_DIR;
86
- });
87
-
88
- afterEach(() => {
89
- delete process.env.CLAUDE_TRANSCRIPTS_DIR;
90
- });
91
-
92
- test('transcripts 目录不存在:提示 No transcripts directory found', async () => {
93
- process.env.CLAUDE_TRANSCRIPTS_DIR = path.join(TEST_HOME, 'missing-dir');
94
-
95
- const result = await analyzeSessions({}, io);
96
-
97
- expect(io.outText()).toContain('No transcripts directory found');
98
- expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('会话记录目录不存在') });
99
- expect(mockReadTranscriptSessions).not.toHaveBeenCalled();
100
- });
101
-
102
- test('窗口内无会话:提示 No sessions found(默认最近 7 天)', async () => {
103
- const before = Date.now() - 7 * 86_400_000;
104
- mockReadTranscriptSessions.mockReturnValue([]);
105
-
106
- const result = await analyzeSessions({}, io);
107
- const after = Date.now() - 7 * 86_400_000;
108
-
109
- expect(io.outText()).toContain('No sessions found in the last 7 days');
110
- expect(result).toEqual({ kind: 'skip', reason: '最近 7 天没有会话' });
111
- // since 过滤下推到 seam:默认窗口 7 天
112
- const filter = mockReadTranscriptSessions.mock.calls[0][1] as { since?: number };
113
- expect(filter.since).toBeGreaterThanOrEqual(before);
114
- expect(filter.since).toBeLessThanOrEqual(after);
115
- });
116
-
117
- test('--days 1:since 过滤下推到 seam(mtimeMs 窗口)', async () => {
118
- const before = Date.now() - 86_400_000;
119
- mockReadTranscriptSessions.mockReturnValue([mkSession({ id: 'today' })]);
120
-
121
- await analyzeSessions({ days: 1 }, io);
122
- const after = Date.now() - 86_400_000;
123
-
124
- expect(io.outText()).toContain('Analyzing 1 sessions (last 1 days)');
125
- const filter = mockReadTranscriptSessions.mock.calls[0][1] as { since?: number };
126
- expect(filter.since).toBeGreaterThanOrEqual(before);
127
- expect(filter.since).toBeLessThanOrEqual(after);
128
- });
129
-
130
- test('缺省 --days:窗口为 7 天,两天前会话仍计入', async () => {
131
- mockReadTranscriptSessions.mockReturnValue([
132
- mkSession({ id: 'today' }),
133
- mkSession({ id: 'two-days-ago', mtimeMs: Date.now() - 2 * 86_400_000 }),
134
- ]);
135
-
136
- await analyzeSessions({}, io);
137
-
138
- expect(io.outText()).toContain('Analyzing 2 sessions (last 7 days)');
139
- });
140
-
141
- test('--json:纠正句跨 3 会话聚合为 correction 候选', async () => {
142
- mockReadTranscriptSessions.mockReturnValue([
143
- mkSession({ id: 's1' }),
144
- mkSession({ id: 's2' }),
145
- mkSession({ id: 's3' }),
146
- ]);
147
-
148
- await analyzeSessions({ json: true }, io);
149
-
150
- const output = lastJsonOutput(io);
151
- expect(output.sessions).toBe(3);
152
- expect(output.corrections).toBe(3);
153
- expect(output.candidates).toEqual(
154
- expect.arrayContaining([
155
- expect.objectContaining({
156
- source: 'correction',
157
- frequency: 3,
158
- pattern: '我不是说',
159
- }),
160
- ]),
161
- );
162
- });
163
-
164
- test('--json:跨会话重复概念产出 ngram 候选', async () => {
165
- const ngramText = { role: 'user', content: '数据库迁移方案的详细讨论' };
166
- mockReadTranscriptSessions.mockReturnValue([
167
- mkSession({
168
- id: 's1',
169
- turns: [ngramText, { role: 'assistant', content: '好的' }],
170
- }),
171
- mkSession({
172
- id: 's2',
173
- turns: [ngramText, { role: 'assistant', content: '明白' }],
174
- }),
175
- ]);
176
-
177
- await analyzeSessions({ json: true }, io);
178
-
179
- const output = lastJsonOutput(io);
180
- const ngramCandidates = (output.candidates as Array<{ source: string }>)
181
- .filter(c => c.source === 'ngram');
182
- expect(ngramCandidates.length).toBeGreaterThan(0);
183
- });
184
- });