@principles/core 1.240.7 → 1.240.9

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 (34) hide show
  1. package/dist/adapters/__tests__/code-review-pain-adapter.test.d.ts +2 -0
  2. package/dist/adapters/__tests__/code-review-pain-adapter.test.d.ts.map +1 -0
  3. package/dist/adapters/__tests__/code-review-pain-adapter.test.js +346 -0
  4. package/dist/adapters/__tests__/code-review-pain-adapter.test.js.map +1 -0
  5. package/dist/adapters/__tests__/openclaw-pain-adapter.test.d.ts +2 -0
  6. package/dist/adapters/__tests__/openclaw-pain-adapter.test.d.ts.map +1 -0
  7. package/dist/adapters/__tests__/openclaw-pain-adapter.test.js +290 -0
  8. package/dist/adapters/__tests__/openclaw-pain-adapter.test.js.map +1 -0
  9. package/dist/adapters/__tests__/writing-pain-adapter.test.d.ts +2 -0
  10. package/dist/adapters/__tests__/writing-pain-adapter.test.d.ts.map +1 -0
  11. package/dist/adapters/__tests__/writing-pain-adapter.test.js +239 -0
  12. package/dist/adapters/__tests__/writing-pain-adapter.test.js.map +1 -0
  13. package/dist/principle-tree-ledger-lifecycle.test.d.ts +2 -0
  14. package/dist/principle-tree-ledger-lifecycle.test.d.ts.map +1 -0
  15. package/dist/principle-tree-ledger-lifecycle.test.js +530 -0
  16. package/dist/principle-tree-ledger-lifecycle.test.js.map +1 -0
  17. package/dist/quality-scorecard/__tests__/report-generator.test.d.ts +2 -0
  18. package/dist/quality-scorecard/__tests__/report-generator.test.d.ts.map +1 -0
  19. package/dist/quality-scorecard/__tests__/report-generator.test.js +627 -0
  20. package/dist/quality-scorecard/__tests__/report-generator.test.js.map +1 -0
  21. package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js +11 -0
  22. package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js.map +1 -1
  23. package/dist/runtime-v2/index.d.ts +1 -0
  24. package/dist/runtime-v2/index.d.ts.map +1 -1
  25. package/dist/runtime-v2/index.js.map +1 -1
  26. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts +2 -0
  27. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  28. package/dist/runtime-v2/pain-signal-runtime-factory.js +1 -1
  29. package/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  30. package/dist/runtime-v2/pain-to-principle-service.d.ts +2 -0
  31. package/dist/runtime-v2/pain-to-principle-service.d.ts.map +1 -1
  32. package/dist/runtime-v2/pain-to-principle-service.js +2 -0
  33. package/dist/runtime-v2/pain-to-principle-service.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=code-review-pain-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-review-pain-adapter.test.d.ts","sourceRoot":"","sources":["../../../src/adapters/__tests__/code-review-pain-adapter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,346 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { CodeReviewPainAdapter } from '../code-review/code-review-pain-adapter.js';
3
+ describe('CodeReviewPainAdapter', () => {
4
+ const adapter = new CodeReviewPainAdapter();
5
+ const baseFile = {
6
+ path: 'src/index.ts',
7
+ linesAdded: 10,
8
+ linesDeleted: 5,
9
+ changeMagnitude: 15,
10
+ };
11
+ const baseEvent = {
12
+ prId: 'pr-1',
13
+ repositoryId: 'repo-1',
14
+ authorId: 'author-1',
15
+ reviewerIds: ['reviewer-1'],
16
+ filesChanged: [baseFile],
17
+ totalLinesAdded: 10,
18
+ totalLinesDeleted: 5,
19
+ comments: [],
20
+ unresolvedThreadCount: 0,
21
+ createdAt: '2026-01-01T00:00:00Z',
22
+ reviewAgeDays: 1,
23
+ hasTests: true,
24
+ hasSecurityReview: true,
25
+ isBreakingChange: false,
26
+ labels: [],
27
+ sessionId: 'sess-123',
28
+ };
29
+ // ---------------------------------------------------------------------------
30
+ // Null-return paths
31
+ // ---------------------------------------------------------------------------
32
+ it('returns null for missing sessionId', () => {
33
+ const event = { ...baseEvent, sessionId: '' };
34
+ expect(adapter.capture(event)).toBeNull();
35
+ });
36
+ it('returns null when sessionId is not a string', () => {
37
+ const event = { ...baseEvent, sessionId: 42 };
38
+ expect(adapter.capture(event)).toBeNull();
39
+ });
40
+ it('returns null when no files changed and no comments', () => {
41
+ const event = { ...baseEvent, filesChanged: [], comments: [] };
42
+ expect(adapter.capture(event)).toBeNull();
43
+ });
44
+ it('returns null when filesChanged is undefined and no comments', () => {
45
+ const { filesChanged: _filesChanged, ...eventWithoutFiles } = baseEvent;
46
+ const event = { ...eventWithoutFiles, filesChanged: undefined, comments: [] };
47
+ expect(adapter.capture(event)).toBeNull();
48
+ });
49
+ it('returns null for negative reviewAgeDays', () => {
50
+ const event = { ...baseEvent, reviewAgeDays: -1 };
51
+ expect(adapter.capture(event)).toBeNull();
52
+ });
53
+ // ---------------------------------------------------------------------------
54
+ // Valid event with process violation
55
+ // ---------------------------------------------------------------------------
56
+ it('produces high score for process violation (no tests, >2 files, breaking change)', () => {
57
+ const event = {
58
+ ...baseEvent,
59
+ filesChanged: [
60
+ { path: 'src/a.ts', linesAdded: 50, linesDeleted: 10, changeMagnitude: 60 },
61
+ { path: 'src/b.ts', linesAdded: 30, linesDeleted: 5, changeMagnitude: 35 },
62
+ { path: 'src/c.ts', linesAdded: 20, linesDeleted: 5, changeMagnitude: 25 },
63
+ ],
64
+ totalLinesAdded: 100,
65
+ totalLinesDeleted: 20,
66
+ hasTests: false,
67
+ isBreakingChange: true,
68
+ labels: [],
69
+ hasSecurityReview: false,
70
+ };
71
+ const signal = adapter.capture(event);
72
+ expect(signal).not.toBeNull();
73
+ if (!signal)
74
+ throw new Error('Expected non-null signal');
75
+ // No tests + >2 files = +35, breaking change without label = +40 → process >= 40
76
+ expect(signal.source).toBe('process_violation');
77
+ expect(signal.score).toBeGreaterThan(0);
78
+ // Severity should be at least medium for process violations
79
+ expect(['medium', 'high', 'critical']).toContain(signal.severity);
80
+ });
81
+ // ---------------------------------------------------------------------------
82
+ // Valid event with negative sentiment
83
+ // ---------------------------------------------------------------------------
84
+ it('produces negative_sentiment trigger when comments are negative', () => {
85
+ const negativeComment = {
86
+ id: 'c2',
87
+ authorId: 'reviewer-1',
88
+ body: 'This code is terrible and should be rewritten',
89
+ sentimentScore: -80,
90
+ createdAt: '2026-01-01T00:00:00Z',
91
+ // unresolved: no resolvedAt
92
+ };
93
+ const event = {
94
+ ...baseEvent,
95
+ comments: [negativeComment],
96
+ unresolvedThreadCount: 1,
97
+ hasTests: true,
98
+ isBreakingChange: false,
99
+ };
100
+ const signal = adapter.capture(event);
101
+ expect(signal).not.toBeNull();
102
+ if (!signal)
103
+ throw new Error('Expected non-null signal');
104
+ // With negative sentiment comments, source should be negative_sentiment
105
+ // provided process violation score < 40
106
+ expect(signal.source).toBe('negative_sentiment');
107
+ });
108
+ // ---------------------------------------------------------------------------
109
+ // Valid event with diff complexity
110
+ // ---------------------------------------------------------------------------
111
+ it('produces diff_complexity trigger for many files with no process violations or negative sentiment', () => {
112
+ const manyFiles = Array.from({ length: 10 }, (_, i) => ({
113
+ path: `src/file${i}.ts`,
114
+ linesAdded: 100,
115
+ linesDeleted: 50,
116
+ changeMagnitude: 150,
117
+ }));
118
+ // Use positive-sentiment comments so sentiment score stays below 40 threshold
119
+ const positiveComment = {
120
+ id: 'c-pos',
121
+ authorId: 'reviewer-1',
122
+ body: 'Looks great!',
123
+ sentimentScore: 80,
124
+ createdAt: '2026-01-01T00:00:00Z',
125
+ resolvedAt: '2026-01-02T00:00:00Z',
126
+ };
127
+ const event = {
128
+ ...baseEvent,
129
+ filesChanged: manyFiles,
130
+ totalLinesAdded: 1000,
131
+ totalLinesDeleted: 500,
132
+ hasTests: true,
133
+ isBreakingChange: false,
134
+ hasSecurityReview: true,
135
+ comments: [positiveComment],
136
+ };
137
+ const signal = adapter.capture(event);
138
+ expect(signal).not.toBeNull();
139
+ if (!signal)
140
+ throw new Error('Expected non-null signal');
141
+ expect(signal.source).toBe('diff_complexity');
142
+ });
143
+ // ---------------------------------------------------------------------------
144
+ // Score clamping
145
+ // ---------------------------------------------------------------------------
146
+ it('clamps score to 0-100 range', () => {
147
+ // Extreme process violations could theoretically push score > 100
148
+ const event = {
149
+ ...baseEvent,
150
+ filesChanged: [
151
+ { path: 'security/auth.ts', linesAdded: 300, linesDeleted: 200, changeMagnitude: 500 },
152
+ { path: 'security/crypto.ts', linesAdded: 300, linesDeleted: 200, changeMagnitude: 500 },
153
+ { path: 'src/main.ts', linesAdded: 300, linesDeleted: 200, changeMagnitude: 500 },
154
+ ],
155
+ totalLinesAdded: 900,
156
+ totalLinesDeleted: 600,
157
+ hasTests: false,
158
+ isBreakingChange: true,
159
+ labels: [],
160
+ hasSecurityReview: false,
161
+ };
162
+ const signal = adapter.capture(event);
163
+ expect(signal).not.toBeNull();
164
+ if (!signal)
165
+ throw new Error('Expected non-null signal');
166
+ expect(signal.score).toBeGreaterThanOrEqual(0);
167
+ expect(signal.score).toBeLessThanOrEqual(100);
168
+ });
169
+ // ---------------------------------------------------------------------------
170
+ // triggerTextPreview truncation
171
+ // ---------------------------------------------------------------------------
172
+ it('truncates triggerTextPreview to 200 chars', () => {
173
+ const longBody = 'x'.repeat(300);
174
+ const negativeComment = {
175
+ id: 'c3',
176
+ authorId: 'reviewer-1',
177
+ body: longBody,
178
+ sentimentScore: -90,
179
+ createdAt: '2026-01-01T00:00:00Z',
180
+ };
181
+ const event = {
182
+ ...baseEvent,
183
+ comments: [negativeComment],
184
+ hasTests: true,
185
+ isBreakingChange: false,
186
+ };
187
+ const signal = adapter.capture(event);
188
+ expect(signal).not.toBeNull();
189
+ if (!signal)
190
+ throw new Error('Expected non-null signal');
191
+ expect(signal.triggerTextPreview.length).toBeLessThanOrEqual(200);
192
+ });
193
+ // ---------------------------------------------------------------------------
194
+ // PainSignal structure
195
+ // ---------------------------------------------------------------------------
196
+ it('returns PainSignal with correct structure', () => {
197
+ const signal = adapter.capture(baseEvent);
198
+ expect(signal).not.toBeNull();
199
+ if (!signal)
200
+ throw new Error('Expected non-null signal');
201
+ expect(typeof signal.source).toBe('string');
202
+ expect(typeof signal.score).toBe('number');
203
+ expect(signal.timestamp).toBeTruthy();
204
+ expect(typeof signal.reason).toBe('string');
205
+ expect(signal.sessionId).toBe('sess-123');
206
+ expect(signal.agentId).toBe('code-review-evaluator');
207
+ expect(signal.domain).toBe('code-review');
208
+ expect(typeof signal.severity).toBe('string');
209
+ expect(typeof signal.context).toBe('object');
210
+ });
211
+ // ---------------------------------------------------------------------------
212
+ // deriveProcessViolationScore scenarios
213
+ // ---------------------------------------------------------------------------
214
+ it('adds 35 when no tests and >2 files', () => {
215
+ // With no tests + 3 files: +35, no other violations → process = 35 (< 40 threshold)
216
+ // So primary trigger should NOT be process_violation
217
+ const event = {
218
+ ...baseEvent,
219
+ filesChanged: [
220
+ { path: 'src/a.ts', linesAdded: 5, linesDeleted: 5, changeMagnitude: 10 },
221
+ { path: 'src/b.ts', linesAdded: 5, linesDeleted: 5, changeMagnitude: 10 },
222
+ { path: 'src/c.ts', linesAdded: 5, linesDeleted: 5, changeMagnitude: 10 },
223
+ ],
224
+ hasTests: false,
225
+ isBreakingChange: false,
226
+ hasSecurityReview: true,
227
+ totalLinesAdded: 15,
228
+ totalLinesDeleted: 15,
229
+ labels: [],
230
+ };
231
+ const signal = adapter.capture(event);
232
+ expect(signal).not.toBeNull();
233
+ if (!signal)
234
+ throw new Error('Expected non-null signal');
235
+ // process score is 35, which is < 40, so not process_violation
236
+ expect(signal.context.processViolationScore).toBe(35);
237
+ expect(signal.source).not.toBe('process_violation');
238
+ });
239
+ it('adds 40 for breaking change without label', () => {
240
+ const event = {
241
+ ...baseEvent,
242
+ hasTests: true,
243
+ isBreakingChange: true,
244
+ labels: [],
245
+ hasSecurityReview: true,
246
+ };
247
+ const signal = adapter.capture(event);
248
+ expect(signal).not.toBeNull();
249
+ if (!signal)
250
+ throw new Error('Expected non-null signal');
251
+ expect(signal.context.processViolationScore).toBe(40);
252
+ expect(signal.source).toBe('process_violation');
253
+ });
254
+ it('adds 50 for security changes without security review', () => {
255
+ const event = {
256
+ ...baseEvent,
257
+ filesChanged: [{ path: 'security/auth.ts', linesAdded: 10, linesDeleted: 5, changeMagnitude: 15 }],
258
+ hasTests: true,
259
+ isBreakingChange: false,
260
+ hasSecurityReview: false,
261
+ labels: [],
262
+ };
263
+ const signal = adapter.capture(event);
264
+ expect(signal).not.toBeNull();
265
+ if (!signal)
266
+ throw new Error('Expected non-null signal');
267
+ expect(signal.context.processViolationScore).toBe(50);
268
+ expect(signal.source).toBe('process_violation');
269
+ });
270
+ it('adds 20 for >500 lines changed without tests', () => {
271
+ const event = {
272
+ ...baseEvent,
273
+ filesChanged: [
274
+ { path: 'src/a.ts', linesAdded: 400, linesDeleted: 200, changeMagnitude: 600 },
275
+ ],
276
+ totalLinesAdded: 400,
277
+ totalLinesDeleted: 200,
278
+ hasTests: false,
279
+ isBreakingChange: false,
280
+ hasSecurityReview: true,
281
+ labels: [],
282
+ };
283
+ const signal = adapter.capture(event);
284
+ expect(signal).not.toBeNull();
285
+ if (!signal)
286
+ throw new Error('Expected non-null signal');
287
+ // no tests + 1 file (not >2) = 0 for that rule, but >500 lines + no tests = +20
288
+ expect(signal.context.processViolationScore).toBe(20);
289
+ });
290
+ // ---------------------------------------------------------------------------
291
+ // deriveSentimentScore recency multiplier
292
+ // ---------------------------------------------------------------------------
293
+ it('applies recency multiplier when reviewAgeDays > 7', () => {
294
+ const negativeComment = {
295
+ id: 'c4',
296
+ authorId: 'reviewer-1',
297
+ body: 'Bad code',
298
+ sentimentScore: -50,
299
+ createdAt: '2026-01-01T00:00:00Z',
300
+ };
301
+ const recentEvent = {
302
+ ...baseEvent,
303
+ comments: [negativeComment],
304
+ reviewAgeDays: 3,
305
+ hasTests: true,
306
+ isBreakingChange: false,
307
+ };
308
+ const oldEvent = {
309
+ ...baseEvent,
310
+ comments: [negativeComment],
311
+ reviewAgeDays: 10,
312
+ hasTests: true,
313
+ isBreakingChange: false,
314
+ };
315
+ const recentSignal = adapter.capture(recentEvent);
316
+ const oldSignal = adapter.capture(oldEvent);
317
+ expect(recentSignal).not.toBeNull();
318
+ if (!recentSignal)
319
+ throw new Error('Expected non-null recentSignal');
320
+ expect(oldSignal).not.toBeNull();
321
+ if (!oldSignal)
322
+ throw new Error('Expected non-null oldSignal');
323
+ // Older reviews with unresolved negatives should produce higher sentiment pain
324
+ expect(oldSignal.context.sentimentScore).toBeGreaterThan(recentSignal.context.sentimentScore);
325
+ });
326
+ // ---------------------------------------------------------------------------
327
+ // traceId fallback
328
+ // ---------------------------------------------------------------------------
329
+ it('uses "unknown" for traceId when not provided', () => {
330
+ const event = { ...baseEvent, traceId: undefined };
331
+ const signal = adapter.capture(event);
332
+ expect(signal).not.toBeNull();
333
+ if (!signal)
334
+ throw new Error('Expected non-null signal');
335
+ expect(signal.traceId).toBe('unknown');
336
+ });
337
+ it('uses provided traceId', () => {
338
+ const event = { ...baseEvent, traceId: 'trace-abc' };
339
+ const signal = adapter.capture(event);
340
+ expect(signal).not.toBeNull();
341
+ if (!signal)
342
+ throw new Error('Expected non-null signal');
343
+ expect(signal.traceId).toBe('trace-abc');
344
+ });
345
+ });
346
+ //# sourceMappingURL=code-review-pain-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-review-pain-adapter.test.js","sourceRoot":"","sources":["../../../src/adapters/__tests__/code-review-pain-adapter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=openclaw-pain-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-pain-adapter.test.d.ts","sourceRoot":"","sources":["../../../src/adapters/__tests__/openclaw-pain-adapter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,290 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { OpenClawPainAdapter } from '../coding/openclaw-pain-adapter.js';
3
+ import { deriveSeverity } from '../../pain-signal.js';
4
+ describe('OpenClawPainAdapter', () => {
5
+ const adapter = new OpenClawPainAdapter();
6
+ const baseEvent = {
7
+ toolName: 'write',
8
+ error: 'some error',
9
+ params: {},
10
+ sessionId: 'sess-123',
11
+ agentId: 'agent-1',
12
+ };
13
+ // ---------------------------------------------------------------------------
14
+ // Null-return paths
15
+ // ---------------------------------------------------------------------------
16
+ it('returns null for non-failure event (no error field)', () => {
17
+ const event = { ...baseEvent, error: undefined };
18
+ expect(adapter.capture(event)).toBeNull();
19
+ });
20
+ it('returns null for malformed event (missing toolName)', () => {
21
+ const event = { ...baseEvent, toolName: '' };
22
+ expect(adapter.capture(event)).toBeNull();
23
+ });
24
+ it('returns null for malformed event (toolName is not a string)', () => {
25
+ const event = { ...baseEvent, toolName: 42 };
26
+ expect(adapter.capture(event)).toBeNull();
27
+ });
28
+ // ---------------------------------------------------------------------------
29
+ // deriveScoreFromError mappings
30
+ // ---------------------------------------------------------------------------
31
+ it('maps permission denied error to score 95, severity critical', () => {
32
+ const signal = adapter.capture({ ...baseEvent, error: 'permission denied' });
33
+ expect(signal).not.toBeNull();
34
+ if (!signal)
35
+ throw new Error('Expected non-null signal');
36
+ expect(signal.score).toBe(95);
37
+ expect(signal.severity).toBe('critical');
38
+ });
39
+ it('maps EACCES error to score 95', () => {
40
+ const signal = adapter.capture({ ...baseEvent, error: 'EACCES: access denied' });
41
+ expect(signal).not.toBeNull();
42
+ if (!signal)
43
+ throw new Error('Expected non-null signal');
44
+ expect(signal.score).toBe(95);
45
+ });
46
+ it('maps ENOENT error to score 80, severity high', () => {
47
+ const signal = adapter.capture({ ...baseEvent, error: 'ENOENT: no such file or directory' });
48
+ expect(signal).not.toBeNull();
49
+ if (!signal)
50
+ throw new Error('Expected non-null signal');
51
+ expect(signal.score).toBe(80);
52
+ expect(signal.severity).toBe('high');
53
+ });
54
+ it('maps "no such file" error to score 80', () => {
55
+ const signal = adapter.capture({ ...baseEvent, error: 'no such file found' });
56
+ expect(signal).not.toBeNull();
57
+ if (!signal)
58
+ throw new Error('Expected non-null signal');
59
+ expect(signal.score).toBe(80);
60
+ });
61
+ it('maps EISDIR error to score 85', () => {
62
+ const signal = adapter.capture({ ...baseEvent, error: 'EISDIR: illegal operation' });
63
+ expect(signal).not.toBeNull();
64
+ if (!signal)
65
+ throw new Error('Expected non-null signal');
66
+ expect(signal.score).toBe(85);
67
+ });
68
+ it('maps EISFDIR error to score 85', () => {
69
+ const signal = adapter.capture({ ...baseEvent, error: 'EISFDIR: expected file' });
70
+ expect(signal).not.toBeNull();
71
+ if (!signal)
72
+ throw new Error('Expected non-null signal');
73
+ expect(signal.score).toBe(85);
74
+ });
75
+ it('maps SyntaxError to score 82', () => {
76
+ const signal = adapter.capture({ ...baseEvent, error: 'SyntaxError: unexpected token' });
77
+ expect(signal).not.toBeNull();
78
+ if (!signal)
79
+ throw new Error('Expected non-null signal');
80
+ expect(signal.score).toBe(82);
81
+ });
82
+ it('maps ParseError to score 82', () => {
83
+ const signal = adapter.capture({ ...baseEvent, error: 'ParseError: invalid JSON' });
84
+ expect(signal).not.toBeNull();
85
+ if (!signal)
86
+ throw new Error('Expected non-null signal');
87
+ expect(signal.score).toBe(82);
88
+ });
89
+ it('maps TypeError to score 75', () => {
90
+ const signal = adapter.capture({ ...baseEvent, error: 'TypeError: cannot read property' });
91
+ expect(signal).not.toBeNull();
92
+ if (!signal)
93
+ throw new Error('Expected non-null signal');
94
+ expect(signal.score).toBe(75);
95
+ });
96
+ it('maps "invalid type" error to score 75', () => {
97
+ const signal = adapter.capture({ ...baseEvent, error: 'invalid type: expected string' });
98
+ expect(signal).not.toBeNull();
99
+ if (!signal)
100
+ throw new Error('Expected non-null signal');
101
+ expect(signal.score).toBe(75);
102
+ });
103
+ it('maps ReferenceError to score 72', () => {
104
+ const signal = adapter.capture({ ...baseEvent, error: 'ReferenceError: x is not defined' });
105
+ expect(signal).not.toBeNull();
106
+ if (!signal)
107
+ throw new Error('Expected non-null signal');
108
+ expect(signal.score).toBe(72);
109
+ });
110
+ it('maps timeout error to score 60, severity medium', () => {
111
+ const signal = adapter.capture({ ...baseEvent, error: 'timeout: request exceeded 30s' });
112
+ expect(signal).not.toBeNull();
113
+ if (!signal)
114
+ throw new Error('Expected non-null signal');
115
+ expect(signal.score).toBe(60);
116
+ expect(signal.severity).toBe('medium');
117
+ });
118
+ it('maps ETIMEDOUT error to score 60', () => {
119
+ const signal = adapter.capture({ ...baseEvent, error: 'ETIMEDOUT: connection timed out' });
120
+ expect(signal).not.toBeNull();
121
+ if (!signal)
122
+ throw new Error('Expected non-null signal');
123
+ expect(signal.score).toBe(60);
124
+ });
125
+ it('maps ECONNREFUSED error to score 65', () => {
126
+ const signal = adapter.capture({ ...baseEvent, error: 'ECONNREFUSED: connection refused' });
127
+ expect(signal).not.toBeNull();
128
+ if (!signal)
129
+ throw new Error('Expected non-null signal');
130
+ expect(signal.score).toBe(65);
131
+ });
132
+ it('maps network error to score 65', () => {
133
+ const signal = adapter.capture({ ...baseEvent, error: 'network error: unreachable' });
134
+ expect(signal).not.toBeNull();
135
+ if (!signal)
136
+ throw new Error('Expected non-null signal');
137
+ expect(signal.score).toBe(65);
138
+ });
139
+ it('maps ENOTEMPTY error to score 55', () => {
140
+ const signal = adapter.capture({ ...baseEvent, error: 'ENOTEMPTY: directory not empty' });
141
+ expect(signal).not.toBeNull();
142
+ if (!signal)
143
+ throw new Error('Expected non-null signal');
144
+ expect(signal.score).toBe(55);
145
+ });
146
+ it('maps EEXIST error to score 55', () => {
147
+ const signal = adapter.capture({ ...baseEvent, error: 'EEXIST: file already exists' });
148
+ expect(signal).not.toBeNull();
149
+ if (!signal)
150
+ throw new Error('Expected non-null signal');
151
+ expect(signal.score).toBe(55);
152
+ });
153
+ it('maps unknown error to default score 50, severity medium', () => {
154
+ const signal = adapter.capture({ ...baseEvent, error: 'something unexpected happened' });
155
+ expect(signal).not.toBeNull();
156
+ if (!signal)
157
+ throw new Error('Expected non-null signal');
158
+ expect(signal.score).toBe(50);
159
+ expect(signal.severity).toBe('medium');
160
+ });
161
+ // ---------------------------------------------------------------------------
162
+ // PainSignal structure
163
+ // ---------------------------------------------------------------------------
164
+ it('returns PainSignal with correct structure', () => {
165
+ const signal = adapter.capture(baseEvent);
166
+ expect(signal).not.toBeNull();
167
+ if (!signal)
168
+ throw new Error('Expected non-null signal');
169
+ expect(signal.source).toBe('tool_failure');
170
+ expect(signal.timestamp).toBeTruthy();
171
+ expect(new Date(signal.timestamp).toISOString()).toBe(signal.timestamp);
172
+ expect(signal.reason).toBe(`Tool ${baseEvent.toolName} failed: ${baseEvent.error}`);
173
+ expect(signal.sessionId).toBe(baseEvent.sessionId);
174
+ expect(signal.agentId).toBe(baseEvent.agentId);
175
+ expect(signal.traceId).toBe(baseEvent.sessionId);
176
+ expect(signal.domain).toBe('coding');
177
+ expect(typeof signal.severity).toBe('string');
178
+ expect(signal.context).toEqual({
179
+ toolName: baseEvent.toolName,
180
+ hasParams: true,
181
+ });
182
+ });
183
+ it('uses "unknown" for sessionId/agentId/traceId when not provided', () => {
184
+ const event = {
185
+ toolName: 'write',
186
+ error: 'some error',
187
+ };
188
+ const signal = adapter.capture(event);
189
+ expect(signal).not.toBeNull();
190
+ if (!signal)
191
+ throw new Error('Expected non-null signal');
192
+ expect(signal.sessionId).toBe('unknown');
193
+ expect(signal.agentId).toBe('unknown');
194
+ expect(signal.traceId).toBe('unknown');
195
+ });
196
+ // ---------------------------------------------------------------------------
197
+ // deriveSeverity integration
198
+ // ---------------------------------------------------------------------------
199
+ it('uses deriveSeverity from pain-signal.js for severity derivation', () => {
200
+ const signal = adapter.capture({ ...baseEvent, error: 'permission denied' });
201
+ expect(signal).not.toBeNull();
202
+ if (!signal)
203
+ throw new Error('Expected non-null signal');
204
+ expect(signal.score).toBe(95);
205
+ expect(signal.severity).toBe(deriveSeverity(95));
206
+ });
207
+ // ---------------------------------------------------------------------------
208
+ // buildTriggerPreview
209
+ // ---------------------------------------------------------------------------
210
+ it('builds trigger preview with file_path from params', () => {
211
+ const event = {
212
+ ...baseEvent,
213
+ params: { file_path: '/src/index.ts' },
214
+ };
215
+ const signal = adapter.capture(event);
216
+ expect(signal).not.toBeNull();
217
+ if (!signal)
218
+ throw new Error('Expected non-null signal');
219
+ expect(signal.triggerTextPreview).toBe('write(/src/index.ts)');
220
+ });
221
+ it('builds trigger preview with path from params', () => {
222
+ const event = {
223
+ ...baseEvent,
224
+ params: { path: '/src/util.ts' },
225
+ };
226
+ const signal = adapter.capture(event);
227
+ expect(signal).not.toBeNull();
228
+ if (!signal)
229
+ throw new Error('Expected non-null signal');
230
+ expect(signal.triggerTextPreview).toBe('write(/src/util.ts)');
231
+ });
232
+ it('builds trigger preview with file from params', () => {
233
+ const event = {
234
+ ...baseEvent,
235
+ params: { file: '/src/config.ts' },
236
+ };
237
+ const signal = adapter.capture(event);
238
+ expect(signal).not.toBeNull();
239
+ if (!signal)
240
+ throw new Error('Expected non-null signal');
241
+ expect(signal.triggerTextPreview).toBe('write(/src/config.ts)');
242
+ });
243
+ it('truncates content to 50 chars in trigger preview when no file_path', () => {
244
+ const longContent = 'a'.repeat(80);
245
+ const event = {
246
+ ...baseEvent,
247
+ params: { content: longContent },
248
+ };
249
+ const signal = adapter.capture(event);
250
+ expect(signal).not.toBeNull();
251
+ if (!signal)
252
+ throw new Error('Expected non-null signal');
253
+ expect(signal.triggerTextPreview).toBe(`write(${longContent.slice(0, 50)}...)`);
254
+ });
255
+ it('returns tool name only when no file_path or content in params', () => {
256
+ const event = {
257
+ ...baseEvent,
258
+ params: { some_other_key: 'value' },
259
+ };
260
+ const signal = adapter.capture(event);
261
+ expect(signal).not.toBeNull();
262
+ if (!signal)
263
+ throw new Error('Expected non-null signal');
264
+ expect(signal.triggerTextPreview).toBe('write');
265
+ });
266
+ it('returns empty trigger preview when params is undefined', () => {
267
+ const event = {
268
+ ...baseEvent,
269
+ params: undefined,
270
+ };
271
+ const signal = adapter.capture(event);
272
+ expect(signal).not.toBeNull();
273
+ if (!signal)
274
+ throw new Error('Expected non-null signal');
275
+ expect(signal.triggerTextPreview).toBe('');
276
+ });
277
+ it('returns empty trigger preview when params has no relevant keys', () => {
278
+ const event = {
279
+ toolName: 'bash',
280
+ error: 'command failed',
281
+ params: {},
282
+ };
283
+ const signal = adapter.capture(event);
284
+ expect(signal).not.toBeNull();
285
+ if (!signal)
286
+ throw new Error('Expected non-null signal');
287
+ expect(signal.triggerTextPreview).toBe('bash');
288
+ });
289
+ });
290
+ //# sourceMappingURL=openclaw-pain-adapter.test.js.map