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.
- package/.ai/plugins/catalog/.ai/checks/pre-commit-gate.md +14 -0
- package/.ai/plugins/catalog/.ai/skills/checkout-ops.md +12 -0
- package/.ai/plugins/catalog/.ai/skills/git-operations.md +21 -0
- package/.ai/plugins/catalog/.ai/skills/nextjs-builder.md +12 -0
- package/.ai/plugins/catalog/.ai/skills/release-ops.md +12 -0
- package/.ai/plugins/catalog/.ai/skills/seo-audit-ops.md +14 -0
- package/.ai/plugins/catalog/.ai/skills/wp-helper.md +13 -0
- package/.ai/plugins/catalog/README.md +34 -0
- package/.ai/plugins/catalog/ecommerce-workflows.yaml +14 -0
- package/.ai/plugins/catalog/git-workflows.yaml +22 -0
- package/.ai/plugins/catalog/nextjs-workflows.yaml +14 -0
- package/.ai/plugins/catalog/release-workflows.yaml +14 -0
- package/.ai/plugins/catalog/seo-workflows.yaml +19 -0
- package/.ai/plugins/catalog/wordpress-workflows.yaml +14 -0
- package/.ai/plugins/catalog.yaml +161 -0
- package/.ai/policies/registry-policy.yaml +51 -0
- package/.ai/registries/sources.yaml +15 -0
- package/.ai/registry-cache/README.md +35 -0
- package/.ai/schema/registry-manifest.schema.json +57 -0
- package/.ai/schema/registry-policy.schema.json +66 -0
- package/README.md +6 -5
- package/bin/multimodel-dev-os.js +1309 -30
- package/docs/.vitepress/config.js +16 -2
- package/docs/CLI.md +54 -1
- package/docs/architecture.md +9 -3
- package/docs/catalog-authoring.md +63 -0
- package/docs/catalog.md +72 -0
- package/docs/comparison.md +1 -0
- package/docs/dashboard.md +13 -2
- package/docs/faq.md +19 -0
- package/docs/plugin-authoring.md +6 -0
- package/docs/plugin-catalog.md +35 -0
- package/docs/plugin-hooks.md +6 -0
- package/docs/public/llms-full.txt +18 -1
- package/docs/public/llms.txt +17 -1
- package/docs/public/sitemap.xml +248 -203
- package/docs/quickstart.md +17 -0
- package/docs/registry-policy.md +93 -0
- package/docs/registry-security.md +67 -0
- package/docs/registry-sync.md +106 -0
- package/docs/remote-catalog-authoring.md +139 -0
- package/docs/repository-command-center.md +2 -0
- package/docs/trusted-registries.md +77 -0
- package/docs/v2-roadmap.md +13 -4
- package/docs/workflow-marketplace.md +22 -0
- package/docs/workflow-orchestration.md +6 -0
- package/package.json +1 -1
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/prepublish-guard.js +27 -5
- 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
|
-
|
|
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
|
-
|
|
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') {
|