multimodel-dev-os 2.8.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +6 -5
  22. package/bin/multimodel-dev-os.js +1309 -30
  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 +13 -2
  30. package/docs/faq.md +19 -0
  31. package/docs/plugin-authoring.md +6 -0
  32. package/docs/plugin-catalog.md +35 -0
  33. package/docs/plugin-hooks.md +6 -0
  34. package/docs/public/llms-full.txt +18 -1
  35. package/docs/public/llms.txt +17 -1
  36. package/docs/public/sitemap.xml +248 -203
  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/v2-roadmap.md +13 -4
  45. package/docs/workflow-marketplace.md +22 -0
  46. package/docs/workflow-orchestration.md +6 -0
  47. package/package.json +1 -1
  48. package/scripts/install.ps1 +1 -1
  49. package/scripts/install.sh +1 -1
  50. package/scripts/prepublish-guard.js +27 -5
  51. package/scripts/verify.js +523 -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 });
@@ -469,6 +527,71 @@ try {
469
527
  fail++;
470
528
  }
471
529
 
530
+ // Verify prepublish guard behavior
531
+ try {
532
+ // Test 1: Blocks without MMDO_ALLOW_PUBLISH
533
+ try {
534
+ execSync('node scripts/prepublish-guard.js', {
535
+ cwd: projectRoot,
536
+ env: { ...process.env, MMDO_ALLOW_PUBLISH: 'false' },
537
+ stdio: 'pipe'
538
+ });
539
+ console.error(` ${RED}✗${NC} prepublish-guard should have failed without MMDO_ALLOW_PUBLISH=true`);
540
+ fail++;
541
+ } catch (err) {
542
+ const output = err.stderr ? err.stderr.toString() : '';
543
+ if (output.includes('Publishing requires explicit release approval')) {
544
+ console.log(` ${GREEN}✓${NC} prepublish guard blocks without MMDO_ALLOW_PUBLISH`);
545
+ pass++;
546
+ } else {
547
+ console.error(` ${RED}✗${NC} prepublish guard failed with unexpected error: ${output}`);
548
+ fail++;
549
+ }
550
+ }
551
+
552
+ // Test 2: Allows version 3.0.0 with MMDO_ALLOW_PUBLISH=true
553
+ try {
554
+ const output = execSync('node scripts/prepublish-guard.js', {
555
+ cwd: projectRoot,
556
+ env: { ...process.env, MMDO_ALLOW_PUBLISH: 'true' },
557
+ encoding: 'utf8'
558
+ });
559
+ if (output.includes('Prepublish guard passed')) {
560
+ console.log(` ${GREEN}✓${NC} prepublish guard allows version 3.0.0 when MMDO_ALLOW_PUBLISH=true`);
561
+ pass++;
562
+ } else {
563
+ console.error(` ${RED}✗${NC} prepublish guard passed but stdout missing success indicator`);
564
+ fail++;
565
+ }
566
+ } catch (err) {
567
+ const errText = err.stderr ? err.stderr.toString() : '';
568
+ console.error(` ${RED}✗${NC} prepublish guard blocked version 3.0.0: ${errText || err.message}`);
569
+ fail++;
570
+ }
571
+
572
+ // Test 3: Guard output no longer has "Only major v2" wording
573
+ const guardCode = readFileSync(join(projectRoot, 'scripts', 'prepublish-guard.js'), 'utf8');
574
+ if (guardCode.includes('Only major v2')) {
575
+ console.error(` ${RED}✗${NC} prepublish-guard still contains "Only major v2" wording`);
576
+ fail++;
577
+ } else {
578
+ console.log(` ${GREEN}✓${NC} prepublish guard no longer has "Only major v2" wording`);
579
+ pass++;
580
+ }
581
+
582
+ // Test 4: Package.json version is exactly 3.0.0
583
+ if (expectedVersion === '3.0.0') {
584
+ console.log(` ${GREEN}✓${NC} package.json version is exactly 3.0.0`);
585
+ pass++;
586
+ } else {
587
+ console.error(` ${RED}✗${NC} package.json version is not 3.0.0 (found ${expectedVersion})`);
588
+ fail++;
589
+ }
590
+ } catch (e) {
591
+ console.error(` ${RED}✗${NC} prepublish guard checks failed: ${e.message}`);
592
+ fail++;
593
+ }
594
+
472
595
  // --- v2.8.0 / v2.8.1 Dashboard & Plugin Tests ---
473
596
  console.log('\nRunning TUI Dashboard & Plugin Pre-Flight Tests...');
474
597
 
@@ -549,6 +672,204 @@ try {
549
672
  fail++;
550
673
  }
551
674
 
675
+ // --- v2.9.0 Catalog & Marketplace Tests ---
676
+ console.log('\nRunning Catalog & Marketplace Pre-Flight Tests...');
677
+
678
+ // 1. Catalog list check
679
+ try {
680
+ const output = execSync('node bin/multimodel-dev-os.js catalog list', { cwd: projectRoot, encoding: 'utf8' });
681
+ if (output.includes('Workflow Marketplace & Plugin Catalog') && output.includes('git-workflows')) {
682
+ console.log(` ${GREEN}✓${NC} catalog list executes successfully and displays catalog listings`);
683
+ pass++;
684
+ } else {
685
+ console.error(` ${RED}✗${NC} catalog list output is missing catalog listings`);
686
+ fail++;
687
+ }
688
+ } catch (e) {
689
+ console.error(` ${RED}✗${NC} catalog list execution failed: ${e.message}`);
690
+ fail++;
691
+ }
692
+
693
+ // 2. Catalog categories check
694
+ try {
695
+ const output = execSync('node bin/multimodel-dev-os.js catalog categories', { cwd: projectRoot, encoding: 'utf8' });
696
+ if (output.includes('Marketplace Categories') && output.includes('git')) {
697
+ console.log(` ${GREEN}✓${NC} catalog categories executes successfully`);
698
+ pass++;
699
+ } else {
700
+ console.error(` ${RED}✗${NC} catalog categories output is missing categories`);
701
+ fail++;
702
+ }
703
+ } catch (e) {
704
+ console.error(` ${RED}✗${NC} catalog categories execution failed: ${e.message}`);
705
+ fail++;
706
+ }
707
+
708
+ // 3. Catalog search check
709
+ try {
710
+ const output = execSync('node bin/multimodel-dev-os.js catalog search release', { cwd: projectRoot, encoding: 'utf8' });
711
+ if (output.includes('Search Catalog Results') && output.includes('release-workflows')) {
712
+ console.log(` ${GREEN}✓${NC} catalog search release executes successfully`);
713
+ pass++;
714
+ } else {
715
+ console.error(` ${RED}✗${NC} catalog search release output is missing matches`);
716
+ fail++;
717
+ }
718
+ } catch (e) {
719
+ console.error(` ${RED}✗${NC} catalog search execution failed: ${e.message}`);
720
+ fail++;
721
+ }
722
+
723
+ // 4. Catalog show check
724
+ try {
725
+ const output = execSync('node bin/multimodel-dev-os.js catalog show release-workflows', { cwd: projectRoot, encoding: 'utf8' });
726
+ if (output.includes('Catalog Plugin: Release Preparation') && output.includes('release-workflows')) {
727
+ console.log(` ${GREEN}✓${NC} catalog show release-workflows executes successfully`);
728
+ pass++;
729
+ } else {
730
+ console.error(` ${RED}✗${NC} catalog show output is missing details`);
731
+ fail++;
732
+ }
733
+ } catch (e) {
734
+ console.error(` ${RED}✗${NC} catalog show execution failed: ${e.message}`);
735
+ fail++;
736
+ }
737
+
738
+ // 5. Catalog recommend check
739
+ try {
740
+ const output = execSync('node bin/multimodel-dev-os.js catalog recommend --target .', { cwd: projectRoot, encoding: 'utf8' });
741
+ if (output.includes('Marketplace Recommendations') && output.includes('git-workflows')) {
742
+ console.log(` ${GREEN}✓${NC} catalog recommend executes successfully`);
743
+ pass++;
744
+ } else {
745
+ console.error(` ${RED}✗${NC} catalog recommend output is missing recommendations`);
746
+ fail++;
747
+ }
748
+ } catch (e) {
749
+ console.error(` ${RED}✗${NC} catalog recommend execution failed: ${e.message}`);
750
+ fail++;
751
+ }
752
+
753
+ // 6. Catalog status check
754
+ try {
755
+ const output = execSync('node bin/multimodel-dev-os.js catalog status --target .', { cwd: projectRoot, encoding: 'utf8' });
756
+ if (output.includes('Auditing Catalog Plugins') && output.includes('git-workflows')) {
757
+ console.log(` ${GREEN}✓${NC} catalog status executes successfully`);
758
+ pass++;
759
+ } else {
760
+ console.error(` ${RED}✗${NC} catalog status output is missing audit results`);
761
+ fail++;
762
+ }
763
+ } catch (e) {
764
+ console.error(` ${RED}✗${NC} catalog status execution failed: ${e.message}`);
765
+ fail++;
766
+ }
767
+
768
+ // 7. Catalog install refusal check (no --approved)
769
+ try {
770
+ execSync('node bin/multimodel-dev-os.js catalog install release-workflows', { cwd: projectRoot, stdio: 'pipe' });
771
+ console.error(` ${RED}✗${NC} catalog install without --approved should have exited with code 1, but exited with 0`);
772
+ fail++;
773
+ } catch (e) {
774
+ if (e.status === 1) {
775
+ const stdOutOut = e.stdout ? e.stdout.toString() : '';
776
+ const stdErrOut = e.stderr ? e.stderr.toString() : '';
777
+ if (stdOutOut.includes('Installation refused') || stdErrOut.includes('Installation refused')) {
778
+ console.log(` ${GREEN}✓${NC} catalog install without --approved correctly refuses and exits with code 1`);
779
+ pass++;
780
+ } else {
781
+ console.error(` ${RED}✗${NC} catalog install without --approved exited with 1 but missing refusal message`);
782
+ fail++;
783
+ }
784
+ } else {
785
+ console.error(` ${RED}✗${NC} catalog install without --approved failed with unexpected code ${e.status}: ${e.message}`);
786
+ fail++;
787
+ }
788
+ }
789
+
790
+ // 8. Catalog file checks and schema validations
791
+ try {
792
+ const catalogYamlPath = join(projectRoot, '.ai', 'plugins', 'catalog.yaml');
793
+ if (existsSync(catalogYamlPath)) {
794
+ console.log(` ${GREEN}✓${NC} catalog.yaml file exists in registries`);
795
+ pass++;
796
+ } else {
797
+ console.error(` ${RED}✗${NC} catalog.yaml is missing`);
798
+ fail++;
799
+ }
800
+
801
+ // Parse and validate catalog plugins
802
+ const catalogData = parseYaml(readFileSync(catalogYamlPath, 'utf8'));
803
+ const plugins = (catalogData.catalog && catalogData.catalog.plugins) || [];
804
+ let catalogValid = true;
805
+
806
+ plugins.forEach(p => {
807
+ const manifestPath = join(projectRoot, '.ai', 'plugins', 'catalog', `${p.slug}.yaml`);
808
+ if (!existsSync(manifestPath)) {
809
+ console.error(` ${RED}✗${NC} Catalog plugin manifest missing for: ${p.slug}`);
810
+ catalogValid = false;
811
+ } else {
812
+ // Validate manifest against plugin validate logic
813
+ const out = execSync(`node bin/multimodel-dev-os.js plugin validate .ai/plugins/catalog/${p.slug}.yaml`, { cwd: projectRoot, encoding: 'utf8' });
814
+ if (!out.includes('fully valid and compliant')) {
815
+ console.error(` ${RED}✗${NC} Catalog plugin validate failed for: ${p.slug}`);
816
+ catalogValid = false;
817
+ }
818
+ }
819
+ });
820
+
821
+ if (catalogValid) {
822
+ console.log(` ${GREEN}✓${NC} all bundled catalog plugins exist and pass validation rules`);
823
+ pass++;
824
+ } else {
825
+ fail++;
826
+ }
827
+ } catch (e) {
828
+ console.error(` ${RED}✗${NC} catalog manifests integrity checks failed: ${e.message}`);
829
+ fail++;
830
+ }
831
+
832
+ // 9. YAML Parser regressions check
833
+ try {
834
+ const yamlTest = `
835
+ test_flow_array: ["git", "workflow", "vcs"]
836
+ test_quoted_string: "1.0.0"
837
+ test_comment_inside: "Work with # characters" # inline comment
838
+ test_quoted_bool: "true"
839
+ `;
840
+ const parsed = parseYaml(yamlTest);
841
+ if (parsed &&
842
+ Array.isArray(parsed.test_flow_array) && parsed.test_flow_array.length === 3 && parsed.test_flow_array[0] === 'git' &&
843
+ parsed.test_quoted_string === '1.0.0' &&
844
+ parsed.test_comment_inside === 'Work with # characters' &&
845
+ parsed.test_quoted_bool === 'true') {
846
+ console.log(` ${GREEN}✓${NC} YAML parser regression fixtures passed successfully`);
847
+ pass++;
848
+ } else {
849
+ console.error(` ${RED}✗${NC} YAML parser regression fixtures failed. Flow arrays, quoted types, or comment stripping is broken.`);
850
+ fail++;
851
+ }
852
+ } catch (e) {
853
+ console.error(` ${RED}✗${NC} YAML parser regression check crashed: ${e.message}`);
854
+ fail++;
855
+ }
856
+
857
+ // 10. Catalog search empty result state warning check
858
+ try {
859
+ const out = execSync('node bin/multimodel-dev-os.js catalog search no-match-term', { cwd: projectRoot, encoding: 'utf8' });
860
+ if (out.includes('Warning: No plugins found matching')) {
861
+ console.log(` ${GREEN}✓${NC} catalog search empty state prints correct warning`);
862
+ pass++;
863
+ } else {
864
+ console.error(` ${RED}✗${NC} catalog search empty state does not print warning`);
865
+ fail++;
866
+ }
867
+ } catch (e) {
868
+ console.error(` ${RED}✗${NC} catalog search empty state check failed: ${e.message}`);
869
+ fail++;
870
+ }
871
+
872
+
552
873
  // Verify docs mention memory build
553
874
  try {
554
875
  const mdContent = readFileSync(join(projectRoot, 'docs', 'hash-compressed-memory.md'), 'utf8');
@@ -632,6 +953,198 @@ try {
632
953
  fail++;
633
954
  }
634
955
 
956
+ // --- SHA256 Helper ---
957
+ function computeSHA256(content) {
958
+ return createHash('sha256').update(content, 'utf8').digest('hex');
959
+ }
960
+
961
+ // --- v3.0.0 Trusted Registry & Policy Engine Verification Checks ---
962
+ console.log('\nRegistry & Policy Engine Verification:');
963
+
964
+ // Check policy files
965
+ checkFile('.ai/policies/registry-policy.yaml');
966
+ checkFile('.ai/schema/registry-policy.schema.json');
967
+ checkFile('.ai/registries/sources.yaml');
968
+
969
+ // Verify policy JSON schema parses
970
+ try {
971
+ const schemaPath = join(projectRoot, '.ai', 'schema', 'registry-policy.schema.json');
972
+ const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
973
+ if (schema.title === 'MultiModel Dev OS Registry Policy Schema') {
974
+ console.log(` ${GREEN}✓${NC} registry-policy schema JSON is valid and has correct title`);
975
+ pass++;
976
+ } else {
977
+ console.error(` ${RED}✗${NC} registry-policy schema JSON title mismatch`);
978
+ fail++;
979
+ }
980
+ } catch (e) {
981
+ console.error(` ${RED}✗${NC} registry-policy schema JSON check failed: ${e.message}`);
982
+ fail++;
983
+ }
984
+
985
+ // Verify sources.yaml parses and contains bundled source
986
+ try {
987
+ const sourcesPath = join(projectRoot, '.ai', 'registries', 'sources.yaml');
988
+ const sourcesYaml = readFileSync(sourcesPath, 'utf8');
989
+ const parsed = parseYaml(sourcesYaml);
990
+ const bundled = (parsed.sources || []).find(s => s.name === 'bundled');
991
+ if (bundled && bundled.type === 'local') {
992
+ console.log(` ${GREEN}✓${NC} sources.yaml parsed and verified local bundled registry`);
993
+ pass++;
994
+ } else {
995
+ console.error(` ${RED}✗${NC} sources.yaml does not contain valid local bundled registry`);
996
+ fail++;
997
+ }
998
+ } catch (e) {
999
+ console.error(` ${RED}✗${NC} sources.yaml check failed: ${e.message}`);
1000
+ fail++;
1001
+ }
1002
+
1003
+ // Verify default policy blocks remote registries
1004
+ try {
1005
+ const policyPath = join(projectRoot, '.ai', 'policies', 'registry-policy.yaml');
1006
+ const policyYaml = readFileSync(policyPath, 'utf8');
1007
+ const parsed = parseYaml(policyYaml);
1008
+ if (parsed.allow_remote_registries === false) {
1009
+ console.log(` ${GREEN}✓${NC} default policy blocks remote registries (allow_remote_registries = false)`);
1010
+ pass++;
1011
+ } else {
1012
+ console.error(` ${RED}✗${NC} default policy does not block remote registries`);
1013
+ fail++;
1014
+ }
1015
+ } catch (e) {
1016
+ console.error(` ${RED}✗${NC} default policy check failed: ${e.message}`);
1017
+ fail++;
1018
+ }
1019
+
1020
+ // Verify SHA256 helper is deterministic and works
1021
+ try {
1022
+ const fixture = 'MultiModel Dev OS v3.0.0';
1023
+ const expectedHash = 'feba01a9e59c59a74a15769517aed5e4f5361fa3bd454f1b127357998bdebabe'; // sha256 of 'MultiModel Dev OS v3.0.0'
1024
+ const actualHash = computeSHA256(fixture);
1025
+ if (actualHash === expectedHash) {
1026
+ console.log(` ${GREEN}✓${NC} SHA256 checksum helper verified successfully`);
1027
+ pass++;
1028
+ } else {
1029
+ console.error(` ${RED}✗${NC} SHA256 checksum helper mismatch. Expected: ${expectedHash}, Got: ${actualHash}`);
1030
+ fail++;
1031
+ }
1032
+ } catch (e) {
1033
+ console.error(` ${RED}✗${NC} SHA256 helper check failed: ${e.message}`);
1034
+ fail++;
1035
+ }
1036
+
1037
+ // Verify registry CLI commands
1038
+ try {
1039
+ const helpOutput = execSync('node bin/multimodel-dev-os.js --help', { cwd: projectRoot, encoding: 'utf8' });
1040
+ if (helpOutput.includes('registry <subcmd>') && helpOutput.includes('--all-sources')) {
1041
+ console.log(` ${GREEN}✓${NC} CLI help output includes registry commands and flags`);
1042
+ pass++;
1043
+ } else {
1044
+ console.error(` ${RED}✗${NC} CLI help output missing registry subcommands or flags`);
1045
+ fail++;
1046
+ }
1047
+ } catch (e) {
1048
+ console.error(` ${RED}✗${NC} CLI help check failed: ${e.message}`);
1049
+ fail++;
1050
+ }
1051
+
1052
+ try {
1053
+ const statusOutput = execSync('node bin/multimodel-dev-os.js registry status', { cwd: projectRoot, encoding: 'utf8' });
1054
+ if (statusOutput.includes('allow_remote_registries') && statusOutput.includes('bundled')) {
1055
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry status runs cleanly`);
1056
+ pass++;
1057
+ } else {
1058
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry status output invalid`);
1059
+ fail++;
1060
+ }
1061
+ } catch (e) {
1062
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry status failed: ${e.message}`);
1063
+ fail++;
1064
+ }
1065
+
1066
+ try {
1067
+ const listOutput = execSync('node bin/multimodel-dev-os.js registry list', { cwd: projectRoot, encoding: 'utf8' });
1068
+ if (listOutput.includes('bundled') && listOutput.includes('local')) {
1069
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry list runs cleanly`);
1070
+ pass++;
1071
+ } else {
1072
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry list output invalid`);
1073
+ fail++;
1074
+ }
1075
+ } catch (e) {
1076
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry list failed: ${e.message}`);
1077
+ fail++;
1078
+ }
1079
+
1080
+ try {
1081
+ // Syncing a non-existent or "official" source without approved should refuse or report not found
1082
+ try {
1083
+ execSync('node bin/multimodel-dev-os.js registry sync official', { cwd: projectRoot, stdio: 'pipe' });
1084
+ console.error(` ${RED}✗${NC} registry sync official should have failed without --approved or because registry not found`);
1085
+ fail++;
1086
+ } catch (err) {
1087
+ const errText = err.stderr ? err.stderr.toString() : '';
1088
+ const outText = err.stdout ? err.stdout.toString() : '';
1089
+ if (errText.includes('not found') || outText.includes('Registry Sync Refused')) {
1090
+ console.log(` ${GREEN}✓${NC} registry sync checks validation behavior correctly`);
1091
+ pass++;
1092
+ } else {
1093
+ console.error(` ${RED}✗${NC} registry sync verification output mismatch: ${errText || outText}`);
1094
+ fail++;
1095
+ }
1096
+ }
1097
+ } catch (e) {
1098
+ console.error(` ${RED}✗${NC} registry sync check failed: ${e.message}`);
1099
+ fail++;
1100
+ }
1101
+
1102
+ try {
1103
+ const verifyOutput = execSync('node bin/multimodel-dev-os.js registry verify bundled', { cwd: projectRoot, encoding: 'utf8' });
1104
+ if (verifyOutput.includes('verification passed')) {
1105
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry verify bundled passes cleanly`);
1106
+ pass++;
1107
+ } else {
1108
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry verify bundled failed: ${verifyOutput}`);
1109
+ fail++;
1110
+ }
1111
+ } catch (e) {
1112
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry verify bundled failed: ${e.message}`);
1113
+ fail++;
1114
+ }
1115
+
1116
+ try {
1117
+ const showOutput = execSync('node bin/multimodel-dev-os.js registry show bundled', { cwd: projectRoot, encoding: 'utf8' });
1118
+ if (showOutput.includes('bundled') && showOutput.includes('local')) {
1119
+ console.log(` ${GREEN}✓${NC} node bin/multimodel-dev-os.js registry show bundled runs cleanly`);
1120
+ pass++;
1121
+ } else {
1122
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry show bundled failed: ${showOutput}`);
1123
+ fail++;
1124
+ }
1125
+ } catch (e) {
1126
+ console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js registry show bundled failed: ${e.message}`);
1127
+ fail++;
1128
+ }
1129
+
1130
+ // Backward compatibility catalog checks
1131
+ try {
1132
+ const catList = execSync('node bin/multimodel-dev-os.js catalog list', { cwd: projectRoot, encoding: 'utf8' });
1133
+ const catSearch = execSync('node bin/multimodel-dev-os.js catalog search release', { cwd: projectRoot, encoding: 'utf8' });
1134
+ const catRecommend = execSync('node bin/multimodel-dev-os.js catalog recommend --target .', { cwd: projectRoot, encoding: 'utf8' });
1135
+
1136
+ if (catList.includes('Git Workflows') && catSearch.includes('Release Preparation') && catRecommend.includes('Recommendations')) {
1137
+ console.log(` ${GREEN}✓${NC} catalog commands remain backward-compatible without remote sources`);
1138
+ pass++;
1139
+ } else {
1140
+ console.error(` ${RED}✗${NC} catalog commands backward compatibility check failed`);
1141
+ fail++;
1142
+ }
1143
+ } catch (e) {
1144
+ console.error(` ${RED}✗${NC} catalog backward compatibility check failed: ${e.message}`);
1145
+ fail++;
1146
+ }
1147
+
635
1148
  // --- Package Safety & Hygiene Checks ---
636
1149
  console.log('\nPackage Safety & Hygiene Checks:');
637
1150
  if (existsSync(join(projectRoot, '.npmrc')) && process.env.MMDO_ALLOW_PUBLISH !== 'true') {