@dommaker/harness 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +0 -2
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +1 -32
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/passes-gate.d.ts +0 -2
- package/dist/cli/commands/passes-gate.d.ts.map +1 -1
- package/dist/cli/commands/passes-gate.js +0 -1
- package/dist/cli/commands/passes-gate.js.map +1 -1
- package/dist/cli/commands/performance.d.ts +0 -4
- package/dist/cli/commands/performance.d.ts.map +1 -1
- package/dist/cli/commands/performance.js +10 -20
- package/dist/cli/commands/performance.js.map +1 -1
- package/dist/cli/commands/report.d.ts +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +2 -55
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +4 -8
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/context/index.d.ts +2 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -8
- package/dist/context/index.js.map +1 -1
- package/dist/context/token-budget.d.ts +0 -29
- package/dist/context/token-budget.d.ts.map +1 -1
- package/dist/context/token-budget.js +1 -65
- package/dist/context/token-budget.js.map +1 -1
- package/dist/context/types.d.ts +0 -1
- package/dist/context/types.d.ts.map +1 -1
- package/dist/context/types.js +0 -9
- package/dist/context/types.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +14 -0
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +22 -2
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/git-evidence.d.ts +6 -3
- package/dist/core/constraints/git-evidence.d.ts.map +1 -1
- package/dist/core/constraints/git-evidence.js +6 -3
- package/dist/core/constraints/git-evidence.js.map +1 -1
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/spec/validator.d.ts +2 -0
- package/dist/core/spec/validator.d.ts.map +1 -1
- package/dist/core/spec/validator.js +9 -11
- package/dist/core/spec/validator.js.map +1 -1
- package/dist/core/validators/index.d.ts +1 -1
- package/dist/core/validators/index.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +1 -15
- package/dist/core/validators/passes-gate.d.ts.map +1 -1
- package/dist/core/validators/passes-gate.js +0 -125
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/failure/index.d.ts +0 -1
- package/dist/failure/index.d.ts.map +1 -1
- package/dist/failure/index.js +1 -7
- package/dist/failure/index.js.map +1 -1
- package/dist/gates/definitions.d.ts.map +1 -1
- package/dist/gates/definitions.js +0 -4
- package/dist/gates/definitions.js.map +1 -1
- package/dist/gates/performance.d.ts +7 -22
- package/dist/gates/performance.d.ts.map +1 -1
- package/dist/gates/performance.js +7 -89
- package/dist/gates/performance.js.map +1 -1
- package/dist/gates/types.d.ts +2 -8
- package/dist/gates/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +0 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -3
- package/dist/knowledge/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts +25 -10
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +147 -116
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/types/passes-gate.d.ts +0 -19
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/passes-gate-check.test.ts +20 -4
- package/src/__tests__/passes-gate.test.ts +5 -99
- package/src/__tests__/performance-gate.test.ts +3 -11
- package/src/__tests__/public-exports.test.ts +46 -16
- package/src/__tests__/trace-analyzer.test.ts +52 -2
- package/src/cli/commands/CONTEXT.md +3 -2
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +70 -0
- package/src/cli/commands/__tests__/passes-gate.test.ts +7 -3
- package/src/cli/commands/__tests__/performance.test.ts +5 -16
- package/src/cli/commands/__tests__/registry.test.ts +46 -3
- package/src/cli/commands/__tests__/report-extra.test.ts +0 -19
- package/src/cli/commands/__tests__/report-false-green.test.ts +46 -0
- package/src/cli/commands/__tests__/report.test.ts +0 -20
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +68 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +63 -116
- package/src/cli/commands/__tests__/status.test.ts +66 -148
- package/src/cli/commands/definitions.ts +1 -32
- package/src/cli/commands/passes-gate.ts +0 -3
- package/src/cli/commands/performance.ts +13 -27
- package/src/cli/commands/report.ts +3 -61
- package/src/cli/commands/status.ts +5 -10
- package/src/context/CONTEXT.md +2 -3
- package/src/context/__tests__/token-budget.test.ts +1 -81
- package/src/context/index.ts +2 -7
- package/src/context/token-budget.ts +0 -73
- package/src/context/types.ts +0 -9
- package/src/core/CONTEXT.md +5 -6
- package/src/core/constraints/__tests__/collect-constraints.test.ts +66 -0
- package/src/core/constraints/checker.ts +33 -2
- package/src/core/constraints/git-evidence.ts +6 -3
- package/src/core/index.ts +0 -11
- package/src/core/spec/__tests__/validator.test.ts +24 -0
- package/src/core/spec/validator.ts +9 -10
- package/src/core/validators/__tests__/passes-gate.test.ts +2 -9
- package/src/core/validators/index.ts +0 -1
- package/src/core/validators/passes-gate.ts +1 -149
- package/src/failure/CONTEXT.md +8 -6
- package/src/failure/index.ts +0 -10
- package/src/gates/CONTEXT.md +3 -2
- package/src/gates/__tests__/performance.test.ts +6 -74
- package/src/gates/definitions.ts +0 -4
- package/src/gates/performance.ts +9 -111
- package/src/gates/types.ts +2 -8
- package/src/index.ts +1 -36
- package/src/knowledge/CONTEXT.md +1 -2
- package/src/knowledge/index.ts +0 -1
- package/src/monitoring/CONTEXT.md +2 -2
- package/src/monitoring/trace-analyzer.ts +160 -126
- package/src/types/passes-gate.ts +0 -26
- package/src/utils/__tests__/jsonl-skip-disposition.test.ts +0 -1
- package/dist/cli/commands/analyze-sessions.d.ts +0 -20
- package/dist/cli/commands/analyze-sessions.d.ts.map +0 -1
- package/dist/cli/commands/analyze-sessions.js +0 -290
- package/dist/cli/commands/analyze-sessions.js.map +0 -1
- package/dist/cli/commands/update-user-model.d.ts +0 -30
- package/dist/cli/commands/update-user-model.d.ts.map +0 -1
- package/dist/cli/commands/update-user-model.js +0 -406
- package/dist/cli/commands/update-user-model.js.map +0 -1
- package/dist/cli/session-mining/corrections.d.ts +0 -17
- package/dist/cli/session-mining/corrections.d.ts.map +0 -1
- package/dist/cli/session-mining/corrections.js +0 -58
- package/dist/cli/session-mining/corrections.js.map +0 -1
- package/dist/cli/session-mining/index.d.ts +0 -13
- package/dist/cli/session-mining/index.d.ts.map +0 -1
- package/dist/cli/session-mining/index.js +0 -29
- package/dist/cli/session-mining/index.js.map +0 -1
- package/dist/cli/session-mining/text.d.ts +0 -31
- package/dist/cli/session-mining/text.d.ts.map +0 -1
- package/dist/cli/session-mining/text.js +0 -126
- package/dist/cli/session-mining/text.js.map +0 -1
- package/dist/cli/session-mining/transcript.d.ts +0 -34
- package/dist/cli/session-mining/transcript.d.ts.map +0 -1
- package/dist/cli/session-mining/transcript.js +0 -130
- package/dist/cli/session-mining/transcript.js.map +0 -1
- package/dist/context/compaction.d.ts +0 -57
- package/dist/context/compaction.d.ts.map +0 -1
- package/dist/context/compaction.js +0 -165
- package/dist/context/compaction.js.map +0 -1
- package/dist/core/session/clean-state.d.ts +0 -17
- package/dist/core/session/clean-state.d.ts.map +0 -1
- package/dist/core/session/clean-state.js +0 -192
- package/dist/core/session/clean-state.js.map +0 -1
- package/dist/core/session/index.d.ts +0 -6
- package/dist/core/session/index.d.ts.map +0 -1
- package/dist/core/session/index.js +0 -15
- package/dist/core/session/index.js.map +0 -1
- package/dist/core/session/startup.d.ts +0 -47
- package/dist/core/session/startup.d.ts.map +0 -1
- package/dist/core/session/startup.js +0 -263
- package/dist/core/session/startup.js.map +0 -1
- package/dist/failure/constraint-handler.d.ts +0 -83
- package/dist/failure/constraint-handler.d.ts.map +0 -1
- package/dist/failure/constraint-handler.js +0 -138
- package/dist/failure/constraint-handler.js.map +0 -1
- package/dist/knowledge/lifecycle-hooks.d.ts +0 -62
- package/dist/knowledge/lifecycle-hooks.d.ts.map +0 -1
- package/dist/knowledge/lifecycle-hooks.js +0 -143
- package/dist/knowledge/lifecycle-hooks.js.map +0 -1
- package/dist/types/session.d.ts +0 -113
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/session.js +0 -6
- package/dist/types/session.js.map +0 -1
- package/src/__tests__/clean-state-extra.test.ts +0 -301
- package/src/__tests__/clean-state.test.ts +0 -169
- package/src/__tests__/constraint-handler.test.ts +0 -132
- package/src/__tests__/passes-gate-extra.test.ts +0 -93
- package/src/__tests__/session-startup.test.ts +0 -334
- package/src/cli/commands/__tests__/analyze-sessions.test.ts +0 -184
- package/src/cli/commands/__tests__/update-user-model.test.ts +0 -268
- package/src/cli/commands/analyze-sessions.ts +0 -335
- package/src/cli/commands/update-user-model.ts +0 -476
- package/src/cli/session-mining/__tests__/transcript.test.ts +0 -118
- package/src/cli/session-mining/corrections.ts +0 -56
- package/src/cli/session-mining/index.ts +0 -13
- package/src/cli/session-mining/text.ts +0 -117
- package/src/cli/session-mining/transcript.ts +0 -125
- package/src/context/__tests__/compaction.test.ts +0 -169
- package/src/context/compaction.ts +0 -201
- package/src/core/session/__tests__/startup.test.ts +0 -281
- package/src/core/session/clean-state.ts +0 -178
- package/src/core/session/index.ts +0 -15
- package/src/core/session/startup.ts +0 -253
- package/src/failure/constraint-handler.ts +0 -171
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +0 -140
- package/src/knowledge/lifecycle-hooks.ts +0 -202
- package/src/types/session.ts +0 -132
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SessionStartup 测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SessionStartup } from '../startup';
|
|
6
|
-
import { exec } from 'child_process';
|
|
7
|
-
import * as fs from 'fs/promises';
|
|
8
|
-
|
|
9
|
-
// Mock dependencies
|
|
10
|
-
jest.mock('child_process', () => ({
|
|
11
|
-
exec: jest.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
jest.mock('fs/promises', () => ({
|
|
15
|
-
readFile: jest.fn(),
|
|
16
|
-
access: jest.fn(),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
const mockExec = exec as unknown as jest.Mock;
|
|
20
|
-
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
21
|
-
|
|
22
|
-
describe('SessionStartup', () => {
|
|
23
|
-
let startup: SessionStartup;
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
jest.clearAllMocks();
|
|
27
|
-
startup = new SessionStartup('/test/project', {
|
|
28
|
-
required: ['pwd'],
|
|
29
|
-
optional: [],
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('constructor', () => {
|
|
34
|
-
it('should accept workDir and checkpoints', () => {
|
|
35
|
-
expect(startup).toBeDefined();
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('run()', () => {
|
|
40
|
-
it('should run required checkpoints', async () => {
|
|
41
|
-
const result = await startup.run();
|
|
42
|
-
|
|
43
|
-
expect(result.results.length).toBe(1);
|
|
44
|
-
expect(result.results[0].type).toBe('pwd');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should return success when all required pass', async () => {
|
|
48
|
-
const result = await startup.run();
|
|
49
|
-
|
|
50
|
-
expect(result.success).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should run optional checkpoints', async () => {
|
|
54
|
-
const s = new SessionStartup('/test', {
|
|
55
|
-
required: ['pwd'],
|
|
56
|
-
optional: ['git_status'],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
60
|
-
callback(null, { stdout: '', stderr: '' });
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const result = await s.run();
|
|
64
|
-
|
|
65
|
-
expect(result.results.length).toBe(2);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('checkpoints', () => {
|
|
70
|
-
describe('pwd', () => {
|
|
71
|
-
it('should return workDir', async () => {
|
|
72
|
-
const s = new SessionStartup('/my/project', {
|
|
73
|
-
required: ['pwd'],
|
|
74
|
-
optional: [],
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const result = await s.run();
|
|
78
|
-
|
|
79
|
-
expect(result.results[0].success).toBe(true);
|
|
80
|
-
expect(result.results[0].data).toBe('/my/project');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('git_log', () => {
|
|
85
|
-
it('should check git log', async () => {
|
|
86
|
-
const s = new SessionStartup('/test', {
|
|
87
|
-
required: ['git_log'],
|
|
88
|
-
optional: [],
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
92
|
-
callback(null, { stdout: 'abc123 Initial commit', stderr: '' });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const result = await s.run();
|
|
96
|
-
|
|
97
|
-
expect(result.results[0].success).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('git_status', () => {
|
|
102
|
-
it('should check git status', async () => {
|
|
103
|
-
const s = new SessionStartup('/test', {
|
|
104
|
-
required: ['git_status'],
|
|
105
|
-
optional: [],
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
109
|
-
if (cmd.includes('git status')) {
|
|
110
|
-
callback(null, { stdout: 'On branch main\nnothing to commit', stderr: '' });
|
|
111
|
-
} else {
|
|
112
|
-
callback(null, { stdout: '', stderr: '' });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const result = await s.run();
|
|
117
|
-
|
|
118
|
-
expect(result.results[0].success).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('read_progress', () => {
|
|
123
|
-
it('should read progress file', async () => {
|
|
124
|
-
const s = new SessionStartup('/test', {
|
|
125
|
-
required: ['read_progress'],
|
|
126
|
-
optional: [],
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
mockFs.readFile.mockResolvedValueOnce('# Progress\n- Task 1 done');
|
|
130
|
-
|
|
131
|
-
const result = await s.run();
|
|
132
|
-
|
|
133
|
-
expect(result.results[0].success).toBe(true);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should return null for missing file', async () => {
|
|
137
|
-
const s = new SessionStartup('/test', {
|
|
138
|
-
required: ['read_progress'],
|
|
139
|
-
optional: [],
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
mockFs.readFile.mockRejectedValueOnce(new Error('Not found'));
|
|
143
|
-
|
|
144
|
-
const result = await s.run();
|
|
145
|
-
|
|
146
|
-
// read_progress returns null on error, but doesn't fail
|
|
147
|
-
expect(result.results[0].success).toBe(true);
|
|
148
|
-
expect(result.results[0].data).toBeNull();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe('read_task_list', () => {
|
|
153
|
-
it('should read task list', async () => {
|
|
154
|
-
const s = new SessionStartup('/test', {
|
|
155
|
-
required: ['read_task_list'],
|
|
156
|
-
optional: [],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
mockFs.access.mockResolvedValueOnce(undefined);
|
|
160
|
-
mockFs.readFile.mockResolvedValueOnce(JSON.stringify({ tasks: [] }));
|
|
161
|
-
|
|
162
|
-
const result = await s.run();
|
|
163
|
-
|
|
164
|
-
expect(result.results[0].success).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should handle missing task list', async () => {
|
|
168
|
-
const s = new SessionStartup('/test', {
|
|
169
|
-
required: ['read_task_list'],
|
|
170
|
-
optional: [],
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
mockFs.access.mockRejectedValueOnce(new Error('Not found'));
|
|
174
|
-
|
|
175
|
-
const result = await s.run();
|
|
176
|
-
|
|
177
|
-
// 文件不存在时返回失败
|
|
178
|
-
expect(result.results[0].type).toBe('read_task_list');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('should parse task list steps', async () => {
|
|
182
|
-
const s = new SessionStartup('/test', {
|
|
183
|
-
required: ['read_task_list'],
|
|
184
|
-
optional: [],
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
mockFs.access.mockResolvedValueOnce(undefined);
|
|
188
|
-
mockFs.readFile.mockResolvedValueOnce(JSON.stringify({
|
|
189
|
-
tasks: [
|
|
190
|
-
{ id: 'task-1', steps: [{ status: 'done' }] },
|
|
191
|
-
],
|
|
192
|
-
}));
|
|
193
|
-
|
|
194
|
-
const result = await s.run();
|
|
195
|
-
|
|
196
|
-
expect(result.results[0].success).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('init_sh', () => {
|
|
201
|
-
it('should run init.sh', async () => {
|
|
202
|
-
const s = new SessionStartup('/test', {
|
|
203
|
-
required: ['init_sh'],
|
|
204
|
-
optional: [],
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
208
|
-
callback(null, { stdout: 'Init complete', stderr: '' });
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const result = await s.run();
|
|
212
|
-
|
|
213
|
-
// init_sh 成功或失败取决于 mock 设置
|
|
214
|
-
expect(result.results[0].type).toBe('init_sh');
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe('basic_verification', () => {
|
|
219
|
-
it('should run basic verification', async () => {
|
|
220
|
-
const s = new SessionStartup('/test', {
|
|
221
|
-
required: ['basic_verification'],
|
|
222
|
-
optional: [],
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
226
|
-
callback(null, { stdout: 'Tests passed', stderr: '' });
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const result = await s.run();
|
|
230
|
-
|
|
231
|
-
expect(result.results[0].type).toBe('basic_verification');
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('load_context', () => {
|
|
236
|
-
it('should load context', async () => {
|
|
237
|
-
const s = new SessionStartup('/test', {
|
|
238
|
-
required: ['load_context'],
|
|
239
|
-
optional: [],
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
mockFs.readFile.mockResolvedValueOnce('# Context\nSome content');
|
|
243
|
-
|
|
244
|
-
const result = await s.run();
|
|
245
|
-
|
|
246
|
-
expect(result.results[0].type).toBe('load_context');
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
describe('unknown checkpoint', () => {
|
|
251
|
-
it('should handle unknown checkpoint type', async () => {
|
|
252
|
-
const s = new SessionStartup('/test', {
|
|
253
|
-
required: ['unknown_checkpoint' as any],
|
|
254
|
-
optional: [],
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const result = await s.run();
|
|
258
|
-
|
|
259
|
-
expect(result.results[0].type).toBe('unknown_checkpoint');
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
describe('multiple checkpoints', () => {
|
|
264
|
-
it('should run all required and optional checkpoints', async () => {
|
|
265
|
-
const s = new SessionStartup('/test', {
|
|
266
|
-
required: ['pwd', 'git_log', 'git_status'],
|
|
267
|
-
optional: ['read_progress'],
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
mockExec.mockImplementation((cmd, opts, callback) => {
|
|
271
|
-
callback(null, { stdout: 'output', stderr: '' });
|
|
272
|
-
});
|
|
273
|
-
mockFs.readFile.mockResolvedValue('# Progress');
|
|
274
|
-
|
|
275
|
-
const result = await s.run();
|
|
276
|
-
|
|
277
|
-
expect(result.results.length).toBe(4);
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
});
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CleanStateManager - Session 结束状态管理
|
|
3
|
-
*
|
|
4
|
-
* 确保 Agent 结束时必须"打扫干净",不留烂代码。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { execAsync } from '../../utils/exec';
|
|
8
|
-
import { promisify } from 'util';
|
|
9
|
-
import * as fs from 'fs/promises';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import * as yaml from 'js-yaml';
|
|
12
|
-
import type {
|
|
13
|
-
CleanStateConfig,
|
|
14
|
-
CleanStateResult,
|
|
15
|
-
DetectedBug,
|
|
16
|
-
SessionInfo,
|
|
17
|
-
} from '../../types/session';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const DEFAULT_CONFIG: Required<CleanStateConfig> = {
|
|
21
|
-
enabled: true,
|
|
22
|
-
autoCommit: true,
|
|
23
|
-
detectBugs: true,
|
|
24
|
-
updateProgress: true,
|
|
25
|
-
commitMessageTemplate: '[{taskId}] {description} - {status}',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const BUG_PATTERNS = [
|
|
29
|
-
{ pattern: /console\.(error|warn)\(/, severity: 'low' as const, type: 'logic' as const },
|
|
30
|
-
{ pattern: /FIXME|HACK/i, severity: 'medium' as const, type: 'logic' as const },
|
|
31
|
-
{ pattern: /eval\s*\(/, severity: 'high' as const, type: 'security' as const },
|
|
32
|
-
{ pattern: /dangerouslySetInnerHTML/, severity: 'medium' as const, type: 'security' as const },
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
export class CleanStateManager {
|
|
36
|
-
private config: Required<CleanStateConfig>;
|
|
37
|
-
|
|
38
|
-
constructor(config: Partial<CleanStateConfig> = {}) {
|
|
39
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async onSessionEnd(workDir: string, sessionInfo: SessionInfo): Promise<CleanStateResult> {
|
|
43
|
-
const result: CleanStateResult = {
|
|
44
|
-
isClean: true,
|
|
45
|
-
hasUncommittedChanges: false,
|
|
46
|
-
progressUpdated: false,
|
|
47
|
-
errors: []
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (!this.config.enabled) return result;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
// 1. 检查未提交变更
|
|
54
|
-
result.hasUncommittedChanges = await this.hasUncommittedChanges(workDir);
|
|
55
|
-
|
|
56
|
-
// 2. 自动提交(如果配置开启)
|
|
57
|
-
if (result.hasUncommittedChanges && this.config.autoCommit) {
|
|
58
|
-
result.committedFiles = await this.autoCommit(workDir, sessionInfo);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 3. 检测 bug(如果配置开启)
|
|
62
|
-
if (this.config.detectBugs) {
|
|
63
|
-
result.bugs = await this.detectBugs(workDir);
|
|
64
|
-
if (result.bugs.length > 0) result.isClean = false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 4. 更新 progress(如果配置开启)
|
|
68
|
-
if (this.config.updateProgress) {
|
|
69
|
-
result.progressUpdated = await this.updateProgress(workDir, sessionInfo, result);
|
|
70
|
-
}
|
|
71
|
-
} catch (e: any) {
|
|
72
|
-
result.errors?.push(e.message);
|
|
73
|
-
result.isClean = false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private async hasUncommittedChanges(workDir: string): Promise<boolean> {
|
|
80
|
-
try {
|
|
81
|
-
const { stdout } = await execAsync('git status --porcelain', { cwd: workDir });
|
|
82
|
-
return stdout.trim().length > 0;
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private async autoCommit(workDir: string, sessionInfo: SessionInfo): Promise<string[]> {
|
|
89
|
-
try {
|
|
90
|
-
const { stdout } = await execAsync('git status --porcelain', { cwd: workDir });
|
|
91
|
-
const files = stdout.split('\n').filter(Boolean).map(l => l.substring(3).trim());
|
|
92
|
-
|
|
93
|
-
await execAsync('git add -A', { cwd: workDir });
|
|
94
|
-
|
|
95
|
-
// 生成提交消息
|
|
96
|
-
const msg = sessionInfo.task
|
|
97
|
-
? `[${sessionInfo.task.id}] ${sessionInfo.task.name || sessionInfo.task.id}`
|
|
98
|
-
: `[session] ${sessionInfo.workflowId}`;
|
|
99
|
-
|
|
100
|
-
// 使用 execFile 避免 shell 注入
|
|
101
|
-
const { execFile: execFileCb } = await import('child_process');
|
|
102
|
-
const execFileAsync = promisify(execFileCb);
|
|
103
|
-
await execFileAsync('git', ['commit', '-m', msg], { cwd: workDir });
|
|
104
|
-
return files;
|
|
105
|
-
} catch {
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private async detectBugs(workDir: string): Promise<DetectedBug[]> {
|
|
111
|
-
const bugs: DetectedBug[] = [];
|
|
112
|
-
try {
|
|
113
|
-
// 获取最近变更的文件
|
|
114
|
-
const { stdout } = await execAsync('git diff --name-only HEAD~1', { cwd: workDir });
|
|
115
|
-
|
|
116
|
-
for (const file of stdout.split('\n').filter(Boolean)) {
|
|
117
|
-
// 只检查代码文件
|
|
118
|
-
if (!['.ts', '.tsx', '.js', '.jsx', '.py', '.go'].some(e => file.endsWith(e))) continue;
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const content = await fs.readFile(path.join(workDir, file), 'utf-8');
|
|
122
|
-
const lines = content.split('\n');
|
|
123
|
-
|
|
124
|
-
lines.forEach((line, i) => {
|
|
125
|
-
for (const { pattern, severity, type } of BUG_PATTERNS) {
|
|
126
|
-
if (pattern.test(line)) {
|
|
127
|
-
bugs.push({
|
|
128
|
-
file,
|
|
129
|
-
line: i + 1,
|
|
130
|
-
message: `Detected pattern: ${pattern.source}`,
|
|
131
|
-
severity,
|
|
132
|
-
type
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
} catch {
|
|
138
|
-
// 文件读取失败,跳过
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
} catch {}
|
|
142
|
-
return bugs;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private async updateProgress(workDir: string, sessionInfo: SessionInfo, cleanResult: CleanStateResult): Promise<boolean> {
|
|
146
|
-
const progressPath = path.join(workDir, '.harness', 'progress.yml');
|
|
147
|
-
try {
|
|
148
|
-
await fs.mkdir(path.dirname(progressPath), { recursive: true });
|
|
149
|
-
|
|
150
|
-
// 读取现有 progress 或创建新的
|
|
151
|
-
let data: any = { sessions: [], last_updated: new Date().toISOString() };
|
|
152
|
-
try {
|
|
153
|
-
data = yaml.load(await fs.readFile(progressPath, 'utf-8')) as any || data;
|
|
154
|
-
} catch {}
|
|
155
|
-
|
|
156
|
-
// 添加本次 session 记录
|
|
157
|
-
data.sessions.push({
|
|
158
|
-
id: sessionInfo.sessionId,
|
|
159
|
-
workflow: sessionInfo.workflowId,
|
|
160
|
-
task: sessionInfo.task?.id,
|
|
161
|
-
timestamp: new Date().toISOString(),
|
|
162
|
-
clean: cleanResult.isClean,
|
|
163
|
-
bugs: cleanResult.bugs?.length || 0,
|
|
164
|
-
committed: cleanResult.committedFiles?.length || 0,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
data.last_updated = new Date().toISOString();
|
|
168
|
-
|
|
169
|
-
await fs.writeFile(progressPath, yaml.dump(data));
|
|
170
|
-
return true;
|
|
171
|
-
} catch {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const createCleanStateManager = (config?: Partial<CleanStateConfig>) =>
|
|
178
|
-
new CleanStateManager(config);
|