groove-dev 0.27.102 → 0.27.104

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 (66) hide show
  1. package/CLAUDE.md +0 -7
  2. package/moe-training/client/domain-tagger.js +205 -0
  3. package/moe-training/client/edit-normalizer.js +188 -0
  4. package/moe-training/client/envelope-builder.js +1 -1
  5. package/moe-training/client/index.js +1 -0
  6. package/moe-training/client/parsers/claude-code.js +56 -9
  7. package/moe-training/client/parsers/codex.js +25 -5
  8. package/moe-training/client/parsers/gemini.js +21 -2
  9. package/moe-training/client/parsers/grok.js +18 -0
  10. package/moe-training/client/step-classifier.js +1 -1
  11. package/moe-training/client/trajectory-capture.js +109 -8
  12. package/moe-training/server/routes/ingest.js +26 -0
  13. package/moe-training/server/verifier.js +34 -0
  14. package/moe-training/shared/constants.js +9 -0
  15. package/moe-training/shared/envelope-schema.js +128 -2
  16. package/moe-training/test/client/domain-tagger.test.js +203 -0
  17. package/moe-training/test/client/edit-normalizer.test.js +376 -0
  18. package/moe-training/test/client/envelope-builder.test.js +28 -0
  19. package/moe-training/test/client/parsers/claude-code.test.js +248 -38
  20. package/moe-training/test/client/parsers/codex.test.js +2 -0
  21. package/moe-training/test/client/parsers/gemini.test.js +2 -0
  22. package/moe-training/test/client/step-classifier.test.js +42 -0
  23. package/moe-training/test/client/trajectory-capture.test.js +440 -0
  24. package/moe-training/test/server/verifier.test.js +94 -0
  25. package/moe-training/test/shared/envelope-schema.test.js +291 -0
  26. package/node_modules/@groove-dev/cli/package.json +1 -1
  27. package/node_modules/@groove-dev/daemon/package.json +1 -1
  28. package/node_modules/@groove-dev/daemon/src/api.js +19 -3
  29. package/node_modules/@groove-dev/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
  30. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  31. package/node_modules/@groove-dev/gui/package.json +1 -1
  32. package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
  33. package/node_modules/@groove-dev/gui/src/views/settings.jsx +23 -2
  34. package/node_modules/moe-training/client/domain-tagger.js +205 -0
  35. package/node_modules/moe-training/client/edit-normalizer.js +188 -0
  36. package/node_modules/moe-training/client/envelope-builder.js +1 -1
  37. package/node_modules/moe-training/client/index.js +1 -0
  38. package/node_modules/moe-training/client/parsers/claude-code.js +56 -9
  39. package/node_modules/moe-training/client/parsers/codex.js +25 -5
  40. package/node_modules/moe-training/client/parsers/gemini.js +21 -2
  41. package/node_modules/moe-training/client/parsers/grok.js +18 -0
  42. package/node_modules/moe-training/client/step-classifier.js +1 -1
  43. package/node_modules/moe-training/client/trajectory-capture.js +109 -8
  44. package/node_modules/moe-training/server/routes/ingest.js +26 -0
  45. package/node_modules/moe-training/server/verifier.js +34 -0
  46. package/node_modules/moe-training/shared/constants.js +9 -0
  47. package/node_modules/moe-training/shared/envelope-schema.js +128 -2
  48. package/node_modules/moe-training/test/client/domain-tagger.test.js +203 -0
  49. package/node_modules/moe-training/test/client/edit-normalizer.test.js +376 -0
  50. package/node_modules/moe-training/test/client/envelope-builder.test.js +28 -0
  51. package/node_modules/moe-training/test/client/parsers/claude-code.test.js +248 -38
  52. package/node_modules/moe-training/test/client/parsers/codex.test.js +2 -0
  53. package/node_modules/moe-training/test/client/parsers/gemini.test.js +2 -0
  54. package/node_modules/moe-training/test/client/step-classifier.test.js +42 -0
  55. package/node_modules/moe-training/test/client/trajectory-capture.test.js +440 -0
  56. package/node_modules/moe-training/test/server/verifier.test.js +94 -0
  57. package/node_modules/moe-training/test/shared/envelope-schema.test.js +291 -0
  58. package/package.json +1 -1
  59. package/packages/cli/package.json +1 -1
  60. package/packages/daemon/package.json +1 -1
  61. package/packages/daemon/src/api.js +19 -3
  62. package/packages/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
  63. package/packages/gui/dist/index.html +1 -1
  64. package/packages/gui/package.json +1 -1
  65. package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
  66. package/packages/gui/src/views/settings.jsx +23 -2
@@ -3,6 +3,7 @@
3
3
  import { describe, it } from 'node:test';
4
4
  import assert from 'node:assert/strict';
5
5
  import { validateEnvelope, STEP_TYPES } from '../../shared/envelope-schema.js';
6
+ import { TRAINING_EXCLUSION_REASONS } from '../../shared/constants.js';
6
7
 
7
8
  const VALID_HMAC = 'a'.repeat(64);
8
9
  const VALID_APP_HASH = 'b'.repeat(64);
@@ -89,6 +90,17 @@ describe('envelope-schema', () => {
89
90
  }
90
91
  });
91
92
 
93
+ it('edit step type is valid', () => {
94
+ const env = validEnvelope();
95
+ env.trajectory_log = [{
96
+ step: 1, type: 'edit', timestamp: Date.now() / 1000,
97
+ file_path: 'index.html', edit_type: 'create', content: '<html></html>',
98
+ token_count: 5,
99
+ }];
100
+ const result = validateEnvelope(env);
101
+ assert.equal(result.valid, true);
102
+ });
103
+
92
104
  // --- New security tests ---
93
105
 
94
106
  it('rejects trajectory_log with > 500 steps', () => {
@@ -348,4 +360,283 @@ describe('envelope-schema', () => {
348
360
  assert.equal(result.valid, false);
349
361
  assert.ok(result.errors.some(e => e.includes('session_hmac')));
350
362
  });
363
+
364
+ // --- Observation truncation fields ---
365
+
366
+ it('accepts observation step with truncated and original_token_count', () => {
367
+ const env = validEnvelope();
368
+ env.trajectory_log.push({
369
+ step: 3, type: 'observation', timestamp: Date.now() / 1000,
370
+ content: 'output', token_count: 100, truncated: false, original_token_count: 100,
371
+ });
372
+ const result = validateEnvelope(env);
373
+ assert.equal(result.valid, true);
374
+ });
375
+
376
+ it('accepts observation step with truncated=true', () => {
377
+ const env = validEnvelope();
378
+ env.trajectory_log.push({
379
+ step: 3, type: 'observation', timestamp: Date.now() / 1000,
380
+ content: 'output...', token_count: 4096, truncated: true, original_token_count: 9000,
381
+ });
382
+ const result = validateEnvelope(env);
383
+ assert.equal(result.valid, true);
384
+ });
385
+
386
+ it('rejects non-boolean truncated field', () => {
387
+ const env = validEnvelope();
388
+ env.trajectory_log[0].truncated = 'yes';
389
+ const result = validateEnvelope(env);
390
+ assert.equal(result.valid, false);
391
+ assert.ok(result.errors.some(e => e.includes('truncated')));
392
+ });
393
+
394
+ it('rejects negative original_token_count', () => {
395
+ const env = validEnvelope();
396
+ env.trajectory_log[0].original_token_count = -5;
397
+ const result = validateEnvelope(env);
398
+ assert.equal(result.valid, false);
399
+ assert.ok(result.errors.some(e => e.includes('original_token_count')));
400
+ });
401
+
402
+ it('steps without truncation fields still validate (backward compat)', () => {
403
+ const env = validEnvelope();
404
+ assert.equal(env.trajectory_log[0].truncated, undefined);
405
+ const result = validateEnvelope(env);
406
+ assert.equal(result.valid, true);
407
+ });
408
+
409
+ // --- domain_tags ---
410
+
411
+ it('accepts null domain_tags in metadata', () => {
412
+ const env = validEnvelope();
413
+ env.metadata.domain_tags = null;
414
+ const result = validateEnvelope(env);
415
+ assert.equal(result.valid, true);
416
+ });
417
+
418
+ it('accepts absent domain_tags in metadata (backward compat)', () => {
419
+ const env = validEnvelope();
420
+ assert.equal(env.metadata.domain_tags, undefined);
421
+ const result = validateEnvelope(env);
422
+ assert.equal(result.valid, true);
423
+ });
424
+
425
+ it('accepts valid domain_tags object', () => {
426
+ const env = validEnvelope();
427
+ env.metadata.domain_tags = {
428
+ primary: { domain: 'python', confidence: 0.42 },
429
+ secondary: { domain: 'data_science_ml', confidence: 0.23 },
430
+ tertiary: { domain: 'devops_docker', confidence: 0.11 },
431
+ };
432
+ const result = validateEnvelope(env);
433
+ assert.equal(result.valid, true);
434
+ });
435
+
436
+ it('rejects domain_tags with invalid confidence', () => {
437
+ const env = validEnvelope();
438
+ env.metadata.domain_tags = {
439
+ primary: { domain: 'python', confidence: 1.5 },
440
+ secondary: { domain: 'rust', confidence: 0.2 },
441
+ tertiary: { domain: 'react_frontend', confidence: 0.1 },
442
+ };
443
+ const result = validateEnvelope(env);
444
+ assert.equal(result.valid, false);
445
+ assert.ok(result.errors.some(e => e.includes('confidence')));
446
+ });
447
+
448
+ it('rejects domain_tags missing tertiary', () => {
449
+ const env = validEnvelope();
450
+ env.metadata.domain_tags = {
451
+ primary: { domain: 'python', confidence: 0.4 },
452
+ secondary: { domain: 'rust', confidence: 0.2 },
453
+ };
454
+ const result = validateEnvelope(env);
455
+ assert.equal(result.valid, false);
456
+ assert.ok(result.errors.some(e => e.includes('tertiary')));
457
+ });
458
+
459
+ // --- leaf_context ---
460
+
461
+ it('accepts null leaf_context in metadata', () => {
462
+ const env = validEnvelope();
463
+ env.metadata.leaf_context = null;
464
+ const result = validateEnvelope(env);
465
+ assert.equal(result.valid, true);
466
+ });
467
+
468
+ it('accepts absent leaf_context in metadata (backward compat)', () => {
469
+ const env = validEnvelope();
470
+ assert.equal(env.metadata.leaf_context, undefined);
471
+ const result = validateEnvelope(env);
472
+ assert.equal(result.valid, true);
473
+ });
474
+
475
+ it('accepts valid leaf_context object', () => {
476
+ const env = validEnvelope();
477
+ env.metadata.leaf_context = {
478
+ leaf_id: 'python_expert_v3', leaf_version: '1.2.0',
479
+ confidence_at_route: 0.42, chassis_model: 'Qwen/Qwen3-0.6B',
480
+ };
481
+ const result = validateEnvelope(env);
482
+ assert.equal(result.valid, true);
483
+ });
484
+
485
+ it('rejects leaf_context with invalid confidence_at_route', () => {
486
+ const env = validEnvelope();
487
+ env.metadata.leaf_context = {
488
+ leaf_id: 'test', leaf_version: '1.0', confidence_at_route: 1.5, chassis_model: 'test',
489
+ };
490
+ const result = validateEnvelope(env);
491
+ assert.equal(result.valid, false);
492
+ assert.ok(result.errors.some(e => e.includes('confidence_at_route')));
493
+ });
494
+
495
+ // --- Quality tier in SESSION_CLOSE ---
496
+
497
+ it('SESSION_CLOSE accepts quality_tier and training fields', () => {
498
+ const close = {
499
+ envelope_id: 'env_close-qt',
500
+ session_id: 'sess_test-qt',
501
+ type: 'SESSION_CLOSE',
502
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
503
+ outcome: {
504
+ status: 'SUCCESS', total_steps: 10, total_chunks: 1,
505
+ quality_tier: 'TIER_A', quality_tier_reason: 'high_quality_no_errors',
506
+ training_eligible: true, training_exclusion_reason: null,
507
+ },
508
+ };
509
+ const result = validateEnvelope(close);
510
+ assert.equal(result.valid, true);
511
+ });
512
+
513
+ it('SESSION_CLOSE rejects invalid quality_tier', () => {
514
+ const close = {
515
+ envelope_id: 'env_close-qt2',
516
+ session_id: 'sess_test-qt2',
517
+ type: 'SESSION_CLOSE',
518
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
519
+ outcome: {
520
+ status: 'SUCCESS', total_steps: 10, total_chunks: 1,
521
+ quality_tier: 'TIER_Z',
522
+ },
523
+ };
524
+ const result = validateEnvelope(close);
525
+ assert.equal(result.valid, false);
526
+ assert.ok(result.errors.some(e => e.includes('quality_tier')));
527
+ });
528
+
529
+ it('SESSION_CLOSE rejects invalid training_exclusion_reason', () => {
530
+ const close = {
531
+ envelope_id: 'env_close-te',
532
+ session_id: 'sess_test-te',
533
+ type: 'SESSION_CLOSE',
534
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
535
+ outcome: {
536
+ status: 'SUCCESS', total_steps: 10, total_chunks: 1,
537
+ training_eligible: false, training_exclusion_reason: 'bad_vibes',
538
+ },
539
+ };
540
+ const result = validateEnvelope(close);
541
+ assert.equal(result.valid, false);
542
+ assert.ok(result.errors.some(e => e.includes('training_exclusion_reason')));
543
+ });
544
+
545
+ it('SESSION_CLOSE rejects non-boolean training_eligible', () => {
546
+ const close = {
547
+ envelope_id: 'env_close-te2',
548
+ session_id: 'sess_test-te2',
549
+ type: 'SESSION_CLOSE',
550
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
551
+ outcome: {
552
+ status: 'SUCCESS', total_steps: 10, total_chunks: 1,
553
+ training_eligible: 'yes',
554
+ },
555
+ };
556
+ const result = validateEnvelope(close);
557
+ assert.equal(result.valid, false);
558
+ assert.ok(result.errors.some(e => e.includes('training_eligible')));
559
+ });
560
+
561
+ it('SESSION_CLOSE without new fields still validates (backward compat)', () => {
562
+ const close = {
563
+ envelope_id: 'env_close-bc',
564
+ session_id: 'sess_test-bc',
565
+ type: 'SESSION_CLOSE',
566
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
567
+ outcome: { status: 'SUCCESS', total_steps: 10, total_chunks: 1 },
568
+ };
569
+ const result = validateEnvelope(close);
570
+ assert.equal(result.valid, true);
571
+ });
572
+
573
+ // --- USER_FEEDBACK validation ---
574
+
575
+ it('valid USER_FEEDBACK passes', () => {
576
+ const feedback = {
577
+ envelope_id: 'env_fb_1',
578
+ session_id: 'sess_fb_1',
579
+ type: 'USER_FEEDBACK',
580
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
581
+ feedback: {
582
+ signal: 'accepted', timestamp: Date.now() / 1000,
583
+ context: 'user ran code without modifications',
584
+ target_step: 10, revision_rounds: 0, delta_summary: null,
585
+ },
586
+ };
587
+ const result = validateEnvelope(feedback);
588
+ assert.equal(result.valid, true);
589
+ });
590
+
591
+ it('USER_FEEDBACK rejects invalid signal', () => {
592
+ const feedback = {
593
+ envelope_id: 'env_fb_2',
594
+ session_id: 'sess_fb_2',
595
+ type: 'USER_FEEDBACK',
596
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
597
+ feedback: { signal: 'thumbs_up', timestamp: Date.now() / 1000 },
598
+ };
599
+ const result = validateEnvelope(feedback);
600
+ assert.equal(result.valid, false);
601
+ assert.ok(result.errors.some(e => e.includes('signal')));
602
+ });
603
+
604
+ it('USER_FEEDBACK rejects missing feedback object', () => {
605
+ const feedback = {
606
+ envelope_id: 'env_fb_3',
607
+ session_id: 'sess_fb_3',
608
+ type: 'USER_FEEDBACK',
609
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
610
+ };
611
+ const result = validateEnvelope(feedback);
612
+ assert.equal(result.valid, false);
613
+ assert.ok(result.errors.some(e => e.includes('feedback')));
614
+ });
615
+
616
+ it('USER_FEEDBACK accepts all valid signal types', () => {
617
+ for (const signal of ['accepted', 'modified', 'rejected', 'iterated']) {
618
+ const feedback = {
619
+ envelope_id: `env_fb_${signal}`,
620
+ session_id: `sess_fb_${signal}`,
621
+ type: 'USER_FEEDBACK',
622
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
623
+ feedback: { signal, timestamp: Date.now() / 1000 },
624
+ };
625
+ const result = validateEnvelope(feedback);
626
+ assert.equal(result.valid, true, `Signal "${signal}" should be valid`);
627
+ }
628
+ });
629
+
630
+ it('USER_FEEDBACK rejects negative revision_rounds', () => {
631
+ const feedback = {
632
+ envelope_id: 'env_fb_neg',
633
+ session_id: 'sess_fb_neg',
634
+ type: 'USER_FEEDBACK',
635
+ attestation: { session_hmac: VALID_HMAC, sequence: 0, app_version_hash: VALID_APP_HASH },
636
+ feedback: { signal: 'iterated', timestamp: Date.now() / 1000, revision_rounds: -1 },
637
+ };
638
+ const result = validateEnvelope(feedback);
639
+ assert.equal(result.valid, false);
640
+ assert.ok(result.errors.some(e => e.includes('revision_rounds')));
641
+ });
351
642
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.102",
3
+ "version": "0.27.104",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.102",
3
+ "version": "0.27.104",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -758,9 +758,25 @@ export function createApi(app, daemon) {
758
758
 
759
759
  proc.on('close', (code) => {
760
760
  clearTimeout(timeout);
761
- respond(code === 0
762
- ? { status: 'authenticated' }
763
- : { status: 'error', error: stderr.slice(-200) || `Login failed (exit ${code})` });
761
+ if (code === 0) {
762
+ let hasKey = false;
763
+ try {
764
+ const authPath = resolve(homedir(), '.codex', 'auth.json');
765
+ if (existsSync(authPath)) {
766
+ const auth = JSON.parse(readFileSync(authPath, 'utf8'));
767
+ const token = auth.OPENAI_API_KEY
768
+ || (auth.auth_mode === 'chatgpt' && auth.tokens?.id_token)
769
+ || null;
770
+ if (token) {
771
+ daemon.credentials.setKey('codex', token);
772
+ hasKey = true;
773
+ }
774
+ }
775
+ } catch { /* auth.json missing or malformed — login still succeeded */ }
776
+ respond({ status: 'authenticated', hasKey });
777
+ } else {
778
+ respond({ status: 'error', error: stderr.slice(-200) || `Login failed (exit ${code})` });
779
+ }
764
780
  });
765
781
 
766
782
  proc.on('error', (err) => {