multimodel-dev-os 2.8.0 → 2.9.1

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 (51) hide show
  1. package/.ai/plugins/catalog/.ai/checks/pre-commit-gate.md +14 -0
  2. package/.ai/plugins/catalog/.ai/skills/checkout-ops.md +12 -0
  3. package/.ai/plugins/catalog/.ai/skills/git-operations.md +21 -0
  4. package/.ai/plugins/catalog/.ai/skills/nextjs-builder.md +12 -0
  5. package/.ai/plugins/catalog/.ai/skills/release-ops.md +12 -0
  6. package/.ai/plugins/catalog/.ai/skills/seo-audit-ops.md +14 -0
  7. package/.ai/plugins/catalog/.ai/skills/wp-helper.md +13 -0
  8. package/.ai/plugins/catalog/README.md +34 -0
  9. package/.ai/plugins/catalog/ecommerce-workflows.yaml +14 -0
  10. package/.ai/plugins/catalog/git-workflows.yaml +22 -0
  11. package/.ai/plugins/catalog/nextjs-workflows.yaml +14 -0
  12. package/.ai/plugins/catalog/release-workflows.yaml +14 -0
  13. package/.ai/plugins/catalog/seo-workflows.yaml +19 -0
  14. package/.ai/plugins/catalog/wordpress-workflows.yaml +14 -0
  15. package/.ai/plugins/catalog.yaml +161 -0
  16. package/.ai/policies/registry-policy.yaml +51 -0
  17. package/.ai/registries/sources.yaml +15 -0
  18. package/.ai/registry-cache/README.md +35 -0
  19. package/.ai/schema/registry-manifest.schema.json +57 -0
  20. package/.ai/schema/registry-policy.schema.json +66 -0
  21. package/README.md +7 -6
  22. package/bin/multimodel-dev-os.js +1421 -61
  23. package/docs/.vitepress/config.js +16 -2
  24. package/docs/CLI.md +54 -1
  25. package/docs/architecture.md +9 -3
  26. package/docs/catalog-authoring.md +63 -0
  27. package/docs/catalog.md +72 -0
  28. package/docs/comparison.md +1 -0
  29. package/docs/dashboard.md +22 -9
  30. package/docs/faq.md +20 -1
  31. package/docs/plugin-authoring.md +7 -1
  32. package/docs/plugin-catalog.md +35 -0
  33. package/docs/plugin-hooks.md +15 -0
  34. package/docs/public/llms-full.txt +18 -1
  35. package/docs/public/llms.txt +17 -1
  36. package/docs/public/sitemap.xml +45 -0
  37. package/docs/quickstart.md +17 -0
  38. package/docs/registry-policy.md +93 -0
  39. package/docs/registry-security.md +67 -0
  40. package/docs/registry-sync.md +106 -0
  41. package/docs/remote-catalog-authoring.md +139 -0
  42. package/docs/repository-command-center.md +2 -0
  43. package/docs/trusted-registries.md +77 -0
  44. package/docs/tui-safety.md +1 -1
  45. package/docs/v2-roadmap.md +21 -7
  46. package/docs/workflow-marketplace.md +22 -0
  47. package/docs/workflow-orchestration.md +6 -0
  48. package/package.json +1 -1
  49. package/scripts/install.ps1 +0 -0
  50. package/scripts/install.sh +0 -0
  51. package/scripts/verify.js +546 -10
package/scripts/verify.js CHANGED
@@ -10,6 +10,7 @@ import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
10
10
  import { join, resolve, dirname } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { execSync } from 'child_process';
13
+ import { createHash } from 'crypto';
13
14
 
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = dirname(__filename);
@@ -311,13 +312,51 @@ checkFile('docs/public/assets/terminal-demo.svg');
311
312
  checkFile('docs/public/assets/architecture-preview.svg');
312
313
 
313
314
  // --- YAML Parser Helper ---
315
+ function parseFlowArray(str) {
316
+ const contents = str.slice(1, -1).trim();
317
+ if (!contents) return [];
318
+
319
+ const result = [];
320
+ const regex = /"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|([^,\s][^,]*[^,\s]|[^,\s])/g;
321
+ let match;
322
+ while ((match = regex.exec(contents)) !== null) {
323
+ if (match[1] !== undefined) {
324
+ result.push(match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\'));
325
+ } else if (match[2] !== undefined) {
326
+ result.push(match[2].replace(/\\'/g, "'").replace(/\\\\/g, '\\'));
327
+ } else if (match[3] !== undefined) {
328
+ let val = match[3].trim();
329
+ if (val === 'true') val = true;
330
+ else if (val === 'false') val = false;
331
+ else if (val === 'null') val = null;
332
+ else if (/^-?\d+$/.test(val)) val = parseInt(val, 10);
333
+ result.push(val);
334
+ }
335
+ }
336
+ return result;
337
+ }
338
+
314
339
  function parseYaml(content) {
315
340
  try {
316
341
  const root = {};
317
342
  const stack = [{ obj: root, indent: -1, key: null, isArray: false }];
318
343
  const lines = content.split(/\r?\n/);
319
344
  for (let line of lines) {
320
- const commentIdx = line.indexOf('#');
345
+ // Find comment index outside quotes
346
+ let commentIdx = -1;
347
+ let insideDouble = false;
348
+ let insideSingle = false;
349
+ for (let i = 0; i < line.length; i++) {
350
+ const char = line[i];
351
+ if (char === '"' && (i === 0 || line[i-1] !== '\\')) {
352
+ insideDouble = !insideDouble;
353
+ } else if (char === "'" && (i === 0 || line[i-1] !== '\\')) {
354
+ insideSingle = !insideSingle;
355
+ } else if (char === '#' && !insideDouble && !insideSingle) {
356
+ commentIdx = i;
357
+ break;
358
+ }
359
+ }
321
360
  if (commentIdx !== -1) {
322
361
  line = line.substring(0, commentIdx);
323
362
  }
@@ -340,17 +379,30 @@ function parseYaml(content) {
340
379
  }
341
380
  const colonIdx = trimmed.indexOf(':');
342
381
  if (colonIdx === -1) {
343
- parent.obj.push(trimmed);
382
+ let val = trimmed;
383
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
384
+ val = val.substring(1, val.length - 1);
385
+ }
386
+ if (val.startsWith('[') && val.endsWith(']')) {
387
+ val = parseFlowArray(val);
388
+ }
389
+ parent.obj.push(val);
344
390
  } else {
345
391
  const key = trimmed.substring(0, colonIdx).trim();
346
392
  let val = trimmed.substring(colonIdx + 1).trim();
393
+ let isQuoted = false;
347
394
  if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
348
395
  val = val.substring(1, val.length - 1);
396
+ isQuoted = true;
397
+ }
398
+ if (val.startsWith('[') && val.endsWith(']')) {
399
+ val = parseFlowArray(val);
400
+ } else if (!isQuoted) {
401
+ if (val === 'true') val = true;
402
+ else if (val === 'false') val = false;
403
+ else if (val === 'null') val = null;
404
+ else if (/^-?\d+$/.test(val)) val = parseInt(val, 10);
349
405
  }
350
- if (val === 'true') val = true;
351
- else if (val === 'false') val = false;
352
- else if (val === 'null') val = null;
353
- else if (/^\d+$/.test(val)) val = parseInt(val, 10);
354
406
  const newObj = { [key]: val };
355
407
  parent.obj.push(newObj);
356
408
  stack.push({ obj: newObj, indent: indent, key: key, isArray: false });
@@ -360,13 +412,19 @@ function parseYaml(content) {
360
412
  if (colonIdx === -1) continue;
361
413
  const key = trimmed.substring(0, colonIdx).trim();
362
414
  let val = trimmed.substring(colonIdx + 1).trim();
415
+ let isQuoted = false;
363
416
  if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
364
417
  val = val.substring(1, val.length - 1);
418
+ isQuoted = true;
419
+ }
420
+ if (val.startsWith('[') && val.endsWith(']')) {
421
+ val = parseFlowArray(val);
422
+ } else if (!isQuoted) {
423
+ if (val === 'true') val = true;
424
+ else if (val === 'false') val = false;
425
+ else if (val === 'null') val = null;
426
+ else if (/^\d+$/.test(val)) val = parseInt(val, 10);
365
427
  }
366
- if (val === 'true') val = true;
367
- else if (val === 'false') val = false;
368
- else if (val === 'null') val = null;
369
- else if (/^\d+$/.test(val)) val = parseInt(val, 10);
370
428
  if (val === '') {
371
429
  parent.obj[key] = {};
372
430
  stack.push({ obj: parent.obj[key], indent: indent, key: key, isArray: false });
@@ -456,11 +514,297 @@ try {
456
514
  console.error(` ${RED}✗${NC} CLI help is missing scan, memory, status, workflow, or handoff commands`);
457
515
  fail++;
458
516
  }
517
+
518
+ if (helpOutput.includes('dashboard') && helpOutput.includes('ui') && helpOutput.includes('plugin')) {
519
+ console.log(` ${GREEN}✓${NC} CLI help includes dashboard, ui, and plugin commands`);
520
+ pass++;
521
+ } else {
522
+ console.error(` ${RED}✗${NC} CLI help is missing dashboard, ui, or plugin commands`);
523
+ fail++;
524
+ }
459
525
  } catch (e) {
460
526
  console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js --help failed: ${e.message}`);
461
527
  fail++;
462
528
  }
463
529
 
530
+ // --- v2.8.0 / v2.8.1 Dashboard & Plugin Tests ---
531
+ console.log('\nRunning TUI Dashboard & Plugin Pre-Flight Tests...');
532
+
533
+ // 1. Dashboard dry-run check
534
+ try {
535
+ const output = execSync('node bin/multimodel-dev-os.js dashboard --dry-run', { cwd: projectRoot, encoding: 'utf8' });
536
+ if (output.includes('Headless/CI Preview') && output.includes('npx multimodel-dev-os')) {
537
+ console.log(` ${GREEN}✓${NC} dashboard --dry-run executes successfully and displays headless preview`);
538
+ pass++;
539
+ } else {
540
+ console.error(` ${RED}✗${NC} dashboard --dry-run output is missing preview strings`);
541
+ fail++;
542
+ }
543
+ } catch (e) {
544
+ console.error(` ${RED}✗${NC} dashboard --dry-run execution failed: ${e.message}`);
545
+ fail++;
546
+ }
547
+
548
+ // 2. Dashboard list-actions check
549
+ try {
550
+ const output = execSync('node bin/multimodel-dev-os.js dashboard --list-actions', { cwd: projectRoot, encoding: 'utf8' });
551
+ if (output.includes('Headless/CI Preview') && output.includes('npx multimodel-dev-os')) {
552
+ console.log(` ${GREEN}✓${NC} dashboard --list-actions executes successfully and displays headless preview`);
553
+ pass++;
554
+ } else {
555
+ console.error(` ${RED}✗${NC} dashboard --list-actions output is missing preview strings`);
556
+ fail++;
557
+ }
558
+ } catch (e) {
559
+ console.error(` ${RED}✗${NC} dashboard --list-actions execution failed: ${e.message}`);
560
+ fail++;
561
+ }
562
+
563
+ // 3. Plugin validation check
564
+ try {
565
+ const output = execSync('node bin/multimodel-dev-os.js plugin validate .ai/plugins/plugin.example.yaml', { cwd: projectRoot, encoding: 'utf8' });
566
+ if (output.includes('fully valid and compliant')) {
567
+ console.log(` ${GREEN}✓${NC} plugin validate on example manifest passes successfully`);
568
+ pass++;
569
+ } else {
570
+ console.error(` ${RED}✗${NC} plugin validate on example manifest failed to report compliance`);
571
+ fail++;
572
+ }
573
+ } catch (e) {
574
+ console.error(` ${RED}✗${NC} plugin validate execution failed: ${e.message}`);
575
+ fail++;
576
+ }
577
+
578
+ // 4. Plugin install refusal check (no --approved, should exit with code 1)
579
+ try {
580
+ execSync('node bin/multimodel-dev-os.js plugin install .ai/plugins/plugin.example.yaml', { cwd: projectRoot, stdio: 'pipe' });
581
+ console.error(` ${RED}✗${NC} plugin install without --approved should have exited with code 1, but exited with 0`);
582
+ fail++;
583
+ } catch (e) {
584
+ if (e.status === 1) {
585
+ const stdErrOut = e.stderr ? e.stderr.toString() : '';
586
+ const stdOutOut = e.stdout ? e.stdout.toString() : '';
587
+ if (stdErrOut.includes('Installation refused') || stdOutOut.includes('Installation refused')) {
588
+ console.log(` ${GREEN}✓${NC} plugin install without --approved correctly refuses and exits with code 1`);
589
+ pass++;
590
+ } else {
591
+ console.error(` ${RED}✗${NC} plugin install without --approved exited with 1 but missing refusal message`);
592
+ fail++;
593
+ }
594
+ } else {
595
+ console.error(` ${RED}✗${NC} plugin install without --approved failed with unexpected code ${e.status}: ${e.message}`);
596
+ fail++;
597
+ }
598
+ }
599
+
600
+ // 5. Plugin status check
601
+ try {
602
+ execSync('node bin/multimodel-dev-os.js plugin status', { cwd: projectRoot, stdio: 'ignore' });
603
+ console.log(` ${GREEN}✓${NC} plugin status executes without crashing`);
604
+ pass++;
605
+ } catch (e) {
606
+ console.error(` ${RED}✗${NC} plugin status execution failed: ${e.message}`);
607
+ fail++;
608
+ }
609
+
610
+ // --- v2.9.0 Catalog & Marketplace Tests ---
611
+ console.log('\nRunning Catalog & Marketplace Pre-Flight Tests...');
612
+
613
+ // 1. Catalog list check
614
+ try {
615
+ const output = execSync('node bin/multimodel-dev-os.js catalog list', { cwd: projectRoot, encoding: 'utf8' });
616
+ if (output.includes('Workflow Marketplace & Plugin Catalog') && output.includes('git-workflows')) {
617
+ console.log(` ${GREEN}✓${NC} catalog list executes successfully and displays catalog listings`);
618
+ pass++;
619
+ } else {
620
+ console.error(` ${RED}✗${NC} catalog list output is missing catalog listings`);
621
+ fail++;
622
+ }
623
+ } catch (e) {
624
+ console.error(` ${RED}✗${NC} catalog list execution failed: ${e.message}`);
625
+ fail++;
626
+ }
627
+
628
+ // 2. Catalog categories check
629
+ try {
630
+ const output = execSync('node bin/multimodel-dev-os.js catalog categories', { cwd: projectRoot, encoding: 'utf8' });
631
+ if (output.includes('Marketplace Categories') && output.includes('git')) {
632
+ console.log(` ${GREEN}✓${NC} catalog categories executes successfully`);
633
+ pass++;
634
+ } else {
635
+ console.error(` ${RED}✗${NC} catalog categories output is missing categories`);
636
+ fail++;
637
+ }
638
+ } catch (e) {
639
+ console.error(` ${RED}✗${NC} catalog categories execution failed: ${e.message}`);
640
+ fail++;
641
+ }
642
+
643
+ // 3. Catalog search check
644
+ try {
645
+ const output = execSync('node bin/multimodel-dev-os.js catalog search release', { cwd: projectRoot, encoding: 'utf8' });
646
+ if (output.includes('Search Catalog Results') && output.includes('release-workflows')) {
647
+ console.log(` ${GREEN}✓${NC} catalog search release executes successfully`);
648
+ pass++;
649
+ } else {
650
+ console.error(` ${RED}✗${NC} catalog search release output is missing matches`);
651
+ fail++;
652
+ }
653
+ } catch (e) {
654
+ console.error(` ${RED}✗${NC} catalog search execution failed: ${e.message}`);
655
+ fail++;
656
+ }
657
+
658
+ // 4. Catalog show check
659
+ try {
660
+ const output = execSync('node bin/multimodel-dev-os.js catalog show release-workflows', { cwd: projectRoot, encoding: 'utf8' });
661
+ if (output.includes('Catalog Plugin: Release Preparation') && output.includes('release-workflows')) {
662
+ console.log(` ${GREEN}✓${NC} catalog show release-workflows executes successfully`);
663
+ pass++;
664
+ } else {
665
+ console.error(` ${RED}✗${NC} catalog show output is missing details`);
666
+ fail++;
667
+ }
668
+ } catch (e) {
669
+ console.error(` ${RED}✗${NC} catalog show execution failed: ${e.message}`);
670
+ fail++;
671
+ }
672
+
673
+ // 5. Catalog recommend check
674
+ try {
675
+ const output = execSync('node bin/multimodel-dev-os.js catalog recommend --target .', { cwd: projectRoot, encoding: 'utf8' });
676
+ if (output.includes('Marketplace Recommendations') && output.includes('git-workflows')) {
677
+ console.log(` ${GREEN}✓${NC} catalog recommend executes successfully`);
678
+ pass++;
679
+ } else {
680
+ console.error(` ${RED}✗${NC} catalog recommend output is missing recommendations`);
681
+ fail++;
682
+ }
683
+ } catch (e) {
684
+ console.error(` ${RED}✗${NC} catalog recommend execution failed: ${e.message}`);
685
+ fail++;
686
+ }
687
+
688
+ // 6. Catalog status check
689
+ try {
690
+ const output = execSync('node bin/multimodel-dev-os.js catalog status --target .', { cwd: projectRoot, encoding: 'utf8' });
691
+ if (output.includes('Auditing Catalog Plugins') && output.includes('git-workflows')) {
692
+ console.log(` ${GREEN}✓${NC} catalog status executes successfully`);
693
+ pass++;
694
+ } else {
695
+ console.error(` ${RED}✗${NC} catalog status output is missing audit results`);
696
+ fail++;
697
+ }
698
+ } catch (e) {
699
+ console.error(` ${RED}✗${NC} catalog status execution failed: ${e.message}`);
700
+ fail++;
701
+ }
702
+
703
+ // 7. Catalog install refusal check (no --approved)
704
+ try {
705
+ execSync('node bin/multimodel-dev-os.js catalog install release-workflows', { cwd: projectRoot, stdio: 'pipe' });
706
+ console.error(` ${RED}✗${NC} catalog install without --approved should have exited with code 1, but exited with 0`);
707
+ fail++;
708
+ } catch (e) {
709
+ if (e.status === 1) {
710
+ const stdOutOut = e.stdout ? e.stdout.toString() : '';
711
+ const stdErrOut = e.stderr ? e.stderr.toString() : '';
712
+ if (stdOutOut.includes('Installation refused') || stdErrOut.includes('Installation refused')) {
713
+ console.log(` ${GREEN}✓${NC} catalog install without --approved correctly refuses and exits with code 1`);
714
+ pass++;
715
+ } else {
716
+ console.error(` ${RED}✗${NC} catalog install without --approved exited with 1 but missing refusal message`);
717
+ fail++;
718
+ }
719
+ } else {
720
+ console.error(` ${RED}✗${NC} catalog install without --approved failed with unexpected code ${e.status}: ${e.message}`);
721
+ fail++;
722
+ }
723
+ }
724
+
725
+ // 8. Catalog file checks and schema validations
726
+ try {
727
+ const catalogYamlPath = join(projectRoot, '.ai', 'plugins', 'catalog.yaml');
728
+ if (existsSync(catalogYamlPath)) {
729
+ console.log(` ${GREEN}✓${NC} catalog.yaml file exists in registries`);
730
+ pass++;
731
+ } else {
732
+ console.error(` ${RED}✗${NC} catalog.yaml is missing`);
733
+ fail++;
734
+ }
735
+
736
+ // Parse and validate catalog plugins
737
+ const catalogData = parseYaml(readFileSync(catalogYamlPath, 'utf8'));
738
+ const plugins = (catalogData.catalog && catalogData.catalog.plugins) || [];
739
+ let catalogValid = true;
740
+
741
+ plugins.forEach(p => {
742
+ const manifestPath = join(projectRoot, '.ai', 'plugins', 'catalog', `${p.slug}.yaml`);
743
+ if (!existsSync(manifestPath)) {
744
+ console.error(` ${RED}✗${NC} Catalog plugin manifest missing for: ${p.slug}`);
745
+ catalogValid = false;
746
+ } else {
747
+ // Validate manifest against plugin validate logic
748
+ const out = execSync(`node bin/multimodel-dev-os.js plugin validate .ai/plugins/catalog/${p.slug}.yaml`, { cwd: projectRoot, encoding: 'utf8' });
749
+ if (!out.includes('fully valid and compliant')) {
750
+ console.error(` ${RED}✗${NC} Catalog plugin validate failed for: ${p.slug}`);
751
+ catalogValid = false;
752
+ }
753
+ }
754
+ });
755
+
756
+ if (catalogValid) {
757
+ console.log(` ${GREEN}✓${NC} all bundled catalog plugins exist and pass validation rules`);
758
+ pass++;
759
+ } else {
760
+ fail++;
761
+ }
762
+ } catch (e) {
763
+ console.error(` ${RED}✗${NC} catalog manifests integrity checks failed: ${e.message}`);
764
+ fail++;
765
+ }
766
+
767
+ // 9. YAML Parser regressions check
768
+ try {
769
+ const yamlTest = `
770
+ test_flow_array: ["git", "workflow", "vcs"]
771
+ test_quoted_string: "1.0.0"
772
+ test_comment_inside: "Work with # characters" # inline comment
773
+ test_quoted_bool: "true"
774
+ `;
775
+ const parsed = parseYaml(yamlTest);
776
+ if (parsed &&
777
+ Array.isArray(parsed.test_flow_array) && parsed.test_flow_array.length === 3 && parsed.test_flow_array[0] === 'git' &&
778
+ parsed.test_quoted_string === '1.0.0' &&
779
+ parsed.test_comment_inside === 'Work with # characters' &&
780
+ parsed.test_quoted_bool === 'true') {
781
+ console.log(` ${GREEN}✓${NC} YAML parser regression fixtures passed successfully`);
782
+ pass++;
783
+ } else {
784
+ console.error(` ${RED}✗${NC} YAML parser regression fixtures failed. Flow arrays, quoted types, or comment stripping is broken.`);
785
+ fail++;
786
+ }
787
+ } catch (e) {
788
+ console.error(` ${RED}✗${NC} YAML parser regression check crashed: ${e.message}`);
789
+ fail++;
790
+ }
791
+
792
+ // 10. Catalog search empty result state warning check
793
+ try {
794
+ const out = execSync('node bin/multimodel-dev-os.js catalog search no-match-term', { cwd: projectRoot, encoding: 'utf8' });
795
+ if (out.includes('Warning: No plugins found matching')) {
796
+ console.log(` ${GREEN}✓${NC} catalog search empty state prints correct warning`);
797
+ pass++;
798
+ } else {
799
+ console.error(` ${RED}✗${NC} catalog search empty state does not print warning`);
800
+ fail++;
801
+ }
802
+ } catch (e) {
803
+ console.error(` ${RED}✗${NC} catalog search empty state check failed: ${e.message}`);
804
+ fail++;
805
+ }
806
+
807
+
464
808
  // Verify docs mention memory build
465
809
  try {
466
810
  const mdContent = readFileSync(join(projectRoot, 'docs', 'hash-compressed-memory.md'), 'utf8');
@@ -544,6 +888,198 @@ try {
544
888
  fail++;
545
889
  }
546
890
 
891
+ // --- SHA256 Helper ---
892
+ function computeSHA256(content) {
893
+ return createHash('sha256').update(content, 'utf8').digest('hex');
894
+ }
895
+
896
+ // --- v3.0.0 Trusted Registry & Policy Engine Verification Checks ---
897
+ console.log('\nRegistry & Policy Engine Verification:');
898
+
899
+ // Check policy files
900
+ checkFile('.ai/policies/registry-policy.yaml');
901
+ checkFile('.ai/schema/registry-policy.schema.json');
902
+ checkFile('.ai/registries/sources.yaml');
903
+
904
+ // Verify policy JSON schema parses
905
+ try {
906
+ const schemaPath = join(projectRoot, '.ai', 'schema', 'registry-policy.schema.json');
907
+ const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
908
+ if (schema.title === 'MultiModel Dev OS Registry Policy Schema') {
909
+ console.log(` ${GREEN}✓${NC} registry-policy schema JSON is valid and has correct title`);
910
+ pass++;
911
+ } else {
912
+ console.error(` ${RED}✗${NC} registry-policy schema JSON title mismatch`);
913
+ fail++;
914
+ }
915
+ } catch (e) {
916
+ console.error(` ${RED}✗${NC} registry-policy schema JSON check failed: ${e.message}`);
917
+ fail++;
918
+ }
919
+
920
+ // Verify sources.yaml parses and contains bundled source
921
+ try {
922
+ const sourcesPath = join(projectRoot, '.ai', 'registries', 'sources.yaml');
923
+ const sourcesYaml = readFileSync(sourcesPath, 'utf8');
924
+ const parsed = parseYaml(sourcesYaml);
925
+ const bundled = (parsed.sources || []).find(s => s.name === 'bundled');
926
+ if (bundled && bundled.type === 'local') {
927
+ console.log(` ${GREEN}✓${NC} sources.yaml parsed and verified local bundled registry`);
928
+ pass++;
929
+ } else {
930
+ console.error(` ${RED}✗${NC} sources.yaml does not contain valid local bundled registry`);
931
+ fail++;
932
+ }
933
+ } catch (e) {
934
+ console.error(` ${RED}✗${NC} sources.yaml check failed: ${e.message}`);
935
+ fail++;
936
+ }
937
+
938
+ // Verify default policy blocks remote registries
939
+ try {
940
+ const policyPath = join(projectRoot, '.ai', 'policies', 'registry-policy.yaml');
941
+ const policyYaml = readFileSync(policyPath, 'utf8');
942
+ const parsed = parseYaml(policyYaml);
943
+ if (parsed.allow_remote_registries === false) {
944
+ console.log(` ${GREEN}✓${NC} default policy blocks remote registries (allow_remote_registries = false)`);
945
+ pass++;
946
+ } else {
947
+ console.error(` ${RED}✗${NC} default policy does not block remote registries`);
948
+ fail++;
949
+ }
950
+ } catch (e) {
951
+ console.error(` ${RED}✗${NC} default policy check failed: ${e.message}`);
952
+ fail++;
953
+ }
954
+
955
+ // Verify SHA256 helper is deterministic and works
956
+ try {
957
+ const fixture = 'MultiModel Dev OS v3.0.0';
958
+ const expectedHash = 'feba01a9e59c59a74a15769517aed5e4f5361fa3bd454f1b127357998bdebabe'; // sha256 of 'MultiModel Dev OS v3.0.0'
959
+ const actualHash = computeSHA256(fixture);
960
+ if (actualHash === expectedHash) {
961
+ console.log(` ${GREEN}✓${NC} SHA256 checksum helper verified successfully`);
962
+ pass++;
963
+ } else {
964
+ console.error(` ${RED}✗${NC} SHA256 checksum helper mismatch. Expected: ${expectedHash}, Got: ${actualHash}`);
965
+ fail++;
966
+ }
967
+ } catch (e) {
968
+ console.error(` ${RED}✗${NC} SHA256 helper check failed: ${e.message}`);
969
+ fail++;
970
+ }
971
+
972
+ // Verify registry CLI commands
973
+ try {
974
+ const helpOutput = execSync('node bin/multimodel-dev-os.js --help', { cwd: projectRoot, encoding: 'utf8' });
975
+ if (helpOutput.includes('registry <subcmd>') && helpOutput.includes('--all-sources')) {
976
+ console.log(` ${GREEN}✓${NC} CLI help output includes registry commands and flags`);
977
+ pass++;
978
+ } else {
979
+ console.error(` ${RED}✗${NC} CLI help output missing registry subcommands or flags`);
980
+ fail++;
981
+ }
982
+ } catch (e) {
983
+ console.error(` ${RED}✗${NC} CLI help check failed: ${e.message}`);
984
+ fail++;
985
+ }
986
+
987
+ try {
988
+ const statusOutput = execSync('node bin/multimodel-dev-os.js registry status', { cwd: projectRoot, encoding: 'utf8' });
989
+ if (statusOutput.includes('allow_remote_registries') && statusOutput.includes('bundled')) {
990
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry status runs cleanly`);
991
+ pass++;
992
+ } else {
993
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry status output invalid`);
994
+ fail++;
995
+ }
996
+ } catch (e) {
997
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry status failed: ${e.message}`);
998
+ fail++;
999
+ }
1000
+
1001
+ try {
1002
+ const listOutput = execSync('node bin/multimodel-dev-os.js registry list', { cwd: projectRoot, encoding: 'utf8' });
1003
+ if (listOutput.includes('bundled') && listOutput.includes('local')) {
1004
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry list runs cleanly`);
1005
+ pass++;
1006
+ } else {
1007
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry list output invalid`);
1008
+ fail++;
1009
+ }
1010
+ } catch (e) {
1011
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry list failed: ${e.message}`);
1012
+ fail++;
1013
+ }
1014
+
1015
+ try {
1016
+ // Syncing a non-existent or "official" source without approved should refuse or report not found
1017
+ try {
1018
+ execSync('node bin/multimodel-dev-os.js registry sync official', { cwd: projectRoot, stdio: 'pipe' });
1019
+ console.error(` ${RED}✗${NC} registry sync official should have failed without --approved or because registry not found`);
1020
+ fail++;
1021
+ } catch (err) {
1022
+ const errText = err.stderr ? err.stderr.toString() : '';
1023
+ const outText = err.stdout ? err.stdout.toString() : '';
1024
+ if (errText.includes('not found') || outText.includes('Registry Sync Refused')) {
1025
+ console.log(` ${GREEN}✓${NC} registry sync checks validation behavior correctly`);
1026
+ pass++;
1027
+ } else {
1028
+ console.error(` ${RED}✗${NC} registry sync verification output mismatch: ${errText || outText}`);
1029
+ fail++;
1030
+ }
1031
+ }
1032
+ } catch (e) {
1033
+ console.error(` ${RED}✗${NC} registry sync check failed: ${e.message}`);
1034
+ fail++;
1035
+ }
1036
+
1037
+ try {
1038
+ const verifyOutput = execSync('node bin/multimodel-dev-os.js registry verify bundled', { cwd: projectRoot, encoding: 'utf8' });
1039
+ if (verifyOutput.includes('verification passed')) {
1040
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry verify bundled passes cleanly`);
1041
+ pass++;
1042
+ } else {
1043
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry verify bundled failed: ${verifyOutput}`);
1044
+ fail++;
1045
+ }
1046
+ } catch (e) {
1047
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry verify bundled failed: ${e.message}`);
1048
+ fail++;
1049
+ }
1050
+
1051
+ try {
1052
+ const showOutput = execSync('node bin/multimodel-dev-os.js registry show bundled', { cwd: projectRoot, encoding: 'utf8' });
1053
+ if (showOutput.includes('bundled') && showOutput.includes('local')) {
1054
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry show bundled runs cleanly`);
1055
+ pass++;
1056
+ } else {
1057
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry show bundled failed: ${showOutput}`);
1058
+ fail++;
1059
+ }
1060
+ } catch (e) {
1061
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry show bundled failed: ${e.message}`);
1062
+ fail++;
1063
+ }
1064
+
1065
+ // Backward compatibility catalog checks
1066
+ try {
1067
+ const catList = execSync('node bin/multimodel-dev-os.js catalog list', { cwd: projectRoot, encoding: 'utf8' });
1068
+ const catSearch = execSync('node bin/multimodel-dev-os.js catalog search release', { cwd: projectRoot, encoding: 'utf8' });
1069
+ const catRecommend = execSync('node bin/multimodel-dev-os.js catalog recommend --target .', { cwd: projectRoot, encoding: 'utf8' });
1070
+
1071
+ if (catList.includes('Git Workflows') && catSearch.includes('Release Preparation') && catRecommend.includes('Recommendations')) {
1072
+ console.log(` ${GREEN}✓${NC} catalog commands remain backward-compatible without remote sources`);
1073
+ pass++;
1074
+ } else {
1075
+ console.error(` ${RED}✗${NC} catalog commands backward compatibility check failed`);
1076
+ fail++;
1077
+ }
1078
+ } catch (e) {
1079
+ console.error(` ${RED}✗${NC} catalog backward compatibility check failed: ${e.message}`);
1080
+ fail++;
1081
+ }
1082
+
547
1083
  // --- Package Safety & Hygiene Checks ---
548
1084
  console.log('\nPackage Safety & Hygiene Checks:');
549
1085
  if (existsSync(join(projectRoot, '.npmrc')) && process.env.MMDO_ALLOW_PUBLISH !== 'true') {