groove-dev 0.27.102 → 0.27.103
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/CLAUDE.md +0 -7
- package/moe-training/client/domain-tagger.js +205 -0
- package/moe-training/client/edit-normalizer.js +188 -0
- package/moe-training/client/envelope-builder.js +1 -1
- package/moe-training/client/parsers/claude-code.js +56 -9
- package/moe-training/client/parsers/codex.js +25 -5
- package/moe-training/client/parsers/gemini.js +21 -2
- package/moe-training/client/parsers/grok.js +18 -0
- package/moe-training/client/trajectory-capture.js +95 -3
- package/moe-training/server/routes/ingest.js +26 -0
- package/moe-training/server/verifier.js +34 -0
- package/moe-training/shared/constants.js +9 -0
- package/moe-training/shared/envelope-schema.js +128 -2
- package/moe-training/test/client/domain-tagger.test.js +203 -0
- package/moe-training/test/client/edit-normalizer.test.js +376 -0
- package/moe-training/test/client/envelope-builder.test.js +28 -0
- package/moe-training/test/client/parsers/claude-code.test.js +248 -38
- package/moe-training/test/client/parsers/codex.test.js +2 -0
- package/moe-training/test/client/parsers/gemini.test.js +2 -0
- package/moe-training/test/client/trajectory-capture.test.js +345 -0
- package/moe-training/test/server/verifier.test.js +94 -0
- package/moe-training/test/shared/envelope-schema.test.js +291 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/moe-training/client/domain-tagger.js +205 -0
- package/node_modules/moe-training/client/edit-normalizer.js +188 -0
- package/node_modules/moe-training/client/envelope-builder.js +1 -1
- package/node_modules/moe-training/client/parsers/claude-code.js +56 -9
- package/node_modules/moe-training/client/parsers/codex.js +25 -5
- package/node_modules/moe-training/client/parsers/gemini.js +21 -2
- package/node_modules/moe-training/client/parsers/grok.js +18 -0
- package/node_modules/moe-training/client/trajectory-capture.js +95 -3
- package/node_modules/moe-training/server/routes/ingest.js +26 -0
- package/node_modules/moe-training/server/verifier.js +34 -0
- package/node_modules/moe-training/shared/constants.js +9 -0
- package/node_modules/moe-training/shared/envelope-schema.js +128 -2
- package/node_modules/moe-training/test/client/domain-tagger.test.js +203 -0
- package/node_modules/moe-training/test/client/edit-normalizer.test.js +376 -0
- package/node_modules/moe-training/test/client/envelope-builder.test.js +28 -0
- package/node_modules/moe-training/test/client/parsers/claude-code.test.js +248 -38
- package/node_modules/moe-training/test/client/parsers/codex.test.js +2 -0
- package/node_modules/moe-training/test/client/parsers/gemini.test.js +2 -0
- package/node_modules/moe-training/test/client/trajectory-capture.test.js +345 -0
- package/node_modules/moe-training/test/server/verifier.test.js +94 -0
- package/node_modules/moe-training/test/shared/envelope-schema.test.js +291 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/gui/package.json +1 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import { EditNormalizer, estimateTokens } from '../../client/edit-normalizer.js';
|
|
6
|
+
|
|
7
|
+
describe('EditNormalizer', () => {
|
|
8
|
+
let normalizer;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
normalizer = new EditNormalizer();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('detectApplyPatch', () => {
|
|
15
|
+
it('detects apply_patch in a raw string', () => {
|
|
16
|
+
assert.equal(normalizer.detectApplyPatch('apply_patch <<\'PATCH\'\n*** Begin Patch'), true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('detects apply_patch in action step arguments', () => {
|
|
20
|
+
const step = {
|
|
21
|
+
arguments: { command: 'apply_patch <<\'PATCH\'\n*** Begin Patch\n*** End Patch\nPATCH' },
|
|
22
|
+
};
|
|
23
|
+
assert.equal(normalizer.detectApplyPatch(step), true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('detects apply_patch in action step content', () => {
|
|
27
|
+
const step = { content: 'Running apply_patch to modify files' };
|
|
28
|
+
assert.equal(normalizer.detectApplyPatch(step), true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns false for non-patch content', () => {
|
|
32
|
+
assert.equal(normalizer.detectApplyPatch('ls -la'), false);
|
|
33
|
+
assert.equal(normalizer.detectApplyPatch({ content: 'echo hello' }), false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns false for null/undefined', () => {
|
|
37
|
+
assert.equal(normalizer.detectApplyPatch(null), false);
|
|
38
|
+
assert.equal(normalizer.detectApplyPatch(undefined), false);
|
|
39
|
+
assert.equal(normalizer.detectApplyPatch(''), false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('normalize — Add File', () => {
|
|
44
|
+
it('parses a single Add File section', () => {
|
|
45
|
+
const patch = [
|
|
46
|
+
'apply_patch <<\'PATCH\'',
|
|
47
|
+
'*** Begin Patch',
|
|
48
|
+
'*** Add File: index.html',
|
|
49
|
+
'<!doctype html>',
|
|
50
|
+
'<html><body>Hello</body></html>',
|
|
51
|
+
'*** End Patch',
|
|
52
|
+
'PATCH',
|
|
53
|
+
].join('\n');
|
|
54
|
+
|
|
55
|
+
const edits = normalizer.normalize(patch, 1000, 5);
|
|
56
|
+
assert.equal(edits.length, 1);
|
|
57
|
+
assert.equal(edits[0].type, 'edit');
|
|
58
|
+
assert.equal(edits[0].edit_type, 'create');
|
|
59
|
+
assert.equal(edits[0].file_path, 'index.html');
|
|
60
|
+
assert.ok(edits[0].content.includes('<!doctype html>'));
|
|
61
|
+
assert.ok(edits[0].content.includes('<html><body>Hello</body></html>'));
|
|
62
|
+
assert.equal(edits[0].step, 5);
|
|
63
|
+
assert.equal(edits[0].timestamp, 1000);
|
|
64
|
+
assert.ok(edits[0].token_count > 0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('parses multiple Add File sections', () => {
|
|
68
|
+
const patch = [
|
|
69
|
+
'apply_patch <<\'PATCH\'',
|
|
70
|
+
'*** Begin Patch',
|
|
71
|
+
'*** Add File: index.html',
|
|
72
|
+
'<html></html>',
|
|
73
|
+
'*** Add File: styles.css',
|
|
74
|
+
'body { margin: 0; }',
|
|
75
|
+
'*** Add File: app.js',
|
|
76
|
+
'console.log("hello");',
|
|
77
|
+
'*** End Patch',
|
|
78
|
+
'PATCH',
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
82
|
+
assert.equal(edits.length, 3);
|
|
83
|
+
assert.equal(edits[0].file_path, 'index.html');
|
|
84
|
+
assert.equal(edits[0].edit_type, 'create');
|
|
85
|
+
assert.equal(edits[1].file_path, 'styles.css');
|
|
86
|
+
assert.equal(edits[1].edit_type, 'create');
|
|
87
|
+
assert.equal(edits[2].file_path, 'app.js');
|
|
88
|
+
assert.equal(edits[2].edit_type, 'create');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('increments step numbers for multiple edits', () => {
|
|
92
|
+
const patch = [
|
|
93
|
+
'apply_patch <<\'PATCH\'',
|
|
94
|
+
'*** Begin Patch',
|
|
95
|
+
'*** Add File: a.js',
|
|
96
|
+
'file a',
|
|
97
|
+
'*** Add File: b.js',
|
|
98
|
+
'file b',
|
|
99
|
+
'*** End Patch',
|
|
100
|
+
'PATCH',
|
|
101
|
+
].join('\n');
|
|
102
|
+
|
|
103
|
+
const edits = normalizer.normalize(patch, 1000, 10);
|
|
104
|
+
assert.equal(edits[0].step, 10);
|
|
105
|
+
assert.equal(edits[1].step, 11);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('normalize — Update File', () => {
|
|
110
|
+
it('parses a simple Update File with a hunk', () => {
|
|
111
|
+
const patch = [
|
|
112
|
+
'apply_patch <<\'PATCH\'',
|
|
113
|
+
'*** Begin Patch',
|
|
114
|
+
'*** Update File: app.js',
|
|
115
|
+
'@@@ context @@@',
|
|
116
|
+
' const x = 1;',
|
|
117
|
+
'-const y = 2;',
|
|
118
|
+
'+const y = 3;',
|
|
119
|
+
' const z = 4;',
|
|
120
|
+
'*** End Patch',
|
|
121
|
+
'PATCH',
|
|
122
|
+
].join('\n');
|
|
123
|
+
|
|
124
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
125
|
+
assert.equal(edits.length, 1);
|
|
126
|
+
assert.equal(edits[0].type, 'edit');
|
|
127
|
+
assert.equal(edits[0].edit_type, 'modify');
|
|
128
|
+
assert.equal(edits[0].file_path, 'app.js');
|
|
129
|
+
assert.ok(edits[0].old_string.includes('const y = 2;'));
|
|
130
|
+
assert.ok(edits[0].new_string.includes('const y = 3;'));
|
|
131
|
+
assert.ok(edits[0].old_string.includes('const x = 1;'));
|
|
132
|
+
assert.ok(edits[0].new_string.includes('const x = 1;'));
|
|
133
|
+
assert.equal(edits[0].content, null);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('parses multiple hunks as separate edit steps', () => {
|
|
137
|
+
const patch = [
|
|
138
|
+
'apply_patch <<\'PATCH\'',
|
|
139
|
+
'*** Begin Patch',
|
|
140
|
+
'*** Update File: app.js',
|
|
141
|
+
'@@@ hunk 1 @@@',
|
|
142
|
+
'-old line 1',
|
|
143
|
+
'+new line 1',
|
|
144
|
+
'@@@ hunk 2 @@@',
|
|
145
|
+
'-old line 2',
|
|
146
|
+
'+new line 2',
|
|
147
|
+
'*** End Patch',
|
|
148
|
+
'PATCH',
|
|
149
|
+
].join('\n');
|
|
150
|
+
|
|
151
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
152
|
+
assert.equal(edits.length, 2);
|
|
153
|
+
assert.equal(edits[0].old_string, 'old line 1');
|
|
154
|
+
assert.equal(edits[0].new_string, 'new line 1');
|
|
155
|
+
assert.equal(edits[1].old_string, 'old line 2');
|
|
156
|
+
assert.equal(edits[1].new_string, 'new line 2');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('handles complex hunks with multiple old/new lines', () => {
|
|
160
|
+
const patch = [
|
|
161
|
+
'apply_patch <<\'PATCH\'',
|
|
162
|
+
'*** Begin Patch',
|
|
163
|
+
'*** Update File: config.json',
|
|
164
|
+
'@@@ context @@@',
|
|
165
|
+
' {',
|
|
166
|
+
'- "port": 3000,',
|
|
167
|
+
'- "host": "localhost"',
|
|
168
|
+
'+ "port": 8080,',
|
|
169
|
+
'+ "host": "0.0.0.0",',
|
|
170
|
+
'+ "debug": true',
|
|
171
|
+
' }',
|
|
172
|
+
'*** End Patch',
|
|
173
|
+
'PATCH',
|
|
174
|
+
].join('\n');
|
|
175
|
+
|
|
176
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
177
|
+
assert.equal(edits.length, 1);
|
|
178
|
+
assert.ok(edits[0].old_string.includes('"port": 3000,'));
|
|
179
|
+
assert.ok(edits[0].old_string.includes('"host": "localhost"'));
|
|
180
|
+
assert.ok(edits[0].new_string.includes('"port": 8080,'));
|
|
181
|
+
assert.ok(edits[0].new_string.includes('"host": "0.0.0.0",'));
|
|
182
|
+
assert.ok(edits[0].new_string.includes('"debug": true'));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('handles hunks without explicit @@@ headers', () => {
|
|
186
|
+
const patch = [
|
|
187
|
+
'apply_patch <<\'PATCH\'',
|
|
188
|
+
'*** Begin Patch',
|
|
189
|
+
'*** Update File: app.js',
|
|
190
|
+
'-const old = true;',
|
|
191
|
+
'+const old = false;',
|
|
192
|
+
'*** End Patch',
|
|
193
|
+
'PATCH',
|
|
194
|
+
].join('\n');
|
|
195
|
+
|
|
196
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
197
|
+
assert.equal(edits.length, 1);
|
|
198
|
+
assert.equal(edits[0].old_string, 'const old = true;');
|
|
199
|
+
assert.equal(edits[0].new_string, 'const old = false;');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('normalize — Delete File', () => {
|
|
204
|
+
it('parses a Delete File section', () => {
|
|
205
|
+
const patch = [
|
|
206
|
+
'apply_patch <<\'PATCH\'',
|
|
207
|
+
'*** Begin Patch',
|
|
208
|
+
'*** Delete File: obsolete.json',
|
|
209
|
+
'*** End Patch',
|
|
210
|
+
'PATCH',
|
|
211
|
+
].join('\n');
|
|
212
|
+
|
|
213
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
214
|
+
assert.equal(edits.length, 1);
|
|
215
|
+
assert.equal(edits[0].type, 'edit');
|
|
216
|
+
assert.equal(edits[0].edit_type, 'delete');
|
|
217
|
+
assert.equal(edits[0].file_path, 'obsolete.json');
|
|
218
|
+
assert.equal(edits[0].new_string, null);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('captures old content for Delete File when present', () => {
|
|
222
|
+
const patch = [
|
|
223
|
+
'apply_patch <<\'PATCH\'',
|
|
224
|
+
'*** Begin Patch',
|
|
225
|
+
'*** Delete File: old-config.json',
|
|
226
|
+
'{"key": "value"}',
|
|
227
|
+
'*** End Patch',
|
|
228
|
+
'PATCH',
|
|
229
|
+
].join('\n');
|
|
230
|
+
|
|
231
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
232
|
+
assert.equal(edits.length, 1);
|
|
233
|
+
assert.equal(edits[0].edit_type, 'delete');
|
|
234
|
+
assert.equal(edits[0].old_string, '{"key": "value"}');
|
|
235
|
+
assert.equal(edits[0].new_string, null);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('normalize — Mixed operations', () => {
|
|
240
|
+
it('parses a patch with Add, Update, and Delete in one block', () => {
|
|
241
|
+
const patch = [
|
|
242
|
+
'apply_patch <<\'PATCH\'',
|
|
243
|
+
'*** Begin Patch',
|
|
244
|
+
'*** Add File: new-file.js',
|
|
245
|
+
'console.log("new");',
|
|
246
|
+
'*** Update File: existing.js',
|
|
247
|
+
'@@@ context @@@',
|
|
248
|
+
'-const v = 1;',
|
|
249
|
+
'+const v = 2;',
|
|
250
|
+
'*** Delete File: old-file.js',
|
|
251
|
+
'*** End Patch',
|
|
252
|
+
'PATCH',
|
|
253
|
+
].join('\n');
|
|
254
|
+
|
|
255
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
256
|
+
assert.equal(edits.length, 3);
|
|
257
|
+
assert.equal(edits[0].edit_type, 'create');
|
|
258
|
+
assert.equal(edits[0].file_path, 'new-file.js');
|
|
259
|
+
assert.equal(edits[1].edit_type, 'modify');
|
|
260
|
+
assert.equal(edits[1].file_path, 'existing.js');
|
|
261
|
+
assert.equal(edits[2].edit_type, 'delete');
|
|
262
|
+
assert.equal(edits[2].file_path, 'old-file.js');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('handles action step object input', () => {
|
|
266
|
+
const step = {
|
|
267
|
+
arguments: {
|
|
268
|
+
command: [
|
|
269
|
+
'apply_patch <<\'PATCH\'',
|
|
270
|
+
'*** Begin Patch',
|
|
271
|
+
'*** Add File: test.txt',
|
|
272
|
+
'hello world',
|
|
273
|
+
'*** End Patch',
|
|
274
|
+
'PATCH',
|
|
275
|
+
].join('\n'),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const edits = normalizer.normalize(step, 1000, 1);
|
|
280
|
+
assert.equal(edits.length, 1);
|
|
281
|
+
assert.equal(edits[0].edit_type, 'create');
|
|
282
|
+
assert.equal(edits[0].file_path, 'test.txt');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('normalize — Edge cases', () => {
|
|
287
|
+
it('returns empty array for non-patch content', () => {
|
|
288
|
+
assert.deepEqual(normalizer.normalize('ls -la', 1000, 1), []);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('returns empty array for null input', () => {
|
|
292
|
+
assert.deepEqual(normalizer.normalize(null, 1000, 1), []);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('returns empty array for malformed patch (no Begin Patch)', () => {
|
|
296
|
+
const text = '*** Add File: test.txt\nhello\n*** End Patch';
|
|
297
|
+
assert.deepEqual(normalizer.normalize(text, 1000, 1), []);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('handles patch with no End Patch marker', () => {
|
|
301
|
+
const patch = [
|
|
302
|
+
'apply_patch <<\'PATCH\'',
|
|
303
|
+
'*** Begin Patch',
|
|
304
|
+
'*** Add File: file.js',
|
|
305
|
+
'content here',
|
|
306
|
+
].join('\n');
|
|
307
|
+
|
|
308
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
309
|
+
assert.equal(edits.length, 1);
|
|
310
|
+
assert.equal(edits[0].file_path, 'file.js');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('uses current timestamp when not provided', () => {
|
|
314
|
+
const patch = [
|
|
315
|
+
'apply_patch <<\'PATCH\'',
|
|
316
|
+
'*** Begin Patch',
|
|
317
|
+
'*** Add File: a.js',
|
|
318
|
+
'x',
|
|
319
|
+
'*** End Patch',
|
|
320
|
+
'PATCH',
|
|
321
|
+
].join('\n');
|
|
322
|
+
|
|
323
|
+
const before = Date.now() / 1000;
|
|
324
|
+
const edits = normalizer.normalize(patch, undefined, 1);
|
|
325
|
+
const after = Date.now() / 1000;
|
|
326
|
+
assert.ok(edits[0].timestamp >= before);
|
|
327
|
+
assert.ok(edits[0].timestamp <= after + 1);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('defaults startStep to 1', () => {
|
|
331
|
+
const patch = [
|
|
332
|
+
'apply_patch <<\'PATCH\'',
|
|
333
|
+
'*** Begin Patch',
|
|
334
|
+
'*** Add File: a.js',
|
|
335
|
+
'x',
|
|
336
|
+
'*** End Patch',
|
|
337
|
+
'PATCH',
|
|
338
|
+
].join('\n');
|
|
339
|
+
|
|
340
|
+
const edits = normalizer.normalize(patch, 1000);
|
|
341
|
+
assert.equal(edits[0].step, 1);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('handles file paths with directories', () => {
|
|
345
|
+
const patch = [
|
|
346
|
+
'apply_patch <<\'PATCH\'',
|
|
347
|
+
'*** Begin Patch',
|
|
348
|
+
'*** Add File: src/components/Header.jsx',
|
|
349
|
+
'<div>Header</div>',
|
|
350
|
+
'*** End Patch',
|
|
351
|
+
'PATCH',
|
|
352
|
+
].join('\n');
|
|
353
|
+
|
|
354
|
+
const edits = normalizer.normalize(patch, 1000, 1);
|
|
355
|
+
assert.equal(edits[0].file_path, 'src/components/Header.jsx');
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('estimateTokens', () => {
|
|
361
|
+
it('estimates at ~4 chars per token', () => {
|
|
362
|
+
assert.equal(estimateTokens('abcd'), 1);
|
|
363
|
+
assert.equal(estimateTokens('abcdefgh'), 2);
|
|
364
|
+
assert.equal(estimateTokens('a'.repeat(100)), 25);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('returns at least 1 for non-empty input', () => {
|
|
368
|
+
assert.equal(estimateTokens('a'), 1);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('returns 0 for empty/null input', () => {
|
|
372
|
+
assert.equal(estimateTokens(''), 0);
|
|
373
|
+
assert.equal(estimateTokens(null), 0);
|
|
374
|
+
assert.equal(estimateTokens(undefined), 0);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
@@ -89,6 +89,34 @@ describe('EnvelopeBuilder', () => {
|
|
|
89
89
|
assert.equal(envelope.trajectory_log[0].token_count, 100_000);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
it('includes leaf_context in metadata defaulting to null', () => {
|
|
93
|
+
const builder = new EnvelopeBuilder('sess_1', 'user_1', metadata);
|
|
94
|
+
builder.addStep({ step: 1, type: 'thought', timestamp: 123 });
|
|
95
|
+
const envelope = builder.flush();
|
|
96
|
+
assert.equal(envelope.metadata.leaf_context, null);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('preserves provided leaf_context', () => {
|
|
100
|
+
const metaWithLeaf = { ...metadata, leaf_context: { leaf_id: 'py_v1', leaf_version: '1.0', confidence_at_route: 0.4, chassis_model: 'Qwen' } };
|
|
101
|
+
const builder = new EnvelopeBuilder('sess_1', 'user_1', metaWithLeaf);
|
|
102
|
+
builder.addStep({ step: 1, type: 'thought', timestamp: 123 });
|
|
103
|
+
const envelope = builder.flush();
|
|
104
|
+
assert.equal(envelope.metadata.leaf_context.leaf_id, 'py_v1');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('SESSION_CLOSE passes through quality_tier and training fields', () => {
|
|
108
|
+
const builder = new EnvelopeBuilder('sess_1', 'user_1', metadata);
|
|
109
|
+
const outcome = {
|
|
110
|
+
status: 'SUCCESS', total_steps: 10, total_chunks: 1,
|
|
111
|
+
quality_tier: 'TIER_A', quality_tier_reason: 'high_quality_no_errors',
|
|
112
|
+
training_eligible: true, training_exclusion_reason: null,
|
|
113
|
+
};
|
|
114
|
+
const close = builder.buildSessionClose(outcome);
|
|
115
|
+
assert.equal(close.outcome.quality_tier, 'TIER_A');
|
|
116
|
+
assert.equal(close.outcome.training_eligible, true);
|
|
117
|
+
assert.equal(close.outcome.training_exclusion_reason, null);
|
|
118
|
+
});
|
|
119
|
+
|
|
92
120
|
it('chunk sequence increments correctly', () => {
|
|
93
121
|
const builder = new EnvelopeBuilder('sess_1', 'user_1', metadata);
|
|
94
122
|
let first = null;
|