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.
- 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 +7 -6
- package/bin/multimodel-dev-os.js +1421 -61
- 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 +22 -9
- package/docs/faq.md +20 -1
- package/docs/plugin-authoring.md +7 -1
- package/docs/plugin-catalog.md +35 -0
- package/docs/plugin-hooks.md +15 -0
- package/docs/public/llms-full.txt +18 -1
- package/docs/public/llms.txt +17 -1
- package/docs/public/sitemap.xml +45 -0
- 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/tui-safety.md +1 -1
- package/docs/v2-roadmap.md +21 -7
- package/docs/workflow-marketplace.md +22 -0
- package/docs/workflow-orchestration.md +6 -0
- package/package.json +1 -1
- package/scripts/install.ps1 +0 -0
- package/scripts/install.sh +0 -0
- 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
|
-
|
|
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 });
|
|
@@ -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') {
|