multimodel-dev-os 3.1.0 → 3.5.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 (102) hide show
  1. package/.ai/policies/registry-policy.yaml +29 -1
  2. package/.ai/registries/trusted-keys.yaml +12 -0
  3. package/.ai/schema/registry-manifest.schema.json +31 -2
  4. package/.ai/schema/registry-policy.schema.json +37 -1
  5. package/.ai/schema/trusted-keys.schema.json +69 -0
  6. package/AGENTS.md +22 -26
  7. package/MEMORY.md +34 -11
  8. package/README.md +2 -1
  9. package/RUNBOOK.md +28 -36
  10. package/TASKS.md +15 -5
  11. package/bin/multimodel-dev-os.js +1366 -548
  12. package/docs/.vitepress/config.js +3 -1
  13. package/docs/architecture.md +3 -1
  14. package/docs/index.md +5 -5
  15. package/docs/npm-publishing.md +5 -5
  16. package/docs/package-safety.md +17 -0
  17. package/docs/public/llms-full.txt +5 -1
  18. package/docs/public/llms.txt +6 -1
  19. package/docs/public/sitemap.xml +15 -0
  20. package/docs/registry-policy.md +29 -1
  21. package/docs/registry-security.md +73 -6
  22. package/docs/registry-signing.md +70 -0
  23. package/docs/registry-sync.md +5 -2
  24. package/docs/registry-trust-store.md +66 -0
  25. package/docs/release-policy.md +6 -5
  26. package/docs/security-threat-model.md +96 -0
  27. package/docs/testing.md +25 -2
  28. package/docs/trusted-registries.md +1 -1
  29. package/docs/v3-roadmap.md +17 -6
  30. package/docs/v3.5.0-readiness.md +46 -0
  31. package/package.json +5 -2
  32. package/scripts/build-cli.js +45 -3
  33. package/scripts/check-build-fresh.js +52 -0
  34. package/scripts/install.ps1 +1 -1
  35. package/scripts/install.sh +1 -1
  36. package/scripts/verify.js +327 -14
  37. package/scripts/verify.sh +10 -0
  38. package/src/catalog/loader.js +117 -0
  39. package/src/cli/args.js +118 -0
  40. package/src/cli/help.js +60 -0
  41. package/src/cli/main.js +6263 -0
  42. package/src/core/globals.js +52 -0
  43. package/src/core/hashes.js +15 -0
  44. package/src/core/policy.js +44 -0
  45. package/src/core/security.js +61 -0
  46. package/src/core/yaml.js +136 -0
  47. package/src/plugin/manifest.js +95 -0
  48. package/src/registry/provenance.js +114 -0
  49. package/src/registry/signing.js +392 -0
  50. package/src/registry/sources.js +40 -0
  51. package/src/registry/trust-store.js +41 -0
  52. package/src/registry/validation.js +45 -0
  53. package/src/registry/verdict.js +51 -0
  54. package/tests/README.md +37 -0
  55. package/tests/fixtures/README.md +22 -0
  56. package/tests/fixtures/custom-template-example/README.md +10 -0
  57. package/tests/fixtures/proposals/approved-append-line.md +28 -0
  58. package/tests/fixtures/proposals/approved-create-file.md +29 -0
  59. package/tests/fixtures/proposals/approved-replace-text.md +30 -0
  60. package/tests/fixtures/proposals/existing-create-file-no-overwrite.md +29 -0
  61. package/tests/fixtures/proposals/no-operations.md +18 -0
  62. package/tests/fixtures/proposals/path-traversal.md +29 -0
  63. package/tests/fixtures/proposals/pending-proposal.md +29 -0
  64. package/tests/fixtures/proposals/protected-path.md +29 -0
  65. package/tests/fixtures/proposals/replace-multiple-without-allow.md +30 -0
  66. package/tests/fixtures/registry-overrides/README.md +20 -0
  67. package/tests/fixtures/signed-registries/README.md +4 -0
  68. package/tests/fixtures/signed-registries/revoked-key/catalog.yaml +8 -0
  69. package/tests/fixtures/signed-registries/revoked-key/expected-verdict.json +7 -0
  70. package/tests/fixtures/signed-registries/revoked-key/registry-manifest.yaml +14 -0
  71. package/tests/fixtures/signed-registries/tampered-manifest/catalog.yaml +8 -0
  72. package/tests/fixtures/signed-registries/tampered-manifest/expected-verdict.json +7 -0
  73. package/tests/fixtures/signed-registries/tampered-manifest/registry-manifest.yaml +14 -0
  74. package/tests/fixtures/signed-registries/trusted-keys.yaml +23 -0
  75. package/tests/fixtures/signed-registries/unsigned-remote-required/catalog.yaml +8 -0
  76. package/tests/fixtures/signed-registries/unsigned-remote-required/expected-verdict.json +7 -0
  77. package/tests/fixtures/signed-registries/unsigned-remote-required/registry-manifest.yaml +9 -0
  78. package/tests/fixtures/signed-registries/unsupported-algorithm/catalog.yaml +8 -0
  79. package/tests/fixtures/signed-registries/unsupported-algorithm/expected-verdict.json +7 -0
  80. package/tests/fixtures/signed-registries/unsupported-algorithm/registry-manifest.yaml +14 -0
  81. package/tests/fixtures/signed-registries/valid-signed-registry/catalog.yaml +8 -0
  82. package/tests/fixtures/signed-registries/valid-signed-registry/expected-verdict.json +7 -0
  83. package/tests/fixtures/signed-registries/valid-signed-registry/registry-manifest.yaml +14 -0
  84. package/tests/fixtures/signed-registries/wrong-key/catalog.yaml +8 -0
  85. package/tests/fixtures/signed-registries/wrong-key/expected-verdict.json +7 -0
  86. package/tests/fixtures/signed-registries/wrong-key/registry-manifest.yaml +14 -0
  87. package/tests/smoke/README.md +37 -0
  88. package/tests/smoke/cli-smoke.md +49 -0
  89. package/tests/unit/build-output.test.js +40 -0
  90. package/tests/unit/catalog-loader.test.js +44 -0
  91. package/tests/unit/path-safety.test.js +62 -0
  92. package/tests/unit/plugin-manifest.test.js +94 -0
  93. package/tests/unit/prepublish-guard.test.js +35 -0
  94. package/tests/unit/registry-e2e-signature-fixtures.test.js +288 -0
  95. package/tests/unit/registry-policy.test.js +52 -0
  96. package/tests/unit/registry-provenance.test.js +185 -0
  97. package/tests/unit/registry-public-signing.test.js +109 -0
  98. package/tests/unit/registry-signature-policy.test.js +100 -0
  99. package/tests/unit/registry-signing.test.js +193 -0
  100. package/tests/unit/registry-trust-store.test.js +133 -0
  101. package/tests/unit/registry-url-validation.test.js +64 -0
  102. package/tests/unit/yaml.test.js +92 -0
@@ -3,8 +3,8 @@
3
3
 
4
4
 
5
5
  // src/cli/main.js
6
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync2, readdirSync, statSync } from "fs";
7
- import { join as join5, dirname as dirname2, resolve as resolve3, relative, isAbsolute, basename } from "path";
6
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4, readdirSync, statSync } from "fs";
7
+ import { join as join8, dirname as dirname4, resolve as resolve3, relative, isAbsolute as isAbsolute2, basename } from "path";
8
8
  import { createHash as createHash2 } from "crypto";
9
9
  import readline from "readline";
10
10
  import { execSync, execFileSync } from "child_process";
@@ -333,7 +333,7 @@ function showHelp() {
333
333
  console.log(" adapter <subcmd> Manage and sync rule/settings files for IDE adapters (subcmd: status, diff, sync)");
334
334
  console.log(" plugin <subcmd> Manage declarative plugins (subcmd: list, show, validate, install, status)");
335
335
  console.log(" catalog <subcmd> Manage Workflow Marketplace & Plugin Catalog (subcmd: list, search, show, categories, recommend, install, status)");
336
- console.log(" registry <subcmd> Manage trusted remote catalog registries (subcmd: list, add, remove, sync, status, verify, show, cache)");
336
+ console.log(" registry <subcmd> Manage trusted remote catalog registries (subcmd: list, add, remove, sync, status, verify, show, cache, keygen, lock, trust)");
337
337
  console.log(" verify Validate structural integrity of an existing project");
338
338
  console.log(" templates List all built-in template profiles with details");
339
339
  console.log(" list-templates Alias for templates command");
@@ -399,13 +399,21 @@ function loadRegistryPolicy(targetDir) {
399
399
  require_approval_for_remote_sync: true,
400
400
  require_checksum: true,
401
401
  require_signature: false,
402
+ require_lockfile_on_verify: false,
402
403
  allow_untrusted_install: false,
403
404
  allowed_write_roots: [".ai/", "adapters/"],
404
405
  blocked_paths: [".env", ".npmrc", ".git/", "node_modules/", "package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"],
405
406
  max_plugin_files: 20,
406
407
  max_plugin_size_kb: 100,
407
408
  max_registry_cache_size_kb: 512,
408
- allowed_file_extensions: [".md", ".yaml", ".yml", ".json"]
409
+ allowed_file_extensions: [".md", ".yaml", ".yml", ".json"],
410
+ allow_unsigned_local: true,
411
+ allow_unsigned_bundled: true,
412
+ allow_unsigned_remote: false,
413
+ trusted_keys_file: ".ai/registries/trusted-keys.yaml",
414
+ allowed_signature_algorithms: ["ed25519", "hmac-sha256"],
415
+ require_trusted_publisher: false,
416
+ provenance_required: true
409
417
  };
410
418
  const paths = [];
411
419
  if (targetDir) {
@@ -533,9 +541,322 @@ function saveRegistrySources(sources) {
533
541
  writeFileSync(path, yaml, "utf8");
534
542
  }
535
543
 
544
+ // src/registry/provenance.js
545
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync } from "fs";
546
+ import { join as join4, dirname as dirname2 } from "path";
547
+ var LOCKFILE_VERSION = "1";
548
+ var LOCKFILE_FILENAME = "registry-lock.json";
549
+ function getLockfilePath(targetDir) {
550
+ return join4(targetDir, ".ai", LOCKFILE_FILENAME);
551
+ }
552
+ function loadRegistryLockfile(targetDir) {
553
+ const lockfilePath = getLockfilePath(targetDir);
554
+ const empty = { lockfile_version: LOCKFILE_VERSION, generated_at: "", entries: {} };
555
+ if (!existsSync4(lockfilePath)) {
556
+ return empty;
557
+ }
558
+ try {
559
+ const raw = readFileSync5(lockfilePath, "utf8");
560
+ const parsed = JSON.parse(raw);
561
+ if (!parsed || typeof parsed !== "object" || !parsed.entries) {
562
+ return empty;
563
+ }
564
+ parsed.lockfile_version = parsed.lockfile_version || LOCKFILE_VERSION;
565
+ return parsed;
566
+ } catch (_e) {
567
+ return empty;
568
+ }
569
+ }
570
+ function saveRegistryLockfile(targetDir, lockfile) {
571
+ const lockfilePath = getLockfilePath(targetDir);
572
+ const lockfileDir = dirname2(lockfilePath);
573
+ if (!existsSync4(lockfileDir)) {
574
+ mkdirSync(lockfileDir, { recursive: true });
575
+ }
576
+ lockfile.generated_at = (/* @__PURE__ */ new Date()).toISOString();
577
+ lockfile.lockfile_version = LOCKFILE_VERSION;
578
+ writeFileSync2(lockfilePath, JSON.stringify(lockfile, null, 2) + "\n", "utf8");
579
+ }
580
+ function updateLockfileEntry(lockfile, name, entry) {
581
+ if (!lockfile.entries || typeof lockfile.entries !== "object") {
582
+ lockfile.entries = {};
583
+ }
584
+ lockfile.entries[name] = {
585
+ url: entry.url,
586
+ synced_at: entry.synced_at || (/* @__PURE__ */ new Date()).toISOString(),
587
+ catalog_sha256: entry.catalog_sha256,
588
+ manifest_sha256: entry.manifest_sha256 ?? null,
589
+ signature: entry.signature ?? null,
590
+ signature_alg: entry.signature_alg || "hmac-sha256",
591
+ public_signature_status: entry.public_signature_status ?? null,
592
+ public_signature_algorithm: entry.public_signature_algorithm ?? null,
593
+ public_signature_key_id: entry.public_signature_key_id ?? null,
594
+ trusted_publisher_status: entry.trusted_publisher_status ?? null,
595
+ trust_store_path: entry.trust_store_path ?? null,
596
+ trust_verdict: entry.trust_verdict ?? null,
597
+ lockfile_verdict: entry.lockfile_verdict ?? null,
598
+ verification_errors: entry.verification_errors ?? [],
599
+ verification_warnings: entry.verification_warnings ?? []
600
+ };
601
+ }
602
+
603
+ // src/registry/signing.js
604
+ import { generateKeyPairSync, sign, verify, createHmac, timingSafeEqual, randomBytes } from "crypto";
605
+ import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, chmodSync } from "fs";
606
+ import { join as join5, dirname as dirname3 } from "path";
607
+ var SIGNING_KEY_FILENAME = "registry-signing-key";
608
+ function getSigningKeyPath(targetDir) {
609
+ return join5(targetDir, ".ai", SIGNING_KEY_FILENAME);
610
+ }
611
+ function loadSigningKey(targetDir) {
612
+ const keyPath = getSigningKeyPath(targetDir);
613
+ if (!existsSync5(keyPath)) {
614
+ return null;
615
+ }
616
+ const raw = readFileSync6(keyPath, "utf8").trim();
617
+ if (!/^[0-9a-f]{64}$/.test(raw)) {
618
+ throw new Error(
619
+ `Signing key at '${keyPath}' is malformed. Expected a 64-character lowercase hex string (32 bytes). Re-generate with: npx multimodel-dev-os registry keygen --approved`
620
+ );
621
+ }
622
+ return raw;
623
+ }
624
+ function generateSigningKey() {
625
+ return randomBytes(32).toString("hex");
626
+ }
627
+ function saveSigningKey(targetDir, key) {
628
+ const keyPath = getSigningKeyPath(targetDir);
629
+ const keyDir = dirname3(keyPath);
630
+ if (!existsSync5(keyDir)) {
631
+ mkdirSync2(keyDir, { recursive: true });
632
+ }
633
+ writeFileSync3(keyPath, key + "\n", { encoding: "utf8", mode: 384 });
634
+ try {
635
+ chmodSync(keyPath, 384);
636
+ } catch (_e) {
637
+ }
638
+ }
639
+ function signPayload(hexKey, payload) {
640
+ if (typeof hexKey !== "string" || !/^[0-9a-f]{64}$/.test(hexKey)) {
641
+ throw new Error("Invalid signing key: must be a 64-character lowercase hex string.");
642
+ }
643
+ if (typeof payload !== "string") {
644
+ throw new Error("Payload to sign must be a string.");
645
+ }
646
+ const keyBytes = Buffer.from(hexKey, "hex");
647
+ return createHmac("sha256", keyBytes).update(payload, "utf8").digest("hex");
648
+ }
649
+ function createCanonicalPayload(data, fields) {
650
+ if (!data || typeof data !== "object") {
651
+ throw new Error("Data must be an object.");
652
+ }
653
+ if (!Array.isArray(fields)) {
654
+ throw new Error("Fields must be an array of strings.");
655
+ }
656
+ const sortedFields = [...fields].sort();
657
+ const obj = {};
658
+ for (const field of sortedFields) {
659
+ if (data[field] !== void 0) {
660
+ obj[field] = data[field];
661
+ }
662
+ }
663
+ return JSON.stringify(obj, (key, value) => {
664
+ if (value && typeof value === "object" && !Array.isArray(value)) {
665
+ return Object.keys(value).sort().reduce((sorted, k) => {
666
+ sorted[k] = value[k];
667
+ return sorted;
668
+ }, {});
669
+ }
670
+ return value;
671
+ });
672
+ }
673
+ function normalizePublicKey(input) {
674
+ if (typeof input !== "string") {
675
+ throw new Error("Public key must be a string.");
676
+ }
677
+ let trimmed = input.trim();
678
+ if (trimmed.startsWith("-----BEGIN PUBLIC KEY-----")) {
679
+ return trimmed;
680
+ }
681
+ if (trimmed.startsWith("-----BEGIN")) {
682
+ return trimmed;
683
+ }
684
+ const clean = trimmed.replace(/\s+/g, "");
685
+ const lines = [];
686
+ for (let i = 0; i < clean.length; i += 64) {
687
+ lines.push(clean.slice(i, i + 64));
688
+ }
689
+ return `-----BEGIN PUBLIC KEY-----
690
+ ${lines.join("\n")}
691
+ -----END PUBLIC KEY-----`;
692
+ }
693
+ function verifyEd25519Payload(publicKey, payload, signature) {
694
+ if (typeof publicKey !== "string" || typeof payload !== "string" || typeof signature !== "string") {
695
+ return false;
696
+ }
697
+ try {
698
+ const pubKey = normalizePublicKey(publicKey);
699
+ const sigBuffer = Buffer.from(signature, "base64");
700
+ return verify(null, Buffer.from(payload, "utf8"), pubKey, sigBuffer);
701
+ } catch (_e) {
702
+ return false;
703
+ }
704
+ }
705
+ function verifySignatureBlock({ manifest, trustedKeys, policy = {}, hmacKey = null, source = {} }) {
706
+ const isBundled = source.name === "bundled";
707
+ const isLocal = source.type === "local";
708
+ const isRemote = source.type === "remote" || !isBundled && !isLocal;
709
+ const signatureBlocks = [];
710
+ if (manifest.signature && typeof manifest.signature === "object") {
711
+ signatureBlocks.push(manifest.signature);
712
+ }
713
+ if (Array.isArray(manifest.signatures)) {
714
+ signatureBlocks.push(...manifest.signatures);
715
+ }
716
+ if (signatureBlocks.length === 0) {
717
+ if (policy.require_signature) {
718
+ return { verified: false, status: "failed", error: "Signature is required by policy but missing from manifest." };
719
+ }
720
+ if (isRemote && policy.allow_unsigned_remote === false) {
721
+ return { verified: false, status: "failed", error: "Unsigned remote registries are not allowed by policy." };
722
+ }
723
+ if (isBundled && policy.allow_unsigned_bundled === false) {
724
+ return { verified: false, status: "failed", error: "Unsigned bundled registries are not allowed by policy." };
725
+ }
726
+ if (isLocal && !isBundled && policy.allow_unsigned_local === false) {
727
+ return { verified: false, status: "failed", error: "Unsigned local registries are not allowed by policy." };
728
+ }
729
+ return { verified: true, status: "unsigned", message: "Registry is unsigned (allowed by policy)." };
730
+ }
731
+ let verifiedCount = 0;
732
+ const errors = [];
733
+ const allowedAlgs = policy.allowed_signature_algorithms || ["ed25519", "hmac-sha256"];
734
+ for (const sigBlock of signatureBlocks) {
735
+ const alg = sigBlock.algorithm;
736
+ const keyId = sigBlock.key_id;
737
+ const signature = sigBlock.signature;
738
+ const signedFields = sigBlock.signed_fields;
739
+ if (!alg || !keyId || !signature || !Array.isArray(signedFields)) {
740
+ errors.push(`Malformed signature block for key_id '${keyId || "unknown"}'.`);
741
+ continue;
742
+ }
743
+ if (!allowedAlgs.includes(alg)) {
744
+ errors.push(`Signature algorithm '${alg}' is not allowed by policy (allowed: ${allowedAlgs.join(", ")}).`);
745
+ continue;
746
+ }
747
+ if (alg === "hmac-sha256") {
748
+ if (!hmacKey) {
749
+ errors.push(`HMAC key not configured locally for key_id '${keyId}'.`);
750
+ continue;
751
+ }
752
+ try {
753
+ const payload = createCanonicalPayload(manifest, signedFields);
754
+ const expected = createHmac("sha256", Buffer.from(hmacKey, "hex")).update(payload, "utf8").digest("hex");
755
+ if (timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"))) {
756
+ verifiedCount++;
757
+ } else {
758
+ errors.push(`Invalid HMAC signature for key_id '${keyId}'.`);
759
+ }
760
+ } catch (err) {
761
+ errors.push(`HMAC signature verification failed: ${err.message}`);
762
+ }
763
+ } else if (alg === "ed25519") {
764
+ const trustedKey = trustedKeys ? trustedKeys.find((k) => k.key_id === keyId) : null;
765
+ if (!trustedKey) {
766
+ errors.push(`Key ID '${keyId}' not found in trust store.`);
767
+ continue;
768
+ }
769
+ if (trustedKey.status !== "active") {
770
+ errors.push(`Key ID '${keyId}' is ${trustedKey.status} (must be active).`);
771
+ continue;
772
+ }
773
+ const scopes = trustedKey.scopes || [];
774
+ if (!scopes.includes("registry") && !scopes.includes("catalog")) {
775
+ errors.push(`Key ID '${keyId}' does not have required scope 'registry' or 'catalog' (scopes: ${scopes.join(", ")}).`);
776
+ continue;
777
+ }
778
+ try {
779
+ const payload = createCanonicalPayload(manifest, signedFields);
780
+ if (verifyEd25519Payload(trustedKey.public_key, payload, signature)) {
781
+ verifiedCount++;
782
+ } else {
783
+ errors.push(`Invalid Ed25519 signature for key_id '${keyId}'.`);
784
+ }
785
+ } catch (err) {
786
+ errors.push(`Ed25519 signature verification failed: ${err.message}`);
787
+ }
788
+ } else {
789
+ errors.push(`Unsupported signature algorithm '${alg}' for key_id '${keyId}'.`);
790
+ }
791
+ }
792
+ if (verifiedCount > 0) {
793
+ return {
794
+ verified: true,
795
+ status: "verified",
796
+ verified_signatures: signatureBlocks.map((s) => ({ key_id: s.key_id, algorithm: s.algorithm }))
797
+ };
798
+ }
799
+ return {
800
+ verified: false,
801
+ status: "failed",
802
+ errors
803
+ };
804
+ }
805
+
806
+ // src/registry/trust-store.js
807
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
808
+ import { join as join6, isAbsolute } from "path";
809
+ function loadTrustedKeys(targetDir, policy) {
810
+ const pol = policy || loadRegistryPolicy(targetDir);
811
+ const keyFile = pol.trusted_keys_file || ".ai/registries/trusted-keys.yaml";
812
+ const filePath = isAbsolute(keyFile) ? keyFile : join6(targetDir, keyFile);
813
+ if (!existsSync6(filePath)) {
814
+ return [];
815
+ }
816
+ try {
817
+ const raw = readFileSync7(filePath, "utf8");
818
+ const parsed = parseYaml(raw);
819
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.trusted_publishers)) {
820
+ return [];
821
+ }
822
+ return parsed.trusted_publishers;
823
+ } catch (_e) {
824
+ return [];
825
+ }
826
+ }
827
+
828
+ // src/registry/verdict.js
829
+ function createTrustVerdict({
830
+ source,
831
+ source_type,
832
+ manifest_hash_status = "N/A",
833
+ catalog_hash_status = "N/A",
834
+ lockfile_status = "N/A",
835
+ provenance_status = "N/A",
836
+ signature_status = "N/A",
837
+ trusted_publisher_status = "N/A",
838
+ errors = [],
839
+ warnings = [],
840
+ final_status = "unknown"
841
+ }) {
842
+ return {
843
+ source,
844
+ source_type,
845
+ manifest_hash_status,
846
+ catalog_hash_status,
847
+ lockfile_status,
848
+ provenance_status,
849
+ signature_status,
850
+ trusted_publisher_status,
851
+ errors: Array.isArray(errors) ? errors : [],
852
+ warnings: Array.isArray(warnings) ? warnings : [],
853
+ final_status
854
+ };
855
+ }
856
+
536
857
  // src/catalog/loader.js
537
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
538
- import { join as join4 } from "path";
858
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
859
+ import { join as join7 } from "path";
539
860
  function loadCatalog(options = {}) {
540
861
  let catalog;
541
862
  if (options.allSources) {
@@ -543,10 +864,10 @@ function loadCatalog(options = {}) {
543
864
  } else if (options.source) {
544
865
  catalog = loadCatalogFromSource(options.source, options);
545
866
  } else {
546
- const path = join4(sourceRoot, ".ai", "plugins", "catalog.yaml");
867
+ const path = join7(sourceRoot, ".ai", "plugins", "catalog.yaml");
547
868
  try {
548
- if (existsSync4(path)) {
549
- const reg = parseYaml(readFileSync5(path, "utf8"));
869
+ if (existsSync7(path)) {
870
+ const reg = parseYaml(readFileSync8(path, "utf8"));
550
871
  catalog = reg.catalog || { plugins: [] };
551
872
  } else {
552
873
  catalog = { plugins: [] };
@@ -564,10 +885,10 @@ function loadCatalogFromSource(source, options = {}) {
564
885
  if (!source || source === "bundled") {
565
886
  return loadCatalog();
566
887
  } else if (source === "local") {
567
- const localPath = join4(options.target || process.cwd(), ".ai", "plugins", "catalog.yaml");
888
+ const localPath = join7(options.target || process.cwd(), ".ai", "plugins", "catalog.yaml");
568
889
  try {
569
- if (existsSync4(localPath)) {
570
- const reg = parseYaml(readFileSync5(localPath, "utf8"));
890
+ if (existsSync7(localPath)) {
891
+ const reg = parseYaml(readFileSync8(localPath, "utf8"));
571
892
  const catalog = reg.catalog || { plugins: [] };
572
893
  (catalog.plugins || []).forEach((p) => {
573
894
  p._source = "local";
@@ -590,10 +911,10 @@ function loadCatalogFromSource(source, options = {}) {
590
911
  process.exit(1);
591
912
  }
592
913
  }
593
- const cachePath = join4(sourceRoot, ".ai", "registry-cache", regName, "catalog.yaml");
914
+ const cachePath = join7(sourceRoot, ".ai", "registry-cache", regName, "catalog.yaml");
594
915
  try {
595
- if (existsSync4(cachePath)) {
596
- const reg = parseYaml(readFileSync5(cachePath, "utf8"));
916
+ if (existsSync7(cachePath)) {
917
+ const reg = parseYaml(readFileSync8(cachePath, "utf8"));
597
918
  const catalog = reg.catalog || { plugins: [] };
598
919
  (catalog.plugins || []).forEach((p) => {
599
920
  p._source = `remote:${regName}`;
@@ -615,10 +936,10 @@ function loadAllCatalogs(options = {}) {
615
936
  p._source = "bundled";
616
937
  allPlugins.push(p);
617
938
  });
618
- const localPath = join4(options.target || process.cwd(), ".ai", "plugins", "catalog.yaml");
619
- if (existsSync4(localPath)) {
939
+ const localPath = join7(options.target || process.cwd(), ".ai", "plugins", "catalog.yaml");
940
+ if (existsSync7(localPath)) {
620
941
  try {
621
- const localCat = parseYaml(readFileSync5(localPath, "utf8"));
942
+ const localCat = parseYaml(readFileSync8(localPath, "utf8"));
622
943
  const localPlugins = (localCat.catalog || {}).plugins || [];
623
944
  localPlugins.forEach((p) => {
624
945
  if (!allPlugins.some((bp) => bp.slug === p.slug)) {
@@ -631,10 +952,10 @@ function loadAllCatalogs(options = {}) {
631
952
  }
632
953
  if (policy.allow_remote_registries) {
633
954
  sources.filter((s) => s.type !== "local" && s.enabled).forEach((s) => {
634
- const cachePath = join4(sourceRoot, ".ai", "registry-cache", s.name, "catalog.yaml");
635
- if (existsSync4(cachePath)) {
955
+ const cachePath = join7(sourceRoot, ".ai", "registry-cache", s.name, "catalog.yaml");
956
+ if (existsSync7(cachePath)) {
636
957
  try {
637
- const remoteCat = parseYaml(readFileSync5(cachePath, "utf8"));
958
+ const remoteCat = parseYaml(readFileSync8(cachePath, "utf8"));
638
959
  const remotePlugins = (remoteCat.catalog || {}).plugins || [];
639
960
  remotePlugins.forEach((p) => {
640
961
  if (!allPlugins.some((bp) => bp.slug === p.slug)) {
@@ -1017,8 +1338,30 @@ if (COMMAND === "init") {
1017
1338
  console.error("\x1B[31mError: Please specify a cache subcommand: clear.\x1B[0m");
1018
1339
  process.exit(1);
1019
1340
  }
1341
+ } else if (sub === "keygen") {
1342
+ handleRegistryKeygen(params);
1343
+ } else if (sub === "lock") {
1344
+ handleRegistryLock(params);
1345
+ } else if (sub === "trust") {
1346
+ const trustSub = positional[2];
1347
+ if (trustSub === "list") {
1348
+ handleRegistryTrustList(params);
1349
+ } else if (trustSub === "show") {
1350
+ const keyId = positional[3];
1351
+ if (!keyId) {
1352
+ console.error("\x1B[31mError: Please specify a key ID.\x1B[0m");
1353
+ process.exit(1);
1354
+ }
1355
+ handleRegistryTrustShow(keyId, params);
1356
+ } else if (trustSub === "verify") {
1357
+ handleRegistryTrustVerify(params);
1358
+ } else {
1359
+ console.error("\x1B[31mError: Please specify a trust subcommand: list, show, or verify.\x1B[0m");
1360
+ console.log("Example: node bin/multimodel-dev-os.js registry trust list");
1361
+ process.exit(1);
1362
+ }
1020
1363
  } else {
1021
- console.error("\x1B[31mError: Please specify a registry subcommand: list, add, remove, sync, status, verify, show, or cache.\x1B[0m");
1364
+ console.error("\x1B[31mError: Please specify a registry subcommand: list, add, remove, sync, status, verify, show, cache, keygen, lock, or trust.\x1B[0m");
1022
1365
  console.log("Example: node bin/multimodel-dev-os.js registry list");
1023
1366
  process.exit(1);
1024
1367
  }
@@ -1102,40 +1445,40 @@ function handleInit(options) {
1102
1445
  console.log("\x1B[36mDry Run active - no actual modifications will occur\x1B[0m");
1103
1446
  const operations = [];
1104
1447
  const conflicts = [];
1105
- let templateDir = join5(sourceRoot, "examples", options.template);
1106
- if (!existsSync5(templateDir)) {
1448
+ let templateDir = join8(sourceRoot, "examples", options.template);
1449
+ if (!existsSync8(templateDir)) {
1107
1450
  console.warn(` \x1B[33m[WARNING] Template '${options.template}' source files could not be found.\x1B[0m`);
1108
1451
  console.warn(` To view available templates, run: \x1B[36mnpx multimodel-dev-os templates\x1B[0m`);
1109
1452
  console.warn(` Falling back to the stable \x1B[32m'general-app'\x1B[0m profile...
1110
1453
  `);
1111
- templateDir = join5(sourceRoot, "examples", "general-app");
1454
+ templateDir = join8(sourceRoot, "examples", "general-app");
1112
1455
  }
1113
- let agentsSrc = join5(templateDir, "AGENTS.md");
1114
- let memorySrc = join5(templateDir, "MEMORY.md");
1115
- let tasksSrc = join5(templateDir, "TASKS.md");
1116
- let runbookSrc = join5(sourceRoot, "RUNBOOK.md");
1117
- let configSrc = join5(templateDir, ".ai", "config.yaml");
1456
+ let agentsSrc = join8(templateDir, "AGENTS.md");
1457
+ let memorySrc = join8(templateDir, "MEMORY.md");
1458
+ let tasksSrc = join8(templateDir, "TASKS.md");
1459
+ let runbookSrc = join8(sourceRoot, "RUNBOOK.md");
1460
+ let configSrc = join8(templateDir, ".ai", "config.yaml");
1118
1461
  if (options.caveman) {
1119
- agentsSrc = join5(sourceRoot, ".ai", "templates", "AGENTS.caveman.md");
1120
- memorySrc = join5(sourceRoot, ".ai", "templates", "MEMORY.caveman.md");
1121
- tasksSrc = join5(sourceRoot, ".ai", "templates", "TASKS.caveman.md");
1122
- runbookSrc = join5(sourceRoot, ".ai", "templates", "RUNBOOK.caveman.md");
1462
+ agentsSrc = join8(sourceRoot, ".ai", "templates", "AGENTS.caveman.md");
1463
+ memorySrc = join8(sourceRoot, ".ai", "templates", "MEMORY.caveman.md");
1464
+ tasksSrc = join8(sourceRoot, ".ai", "templates", "TASKS.caveman.md");
1465
+ runbookSrc = join8(sourceRoot, ".ai", "templates", "RUNBOOK.caveman.md");
1123
1466
  }
1124
1467
  operations.push({ dest: "AGENTS.md", src: agentsSrc });
1125
1468
  operations.push({ dest: "MEMORY.md", src: memorySrc });
1126
1469
  operations.push({ dest: "TASKS.md", src: tasksSrc });
1127
1470
  operations.push({ dest: "RUNBOOK.md", src: runbookSrc });
1128
1471
  operations.push({ dest: ".ai/config.yaml", src: configSrc });
1129
- const templateAiDir = join5(templateDir, ".ai");
1130
- if (existsSync5(templateAiDir) && !options.caveman) {
1472
+ const templateAiDir = join8(templateDir, ".ai");
1473
+ if (existsSync8(templateAiDir) && !options.caveman) {
1131
1474
  const subdirs = ["context", "skills"];
1132
1475
  subdirs.forEach((sub) => {
1133
- const subPath = join5(templateAiDir, sub);
1134
- if (existsSync5(subPath)) {
1476
+ const subPath = join8(templateAiDir, sub);
1477
+ if (existsSync8(subPath)) {
1135
1478
  readdirSync(subPath).forEach((file) => {
1136
1479
  operations.push({
1137
- dest: join5(".ai", sub, file),
1138
- src: join5(subPath, file)
1480
+ dest: join8(".ai", sub, file),
1481
+ src: join8(subPath, file)
1139
1482
  });
1140
1483
  });
1141
1484
  }
@@ -1143,47 +1486,47 @@ function handleInit(options) {
1143
1486
  }
1144
1487
  const globalAiSubdirs = ["context", "agents", "skills", "prompts", "checks", "templates", "session-logs", "registries", "proposals", "intelligence"];
1145
1488
  globalAiSubdirs.forEach((sub) => {
1146
- const globalPath = join5(sourceRoot, ".ai", sub);
1147
- if (existsSync5(globalPath)) {
1489
+ const globalPath = join8(sourceRoot, ".ai", sub);
1490
+ if (existsSync8(globalPath)) {
1148
1491
  readdirSync(globalPath).forEach((file) => {
1149
- const destRel = join5(".ai", sub, file);
1492
+ const destRel = join8(".ai", sub, file);
1150
1493
  if (!operations.some((op) => op.dest === destRel)) {
1151
1494
  if (options.caveman && (sub === "context" || sub === "skills" || sub === "prompts" || sub === "checks")) {
1152
1495
  return;
1153
1496
  }
1154
1497
  operations.push({
1155
1498
  dest: destRel,
1156
- src: join5(globalPath, file)
1499
+ src: join8(globalPath, file)
1157
1500
  });
1158
1501
  }
1159
1502
  });
1160
1503
  }
1161
1504
  });
1162
1505
  options.adapters.forEach((adapter) => {
1163
- const adapterDir = join5(sourceRoot, "adapters", adapter);
1164
- if (existsSync5(adapterDir)) {
1506
+ const adapterDir = join8(sourceRoot, "adapters", adapter);
1507
+ if (existsSync8(adapterDir)) {
1165
1508
  const copyRecursive = (currSrc, currRel) => {
1166
1509
  if (statSync(currSrc).isDirectory()) {
1167
1510
  readdirSync(currSrc).forEach((file) => {
1168
- copyRecursive(join5(currSrc, file), join5(currRel, file));
1511
+ copyRecursive(join8(currSrc, file), join8(currRel, file));
1169
1512
  });
1170
1513
  } else {
1171
1514
  operations.push({
1172
- dest: join5("adapters", adapter, currRel),
1515
+ dest: join8("adapters", adapter, currRel),
1173
1516
  src: currSrc
1174
1517
  });
1175
1518
  }
1176
1519
  };
1177
1520
  readdirSync(adapterDir).forEach((file) => {
1178
- copyRecursive(join5(adapterDir, file), file);
1521
+ copyRecursive(join8(adapterDir, file), file);
1179
1522
  });
1180
1523
  } else {
1181
1524
  console.warn(`\x1B[33mWarning: Adapter '${adapter}' not found. Skipping.\x1B[0m`);
1182
1525
  }
1183
1526
  });
1184
1527
  operations.forEach((op) => {
1185
- const targetFile = join5(options.target, op.dest);
1186
- if (existsSync5(targetFile)) {
1528
+ const targetFile = join8(options.target, op.dest);
1529
+ if (existsSync8(targetFile)) {
1187
1530
  if (!options.force) {
1188
1531
  conflicts.push(op.dest);
1189
1532
  }
@@ -1197,24 +1540,24 @@ function handleInit(options) {
1197
1540
  process.exit(1);
1198
1541
  }
1199
1542
  operations.forEach((op) => {
1200
- const targetFile = join5(options.target, op.dest);
1201
- const targetDir = dirname2(targetFile);
1543
+ const targetFile = join8(options.target, op.dest);
1544
+ const targetDir = dirname4(targetFile);
1202
1545
  if (options.dryRun) {
1203
1546
  console.log(` \x1B[36m[DRY-RUN] WOULD CREATE:\x1B[0m ${op.dest}`);
1204
1547
  } else {
1205
- if (!existsSync5(targetDir)) {
1206
- mkdirSync(targetDir, { recursive: true });
1548
+ if (!existsSync8(targetDir)) {
1549
+ mkdirSync3(targetDir, { recursive: true });
1207
1550
  }
1208
- const data = readFileSync6(op.src);
1209
- writeFileSync2(targetFile, data);
1551
+ const data = readFileSync9(op.src);
1552
+ writeFileSync4(targetFile, data);
1210
1553
  console.log(` \x1B[32mCREATE:\x1B[0m ${op.dest}`);
1211
1554
  }
1212
1555
  });
1213
1556
  const dirsToEnsure = [".ai/context", ".ai/skills", ".ai/session-logs"];
1214
1557
  dirsToEnsure.forEach((d) => {
1215
- const fullPath = join5(options.target, d);
1216
- if (!options.dryRun && !existsSync5(fullPath)) {
1217
- mkdirSync(fullPath, { recursive: true });
1558
+ const fullPath = join8(options.target, d);
1559
+ if (!options.dryRun && !existsSync8(fullPath)) {
1560
+ mkdirSync3(fullPath, { recursive: true });
1218
1561
  console.log(` \x1B[32mCREATE DIR:\x1B[0m ${d}`);
1219
1562
  }
1220
1563
  });
@@ -1222,25 +1565,25 @@ function handleInit(options) {
1222
1565
  options.adapters.forEach((adapter) => {
1223
1566
  const a = ADAPTERS[adapter];
1224
1567
  if (a && a.rules_file) {
1225
- const srcFile = join5(sourceRoot, "adapters", adapter, a.rules_file);
1226
- const destFile = join5(options.target, a.rules_file);
1227
- const destDir = dirname2(destFile);
1228
- if (existsSync5(srcFile)) {
1229
- if (!existsSync5(destDir))
1230
- mkdirSync(destDir, { recursive: true });
1231
- writeFileSync2(destFile, readFileSync6(srcFile));
1568
+ const srcFile = join8(sourceRoot, "adapters", adapter, a.rules_file);
1569
+ const destFile = join8(options.target, a.rules_file);
1570
+ const destDir = dirname4(destFile);
1571
+ if (existsSync8(srcFile)) {
1572
+ if (!existsSync8(destDir))
1573
+ mkdirSync3(destDir, { recursive: true });
1574
+ writeFileSync4(destFile, readFileSync9(srcFile));
1232
1575
  console.log(` \x1B[32mCREATE ROOT ADAPTER FILE:\x1B[0m ${a.rules_file}`);
1233
1576
  }
1234
1577
  }
1235
1578
  });
1236
- const targetConfigPath = join5(options.target, ".ai/config.yaml");
1237
- if (existsSync5(targetConfigPath) && options.adapters.length > 0) {
1238
- let configContent = readFileSync6(targetConfigPath, "utf8");
1579
+ const targetConfigPath = join8(options.target, ".ai/config.yaml");
1580
+ if (existsSync8(targetConfigPath) && options.adapters.length > 0) {
1581
+ let configContent = readFileSync9(targetConfigPath, "utf8");
1239
1582
  options.adapters.forEach((adapter) => {
1240
1583
  const regex = new RegExp(`${adapter}:\\s*false`, "g");
1241
1584
  configContent = configContent.replace(regex, `${adapter}: true`);
1242
1585
  });
1243
- writeFileSync2(targetConfigPath, configContent, "utf8");
1586
+ writeFileSync4(targetConfigPath, configContent, "utf8");
1244
1587
  console.log(` \x1B[32mUPDATE CONFIG:\x1B[0m Enabled selected adapters [${options.adapters.join(", ")}] in .ai/config.yaml`);
1245
1588
  }
1246
1589
  } else {
@@ -1284,8 +1627,8 @@ function handleVerify(options) {
1284
1627
  let passed = 0;
1285
1628
  let failed = 0;
1286
1629
  const assertFile = (relPath) => {
1287
- const fullPath = join5(options.target, relPath);
1288
- if (existsSync5(fullPath) && statSync(fullPath).isFile()) {
1630
+ const fullPath = join8(options.target, relPath);
1631
+ if (existsSync8(fullPath) && statSync(fullPath).isFile()) {
1289
1632
  console.log(` \x1B[32m\u2713\x1B[0m ${relPath}`);
1290
1633
  passed++;
1291
1634
  } else {
@@ -1356,9 +1699,9 @@ function handleDoctor(options) {
1356
1699
  console.warn(` \x1B[33m[WARNING]\x1B[0m ${msg}`);
1357
1700
  warnings++;
1358
1701
  };
1359
- const gitignorePath = join5(options.target, ".gitignore");
1360
- if (existsSync5(gitignorePath)) {
1361
- const content = readFileSync6(gitignorePath, "utf8");
1702
+ const gitignorePath = join8(options.target, ".gitignore");
1703
+ if (existsSync8(gitignorePath)) {
1704
+ const content = readFileSync9(gitignorePath, "utf8");
1362
1705
  if (!content.includes("node_modules")) {
1363
1706
  warn(".gitignore is missing node_modules! This will cause AI tools to choke by scanning dependencies.");
1364
1707
  }
@@ -1368,9 +1711,9 @@ function handleDoctor(options) {
1368
1711
  } else {
1369
1712
  warn("Missing .gitignore file in target workspace! AI tools might read large build artifacts.");
1370
1713
  }
1371
- const agentsPath = join5(options.target, "AGENTS.md");
1372
- if (existsSync5(agentsPath)) {
1373
- const content = readFileSync6(agentsPath, "utf8");
1714
+ const agentsPath = join8(options.target, "AGENTS.md");
1715
+ if (existsSync8(agentsPath)) {
1716
+ const content = readFileSync9(agentsPath, "utf8");
1374
1717
  if (!content.includes("build:") && !content.includes("build")) {
1375
1718
  warn("AGENTS.md is missing build command specifications.");
1376
1719
  }
@@ -1383,31 +1726,31 @@ function handleDoctor(options) {
1383
1726
  } else {
1384
1727
  warn("AGENTS.md is missing from project root.");
1385
1728
  }
1386
- const memoryPath = join5(options.target, "MEMORY.md");
1387
- if (existsSync5(memoryPath)) {
1388
- const content = readFileSync6(memoryPath, "utf8");
1729
+ const memoryPath = join8(options.target, "MEMORY.md");
1730
+ if (existsSync8(memoryPath)) {
1731
+ const content = readFileSync9(memoryPath, "utf8");
1389
1732
  const placeholdersCount = (content.match(/null/g) || []).length;
1390
1733
  if (placeholdersCount > 3) {
1391
1734
  warn(`MEMORY.md contains ${placeholdersCount} empty 'null' placeholders. Update project constraints.`);
1392
1735
  }
1393
1736
  }
1394
- const tasksPath = join5(options.target, "TASKS.md");
1395
- if (existsSync5(tasksPath)) {
1396
- const content = readFileSync6(tasksPath, "utf8");
1737
+ const tasksPath = join8(options.target, "TASKS.md");
1738
+ if (existsSync8(tasksPath)) {
1739
+ const content = readFileSync9(tasksPath, "utf8");
1397
1740
  if (!content.includes("- [ ]") && !content.includes("- [/]")) {
1398
1741
  warn("TASKS.md has no active task section (no tasks marked as - [ ] or - [/]).");
1399
1742
  }
1400
1743
  } else {
1401
1744
  warn("TASKS.md is missing from project root.");
1402
1745
  }
1403
- const configPath = join5(options.target, ".ai", "config.yaml");
1404
- if (existsSync5(configPath)) {
1405
- const content = readFileSync6(configPath, "utf8");
1746
+ const configPath = join8(options.target, ".ai", "config.yaml");
1747
+ if (existsSync8(configPath)) {
1748
+ const content = readFileSync9(configPath, "utf8");
1406
1749
  const checkAdapter = (adapterName, filename) => {
1407
1750
  const regex = new RegExp(`${adapterName}:\\s*true`);
1408
1751
  if (regex.test(content)) {
1409
- const filePath = join5(options.target, filename);
1410
- if (!existsSync5(filePath)) {
1752
+ const filePath = join8(options.target, filename);
1753
+ if (!existsSync8(filePath)) {
1411
1754
  warn(`Adapter '${adapterName}' is enabled in .ai/config.yaml but matching adapter file '${filename}' is missing from root.`);
1412
1755
  }
1413
1756
  }
@@ -1422,9 +1765,9 @@ function handleDoctor(options) {
1422
1765
  }
1423
1766
  const sinkFolders = ["node_modules", "dist", "build", ".next", ".git"];
1424
1767
  sinkFolders.forEach((folder) => {
1425
- const fullPath = join5(options.target, folder);
1426
- if (existsSync5(fullPath)) {
1427
- const gitignore = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf8") : "";
1768
+ const fullPath = join8(options.target, folder);
1769
+ if (existsSync8(fullPath)) {
1770
+ const gitignore = existsSync8(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
1428
1771
  if (!gitignore.includes(folder)) {
1429
1772
  warn(`Large token-sink directory '${folder}/' is present in workspace but not ignored in .gitignore. AI tools may read it.`);
1430
1773
  }
@@ -1448,8 +1791,8 @@ function handleValidate(options) {
1448
1791
  `);
1449
1792
  let errors = 0;
1450
1793
  const assertPath = (relPath, type) => {
1451
- const fullPath = join5(options.target, relPath);
1452
- if (existsSync5(fullPath)) {
1794
+ const fullPath = join8(options.target, relPath);
1795
+ if (existsSync8(fullPath)) {
1453
1796
  const stat = statSync(fullPath);
1454
1797
  const isOk = type === "file" ? stat.isFile() : stat.isDirectory();
1455
1798
  if (isOk) {
@@ -1467,15 +1810,15 @@ function handleValidate(options) {
1467
1810
  core.forEach((f) => assertPath(f, "file"));
1468
1811
  const dirs = [".ai/context", ".ai/skills", ".ai/session-logs"];
1469
1812
  dirs.forEach((d) => assertPath(d, "dir"));
1470
- const agentsPath = join5(options.target, ".ai/agents");
1471
- const agentsExist = existsSync5(agentsPath) && statSync(agentsPath).isDirectory();
1813
+ const agentsPath = join8(options.target, ".ai/agents");
1814
+ const agentsExist = existsSync8(agentsPath) && statSync(agentsPath).isDirectory();
1472
1815
  if (agentsExist) {
1473
1816
  console.log(` \x1B[32m\u2713\x1B[0m .ai/agents (dir)`);
1474
1817
  } else {
1475
- const agentsMdPath = join5(options.target, "AGENTS.md");
1818
+ const agentsMdPath = join8(options.target, "AGENTS.md");
1476
1819
  let explained = false;
1477
- if (existsSync5(agentsMdPath)) {
1478
- const agentsMdContent = readFileSync6(agentsMdPath, "utf8");
1820
+ if (existsSync8(agentsMdPath)) {
1821
+ const agentsMdContent = readFileSync9(agentsMdPath, "utf8");
1479
1822
  if (agentsMdContent.includes("multimodel") || agentsMdContent.includes("orchestrator") || agentsMdContent.includes("global") || agentsMdContent.includes("role") || agentsMdContent.includes("Agent Roles")) {
1480
1823
  explained = true;
1481
1824
  }
@@ -1487,14 +1830,14 @@ function handleValidate(options) {
1487
1830
  errors++;
1488
1831
  }
1489
1832
  }
1490
- const configPath = join5(options.target, ".ai", "config.yaml");
1491
- if (existsSync5(configPath)) {
1492
- const content = readFileSync6(configPath, "utf8");
1833
+ const configPath = join8(options.target, ".ai", "config.yaml");
1834
+ if (existsSync8(configPath)) {
1835
+ const content = readFileSync9(configPath, "utf8");
1493
1836
  const assertAdapter = (adapterName, filename) => {
1494
1837
  const regex = new RegExp(`${adapterName}:\\s*true`);
1495
1838
  if (regex.test(content)) {
1496
- const fullPath = join5(options.target, filename);
1497
- if (existsSync5(fullPath)) {
1839
+ const fullPath = join8(options.target, filename);
1840
+ if (existsSync8(fullPath)) {
1498
1841
  console.log(` \x1B[32m\u2713\x1B[0m ${filename} (enabled adapter rules file verified)`);
1499
1842
  } else {
1500
1843
  console.error(` \x1B[31m\u2717 ${filename} (adapter '${adapterName}' is enabled in .ai/config.yaml, but rule file is missing!)\x1B[0m`);
@@ -1538,12 +1881,12 @@ function handleValidate(options) {
1538
1881
  }
1539
1882
  }
1540
1883
  function handleListModels(options) {
1541
- const registryPath = join5(sourceRoot, ".ai", "models", "registry.yaml");
1542
- if (!existsSync5(registryPath)) {
1884
+ const registryPath = join8(sourceRoot, ".ai", "models", "registry.yaml");
1885
+ if (!existsSync8(registryPath)) {
1543
1886
  console.error("Error: Model registry not found.");
1544
1887
  process.exit(1);
1545
1888
  }
1546
- const registry = parseYaml(readFileSync6(registryPath, "utf8"));
1889
+ const registry = parseYaml(readFileSync9(registryPath, "utf8"));
1547
1890
  const models = registry.models || {};
1548
1891
  if (options && options.json) {
1549
1892
  console.log(JSON.stringify(models, null, 2));
@@ -1564,12 +1907,12 @@ function handleListModels(options) {
1564
1907
  console.log("\nUse \x1B[36mshow-model <model-alias>\x1B[0m to view detailed model capabilities.\n");
1565
1908
  }
1566
1909
  function handleShowModel(name) {
1567
- const registryPath = join5(sourceRoot, ".ai", "models", "registry.yaml");
1568
- if (!existsSync5(registryPath)) {
1910
+ const registryPath = join8(sourceRoot, ".ai", "models", "registry.yaml");
1911
+ if (!existsSync8(registryPath)) {
1569
1912
  console.error("Error: Model registry not found.");
1570
1913
  process.exit(1);
1571
1914
  }
1572
- const registry = parseYaml(readFileSync6(registryPath, "utf8"));
1915
+ const registry = parseYaml(readFileSync9(registryPath, "utf8"));
1573
1916
  const models = registry.models || {};
1574
1917
  const m = models[name];
1575
1918
  if (!m) {
@@ -1594,12 +1937,12 @@ function handleShowModel(name) {
1594
1937
  console.log();
1595
1938
  }
1596
1939
  function handleListProviders() {
1597
- const providersPath = join5(sourceRoot, ".ai", "models", "providers.yaml");
1598
- if (!existsSync5(providersPath)) {
1940
+ const providersPath = join8(sourceRoot, ".ai", "models", "providers.yaml");
1941
+ if (!existsSync8(providersPath)) {
1599
1942
  console.error("Error: Providers registry not found.");
1600
1943
  process.exit(1);
1601
1944
  }
1602
- const reg = parseYaml(readFileSync6(providersPath, "utf8"));
1945
+ const reg = parseYaml(readFileSync9(providersPath, "utf8"));
1603
1946
  const providers = reg.providers || {};
1604
1947
  console.log(`
1605
1948
  \u{1F50C} \x1B[36mAI Providers [v${version}]\x1B[0m`);
@@ -1614,12 +1957,12 @@ function handleListProviders() {
1614
1957
  console.log();
1615
1958
  }
1616
1959
  function handleRouteModel(task) {
1617
- const presetsPath = join5(sourceRoot, ".ai", "models", "routing-presets.yaml");
1618
- if (!existsSync5(presetsPath)) {
1960
+ const presetsPath = join8(sourceRoot, ".ai", "models", "routing-presets.yaml");
1961
+ if (!existsSync8(presetsPath)) {
1619
1962
  console.error("Error: Routing presets not found.");
1620
1963
  process.exit(1);
1621
1964
  }
1622
- const reg = parseYaml(readFileSync6(presetsPath, "utf8"));
1965
+ const reg = parseYaml(readFileSync9(presetsPath, "utf8"));
1623
1966
  const presets = reg.presets || {};
1624
1967
  const preset = presets[task];
1625
1968
  if (!preset) {
@@ -1634,12 +1977,12 @@ function handleRouteModel(task) {
1634
1977
  console.log();
1635
1978
  }
1636
1979
  function handleListAdapters(options) {
1637
- const adaptersPath = join5(sourceRoot, ".ai", "adapters", "registry.yaml");
1638
- if (!existsSync5(adaptersPath)) {
1980
+ const adaptersPath = join8(sourceRoot, ".ai", "adapters", "registry.yaml");
1981
+ if (!existsSync8(adaptersPath)) {
1639
1982
  console.error("Error: Adapters registry not found.");
1640
1983
  process.exit(1);
1641
1984
  }
1642
- const reg = parseYaml(readFileSync6(adaptersPath, "utf8"));
1985
+ const reg = parseYaml(readFileSync9(adaptersPath, "utf8"));
1643
1986
  const adapters = reg.adapters || {};
1644
1987
  if (options && options.json) {
1645
1988
  console.log(JSON.stringify(adapters, null, 2));
@@ -1659,12 +2002,12 @@ function handleListAdapters(options) {
1659
2002
  console.log("\nUse \x1B[36mshow-adapter <adapter-name>\x1B[0m to view detailed adapter metadata.\n");
1660
2003
  }
1661
2004
  function handleShowAdapter(name) {
1662
- const adaptersPath = join5(sourceRoot, ".ai", "adapters", "registry.yaml");
1663
- if (!existsSync5(adaptersPath)) {
2005
+ const adaptersPath = join8(sourceRoot, ".ai", "adapters", "registry.yaml");
2006
+ if (!existsSync8(adaptersPath)) {
1664
2007
  console.error("Error: Adapters registry not found.");
1665
2008
  process.exit(1);
1666
2009
  }
1667
- const reg = parseYaml(readFileSync6(adaptersPath, "utf8"));
2010
+ const reg = parseYaml(readFileSync9(adaptersPath, "utf8"));
1668
2011
  const adapters = reg.adapters || {};
1669
2012
  const a = adapters[name];
1670
2013
  if (!a) {
@@ -1680,8 +2023,8 @@ function handleShowAdapter(name) {
1680
2023
  console.log();
1681
2024
  }
1682
2025
  function handleListSkills(options) {
1683
- const skillsDir = join5(options.target, ".ai", "skills");
1684
- if (!existsSync5(skillsDir)) {
2026
+ const skillsDir = join8(options.target, ".ai", "skills");
2027
+ if (!existsSync8(skillsDir)) {
1685
2028
  console.log("\n\x1B[33m[Notice] .ai/skills directory is not initialized in the target workspace.\x1B[0m\n");
1686
2029
  return;
1687
2030
  }
@@ -1695,16 +2038,16 @@ function handleListSkills(options) {
1695
2038
  console.log("\nUse \x1B[36mshow-skill <skill-name>\x1B[0m to read a skill's prompt text.\n");
1696
2039
  }
1697
2040
  function handleShowSkill(name, options) {
1698
- const skillsDir = join5(options.target, ".ai", "skills");
1699
- const skillFile = join5(skillsDir, name.endsWith(".md") ? name : `${name}.md`);
1700
- if (!existsSync5(skillFile)) {
2041
+ const skillsDir = join8(options.target, ".ai", "skills");
2042
+ const skillFile = join8(skillsDir, name.endsWith(".md") ? name : `${name}.md`);
2043
+ if (!existsSync8(skillFile)) {
1701
2044
  console.error(`\x1B[31mError: Skill '${name}' not found in target .ai/skills/.\x1B[0m`);
1702
2045
  process.exit(1);
1703
2046
  }
1704
2047
  console.log(`
1705
2048
  \u{1F4D6} \x1B[36mSkill Prompt: ${name}\x1B[0m`);
1706
2049
  console.log("==================================================");
1707
- console.log(readFileSync6(skillFile, "utf8"));
2050
+ console.log(readFileSync9(skillFile, "utf8"));
1708
2051
  console.log();
1709
2052
  }
1710
2053
  function parseThresholdToBytes(val) {
@@ -1728,13 +2071,13 @@ function handleDoctorTokens(options) {
1728
2071
  const filesFound = [];
1729
2072
  const ignoredDirs = [".git", "node_modules", "dist", "build", ".next", ".expo", "bin", "assets", "docs", "web-build", "out", "coverage", ".nuxt", ".svelte-kit", "bower_components", "vendor"];
1730
2073
  function scan(dir) {
1731
- if (!existsSync5(dir))
2074
+ if (!existsSync8(dir))
1732
2075
  return;
1733
2076
  const items = readdirSync(dir);
1734
2077
  for (const item of items) {
1735
2078
  if (ignoredDirs.includes(item))
1736
2079
  continue;
1737
- const fullPath = join5(dir, item);
2080
+ const fullPath = join8(dir, item);
1738
2081
  try {
1739
2082
  const stat = statSync(fullPath);
1740
2083
  if (stat.isDirectory()) {
@@ -1796,19 +2139,19 @@ function handleValidateTemplate(name) {
1796
2139
  console.log(` \x1B[32m\u2713\x1B[0m Registry key: ${k}`);
1797
2140
  }
1798
2141
  });
1799
- const templateDir = join5(sourceRoot, "examples", name);
1800
- if (!existsSync5(templateDir)) {
2142
+ const templateDir = join8(sourceRoot, "examples", name);
2143
+ if (!existsSync8(templateDir)) {
1801
2144
  console.error(` \x1B[31m\u2717 Source folder missing: examples/${name}\x1B[0m`);
1802
2145
  errors++;
1803
2146
  } else {
1804
2147
  console.log(` \x1B[32m\u2713\x1B[0m Source folder: examples/${name}`);
1805
2148
  if (Array.isArray(t.required_files)) {
1806
2149
  t.required_files.forEach((f) => {
1807
- const filePath = join5(templateDir, f);
1808
- const globalPath = join5(sourceRoot, f);
1809
- if (existsSync5(filePath)) {
2150
+ const filePath = join8(templateDir, f);
2151
+ const globalPath = join8(sourceRoot, f);
2152
+ if (existsSync8(filePath)) {
1810
2153
  console.log(` \x1B[32m\u2713\x1B[0m Required file (template override): ${f}`);
1811
- } else if (existsSync5(globalPath)) {
2154
+ } else if (existsSync8(globalPath)) {
1812
2155
  console.log(` \x1B[32m\u2713\x1B[0m Required file (global fallback): ${f}`);
1813
2156
  } else {
1814
2157
  console.error(` \x1B[31m\u2717 Required file missing: ${f}\x1B[0m`);
@@ -1847,22 +2190,22 @@ function handleValidateAdapter(name) {
1847
2190
  console.log(` \x1B[32m\u2713\x1B[0m Registry key: ${k}`);
1848
2191
  }
1849
2192
  });
1850
- const adapterDir = join5(sourceRoot, "adapters", name);
1851
- if (!existsSync5(adapterDir)) {
2193
+ const adapterDir = join8(sourceRoot, "adapters", name);
2194
+ if (!existsSync8(adapterDir)) {
1852
2195
  console.error(` \x1B[31m\u2717 Source folder missing: adapters/${name}\x1B[0m`);
1853
2196
  errors++;
1854
2197
  } else {
1855
2198
  console.log(` \x1B[32m\u2713\x1B[0m Source folder: adapters/${name}`);
1856
- const setupFile = join5(adapterDir, "setup.md");
1857
- if (existsSync5(setupFile)) {
2199
+ const setupFile = join8(adapterDir, "setup.md");
2200
+ if (existsSync8(setupFile)) {
1858
2201
  console.log(` \x1B[32m\u2713\x1B[0m Required file: setup.md`);
1859
2202
  } else {
1860
2203
  console.error(` \x1B[31m\u2717 Required file missing: adapters/${name}/setup.md\x1B[0m`);
1861
2204
  errors++;
1862
2205
  }
1863
2206
  if (a.rules_file) {
1864
- const rulesFile = join5(adapterDir, a.rules_file);
1865
- if (existsSync5(rulesFile)) {
2207
+ const rulesFile = join8(adapterDir, a.rules_file);
2208
+ if (existsSync8(rulesFile)) {
1866
2209
  console.log(` \x1B[32m\u2713\x1B[0m Rules file: ${a.rules_file}`);
1867
2210
  } else {
1868
2211
  console.error(` \x1B[31m\u2717 Rules file missing: adapters/${name}/${a.rules_file}\x1B[0m`);
@@ -1883,18 +2226,18 @@ function handleValidateAdapter(name) {
1883
2226
  }
1884
2227
  }
1885
2228
  function handleValidateSkill(name, options) {
1886
- const skillsDir = join5(options.target, ".ai", "skills");
1887
- let skillFile = join5(skillsDir, name.endsWith(".md") ? name : `${name}.md`);
1888
- if (!existsSync5(skillFile)) {
1889
- skillFile = join5(sourceRoot, ".ai", "skills", name.endsWith(".md") ? name : `${name}.md`);
2229
+ const skillsDir = join8(options.target, ".ai", "skills");
2230
+ let skillFile = join8(skillsDir, name.endsWith(".md") ? name : `${name}.md`);
2231
+ if (!existsSync8(skillFile)) {
2232
+ skillFile = join8(sourceRoot, ".ai", "skills", name.endsWith(".md") ? name : `${name}.md`);
1890
2233
  }
1891
- if (!existsSync5(skillFile)) {
2234
+ if (!existsSync8(skillFile)) {
1892
2235
  console.error(`\x1B[31mError: Skill '${name}' not found.\x1B[0m`);
1893
2236
  process.exit(1);
1894
2237
  }
1895
2238
  console.log(`
1896
2239
  \u{1F4CB} \x1B[34mValidating Skill: ${name}\x1B[0m`);
1897
- const content = readFileSync6(skillFile, "utf8");
2240
+ const content = readFileSync9(skillFile, "utf8");
1898
2241
  let errors = 0;
1899
2242
  const reqHeaders = [
1900
2243
  { header: "# Purpose", regex: /^#\s+Purpose/mi },
@@ -1943,8 +2286,8 @@ Validating Template: ${name}`);
1943
2286
  errors++;
1944
2287
  }
1945
2288
  });
1946
- const templateDir = join5(sourceRoot, "examples", name);
1947
- if (t.status === "stable" && !existsSync5(templateDir)) {
2289
+ const templateDir = join8(sourceRoot, "examples", name);
2290
+ if (t.status === "stable" && !existsSync8(templateDir)) {
1948
2291
  console.error(` \x1B[31m\u2717 Stable template source folder missing: examples/${name}\x1B[0m`);
1949
2292
  errors++;
1950
2293
  }
@@ -1978,7 +2321,7 @@ function handleDoctorRelease(options) {
1978
2321
  let warnings = 0;
1979
2322
  let packageVersion = "unknown";
1980
2323
  try {
1981
- const pkg = JSON.parse(readFileSync6(join5(sourceRoot, "package.json"), "utf8"));
2324
+ const pkg = JSON.parse(readFileSync9(join8(sourceRoot, "package.json"), "utf8"));
1982
2325
  packageVersion = pkg.version;
1983
2326
  console.log(` \x1B[32m\u2713\x1B[0m package.json version: ${packageVersion}`);
1984
2327
  } catch (e) {
@@ -1986,9 +2329,9 @@ function handleDoctorRelease(options) {
1986
2329
  warnings++;
1987
2330
  }
1988
2331
  const checkInstallScript = (filename, regex) => {
1989
- const filePath = join5(sourceRoot, filename);
1990
- if (existsSync5(filePath)) {
1991
- const content = readFileSync6(filePath, "utf8");
2332
+ const filePath = join8(sourceRoot, filename);
2333
+ if (existsSync8(filePath)) {
2334
+ const content = readFileSync9(filePath, "utf8");
1992
2335
  const match = content.match(regex);
1993
2336
  if (match && match[1] === packageVersion) {
1994
2337
  console.log(` \x1B[32m\u2713\x1B[0m ${filename} version aligns: ${match[1]}`);
@@ -2002,8 +2345,8 @@ function handleDoctorRelease(options) {
2002
2345
  checkInstallScript("scripts/install.ps1", /\$VERSION\s*=\s*"([^"]+)"/i);
2003
2346
  const blacklist = [".npmrc"];
2004
2347
  blacklist.forEach((file) => {
2005
- const fullPath = join5(sourceRoot, file);
2006
- if (existsSync5(fullPath)) {
2348
+ const fullPath = join8(sourceRoot, file);
2349
+ if (existsSync8(fullPath)) {
2007
2350
  console.warn(` \x1B[33m[WARNING]\x1B[0m Blacklisted file found in release root: ${file}`);
2008
2351
  warnings++;
2009
2352
  } else {
@@ -2011,11 +2354,11 @@ function handleDoctorRelease(options) {
2011
2354
  }
2012
2355
  });
2013
2356
  const scanSafety = (dir) => {
2014
- if (!existsSync5(dir))
2357
+ if (!existsSync8(dir))
2015
2358
  return;
2016
2359
  const items = readdirSync(dir);
2017
2360
  for (const item of items) {
2018
- const fullPath = join5(dir, item);
2361
+ const fullPath = join8(dir, item);
2019
2362
  try {
2020
2363
  const stat = statSync(fullPath);
2021
2364
  if (stat.isDirectory()) {
@@ -2030,7 +2373,7 @@ function handleDoctorRelease(options) {
2030
2373
  }
2031
2374
  }
2032
2375
  };
2033
- scanSafety(join5(sourceRoot, "examples"));
2376
+ scanSafety(join8(sourceRoot, "examples"));
2034
2377
  console.log("\n==================================================");
2035
2378
  if (warnings > 0) {
2036
2379
  console.warn(` \x1B[33mRelease doctor complete with ${warnings} warnings.\x1B[0m
@@ -2043,11 +2386,11 @@ function scanTarget(targetDir) {
2043
2386
  const files = [];
2044
2387
  let ignoredCount = 0;
2045
2388
  function walk(dir) {
2046
- if (!existsSync5(dir))
2389
+ if (!existsSync8(dir))
2047
2390
  return;
2048
2391
  const items = readdirSync(dir);
2049
2392
  for (const item of items) {
2050
- const fullPath = join5(dir, item);
2393
+ const fullPath = join8(dir, item);
2051
2394
  const relPath = relative(targetDir, fullPath).replace(/\\/g, "/");
2052
2395
  if (shouldIgnorePath(relPath)) {
2053
2396
  ignoredCount++;
@@ -2086,7 +2429,7 @@ function detectFrameworkSignals(files, targetDir) {
2086
2429
  if (hasFile("package.json")) {
2087
2430
  signals.push("Node.js");
2088
2431
  try {
2089
- const pkg = JSON.parse(readFileSync6(join5(targetDir, "package.json"), "utf8"));
2432
+ const pkg = JSON.parse(readFileSync9(join8(targetDir, "package.json"), "utf8"));
2090
2433
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2091
2434
  if (deps["react"])
2092
2435
  signals.push("React");
@@ -2168,8 +2511,8 @@ function detectAiDevOsSignals(files) {
2168
2511
  }
2169
2512
  function detectRisks(files, targetDir) {
2170
2513
  const risks = [];
2171
- const gitignorePath = join5(targetDir, ".gitignore");
2172
- const gitignoreContent = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf8") : "";
2514
+ const gitignorePath = join8(targetDir, ".gitignore");
2515
+ const gitignoreContent = existsSync8(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
2173
2516
  const hasFolder = (name) => files.some((f) => f.relPath.split("/")[0] === name);
2174
2517
  if (hasFolder("node_modules") && !gitignoreContent.includes("node_modules")) {
2175
2518
  risks.push({
@@ -2226,13 +2569,13 @@ function buildMemoryIndex(targetDir) {
2226
2569
  };
2227
2570
  }
2228
2571
  function writeMemoryFiles(targetDir, index) {
2229
- const intelDir = join5(targetDir, ".ai", "intelligence");
2230
- if (!existsSync5(intelDir)) {
2231
- mkdirSync(intelDir, { recursive: true });
2572
+ const intelDir = join8(targetDir, ".ai", "intelligence");
2573
+ if (!existsSync8(intelDir)) {
2574
+ mkdirSync3(intelDir, { recursive: true });
2232
2575
  }
2233
- const hashJsonPath = join5(intelDir, "memory.hash.json");
2234
- writeFileSync2(hashJsonPath, JSON.stringify(index, null, 2), "utf8");
2235
- const summaryMdPath = join5(intelDir, "memory.summary.md");
2576
+ const hashJsonPath = join8(intelDir, "memory.hash.json");
2577
+ writeFileSync4(hashJsonPath, JSON.stringify(index, null, 2), "utf8");
2578
+ const summaryMdPath = join8(intelDir, "memory.summary.md");
2236
2579
  let md = `# MultiModel Dev OS Repository Memory Summary
2237
2580
 
2238
2581
  `;
@@ -2281,16 +2624,16 @@ function writeMemoryFiles(targetDir, index) {
2281
2624
  md += `- ${step}
2282
2625
  `;
2283
2626
  });
2284
- writeFileSync2(summaryMdPath, md, "utf8");
2627
+ writeFileSync4(summaryMdPath, md, "utf8");
2285
2628
  }
2286
2629
  function diffMemory(targetDir) {
2287
- const hashJsonPath = join5(targetDir, ".ai", "intelligence", "memory.hash.json");
2288
- if (!existsSync5(hashJsonPath)) {
2630
+ const hashJsonPath = join8(targetDir, ".ai", "intelligence", "memory.hash.json");
2631
+ if (!existsSync8(hashJsonPath)) {
2289
2632
  return null;
2290
2633
  }
2291
2634
  let existing;
2292
2635
  try {
2293
- existing = JSON.parse(readFileSync6(hashJsonPath, "utf8"));
2636
+ existing = JSON.parse(readFileSync9(hashJsonPath, "utf8"));
2294
2637
  } catch (e) {
2295
2638
  return null;
2296
2639
  }
@@ -2425,9 +2768,9 @@ function handleMemoryDiff(options) {
2425
2768
  console.log();
2426
2769
  }
2427
2770
  function handleFeedbackAdd(options) {
2428
- const intelDir = join5(options.target, ".ai", "intelligence");
2429
- if (!options.dryRun && !existsSync5(intelDir)) {
2430
- mkdirSync(intelDir, { recursive: true });
2771
+ const intelDir = join8(options.target, ".ai", "intelligence");
2772
+ if (!options.dryRun && !existsSync8(intelDir)) {
2773
+ mkdirSync3(intelDir, { recursive: true });
2431
2774
  }
2432
2775
  const addIdx = process.argv.indexOf("add");
2433
2776
  const text = addIdx !== -1 && process.argv[addIdx + 1] && !process.argv[addIdx + 1].startsWith("-") ? process.argv[addIdx + 1] : null;
@@ -2452,15 +2795,15 @@ function handleFeedbackAdd(options) {
2452
2795
  };
2453
2796
  rawRecord.hash = createHash2("sha256").update(JSON.stringify(rawRecord)).digest("hex");
2454
2797
  const recordLine = JSON.stringify(rawRecord) + "\n";
2455
- const feedbackLogPath = join5(intelDir, "feedback-log.jsonl");
2798
+ const feedbackLogPath = join8(intelDir, "feedback-log.jsonl");
2456
2799
  if (options.dryRun) {
2457
2800
  console.log(`\x1B[36m[DRY-RUN] WOULD APPEND TO ${feedbackLogPath}:\x1B[0m`);
2458
2801
  console.log(recordLine.trim());
2459
2802
  } else {
2460
2803
  try {
2461
2804
  let isDuplicate = false;
2462
- if (existsSync5(feedbackLogPath)) {
2463
- const lines = readFileSync6(feedbackLogPath, "utf8").split("\n");
2805
+ if (existsSync8(feedbackLogPath)) {
2806
+ const lines = readFileSync9(feedbackLogPath, "utf8").split("\n");
2464
2807
  for (const line of lines) {
2465
2808
  if (!line.trim())
2466
2809
  continue;
@@ -2478,7 +2821,7 @@ function handleFeedbackAdd(options) {
2478
2821
  console.log(`\x1B[33mFeedback already exists. Skipping duplicate entry.\x1B[0m`);
2479
2822
  return;
2480
2823
  }
2481
- writeFileSync2(feedbackLogPath, recordLine, { flag: "a", encoding: "utf8" });
2824
+ writeFileSync4(feedbackLogPath, recordLine, { flag: "a", encoding: "utf8" });
2482
2825
  console.log(`\u2714 Feedback successfully added (ID: ${rawRecord.id})`);
2483
2826
  } catch (e) {
2484
2827
  console.error(`\x1B[31mError: Failed to write to feedback-log.jsonl: ${e.message}\x1B[0m`);
@@ -2487,13 +2830,13 @@ function handleFeedbackAdd(options) {
2487
2830
  }
2488
2831
  }
2489
2832
  function handleFeedbackList(options) {
2490
- const feedbackLogPath = join5(options.target, ".ai", "intelligence", "feedback-log.jsonl");
2491
- if (!existsSync5(feedbackLogPath)) {
2833
+ const feedbackLogPath = join8(options.target, ".ai", "intelligence", "feedback-log.jsonl");
2834
+ if (!existsSync8(feedbackLogPath)) {
2492
2835
  console.log("No feedback logged yet.");
2493
2836
  return;
2494
2837
  }
2495
2838
  try {
2496
- const content = readFileSync6(feedbackLogPath, "utf8");
2839
+ const content = readFileSync9(feedbackLogPath, "utf8");
2497
2840
  const lines = content.split("\n").filter((l) => l.trim() !== "");
2498
2841
  if (lines.length === 0) {
2499
2842
  console.log("No feedback logged yet.");
@@ -2525,14 +2868,14 @@ function handleFeedbackList(options) {
2525
2868
  }
2526
2869
  }
2527
2870
  function handleFeedbackSummarize(options) {
2528
- const intelDir = join5(options.target, ".ai", "intelligence");
2529
- const feedbackLogPath = join5(intelDir, "feedback-log.jsonl");
2530
- if (!existsSync5(feedbackLogPath)) {
2871
+ const intelDir = join8(options.target, ".ai", "intelligence");
2872
+ const feedbackLogPath = join8(intelDir, "feedback-log.jsonl");
2873
+ if (!existsSync8(feedbackLogPath)) {
2531
2874
  console.log("No feedback logs found to compile.");
2532
2875
  return;
2533
2876
  }
2534
2877
  try {
2535
- const content = readFileSync6(feedbackLogPath, "utf8");
2878
+ const content = readFileSync9(feedbackLogPath, "utf8");
2536
2879
  const lines = content.split("\n").filter((l) => l.trim() !== "");
2537
2880
  if (lines.length === 0) {
2538
2881
  console.log("No feedback logs found to compile.");
@@ -2577,12 +2920,12 @@ function handleFeedbackSummarize(options) {
2577
2920
  `;
2578
2921
  });
2579
2922
  });
2580
- const targetRulesPath = join5(intelDir, "learning-rules.md");
2923
+ const targetRulesPath = join8(intelDir, "learning-rules.md");
2581
2924
  if (options.dryRun) {
2582
2925
  console.log(`\x1B[36m[DRY-RUN] WOULD WRITE TO ${targetRulesPath}:\x1B[0m`);
2583
2926
  console.log(md);
2584
2927
  } else {
2585
- writeFileSync2(targetRulesPath, md, "utf8");
2928
+ writeFileSync4(targetRulesPath, md, "utf8");
2586
2929
  console.log(`\u2714 Compiled ${lines.length} feedback items into learning rules in .ai/intelligence/learning-rules.md`);
2587
2930
  }
2588
2931
  } catch (e) {
@@ -2591,9 +2934,9 @@ function handleFeedbackSummarize(options) {
2591
2934
  }
2592
2935
  }
2593
2936
  function handleImprovePropose(options) {
2594
- const proposalsDir = join5(options.target, ".ai", "proposals");
2595
- if (!options.dryRun && !existsSync5(proposalsDir)) {
2596
- mkdirSync(proposalsDir, { recursive: true });
2937
+ const proposalsDir = join8(options.target, ".ai", "proposals");
2938
+ if (!options.dryRun && !existsSync8(proposalsDir)) {
2939
+ mkdirSync3(proposalsDir, { recursive: true });
2597
2940
  }
2598
2941
  const now = /* @__PURE__ */ new Date();
2599
2942
  const pad = (n) => String(n).padStart(2, "0");
@@ -2609,15 +2952,15 @@ function handleImprovePropose(options) {
2609
2952
  let suggestedChange = "No code suggestions compiled.";
2610
2953
  let verifyCommand = "npm run verify";
2611
2954
  let rollbackPlan = "git checkout -- .";
2612
- const gitignorePath = join5(options.target, ".gitignore");
2613
- const agentsPath = join5(options.target, "AGENTS.md");
2614
- if (!existsSync5(gitignorePath)) {
2955
+ const gitignorePath = join8(options.target, ".gitignore");
2956
+ const agentsPath = join8(options.target, "AGENTS.md");
2957
+ if (!existsSync8(gitignorePath)) {
2615
2958
  problem = "Missing .gitignore file in target workspace. AI agents may scan large build directories and run out of token context.";
2616
2959
  evidence = `.gitignore file is not present at root directory: ${options.target}`;
2617
2960
  affectedFiles = [".gitignore"];
2618
2961
  suggestedChange = "Create a standard .gitignore file to exclude node_modules, build/ and dist/ directories.";
2619
2962
  rollbackPlan = "git clean -fd .gitignore";
2620
- } else if (!existsSync5(agentsPath)) {
2963
+ } else if (!existsSync8(agentsPath)) {
2621
2964
  problem = "Missing AGENTS.md document in target workspace. Models will lack stack-specific implementation blueprints.";
2622
2965
  evidence = `AGENTS.md file is not present at root directory: ${options.target}`;
2623
2966
  affectedFiles = ["AGENTS.md"];
@@ -2671,18 +3014,18 @@ ${suggestedChange}
2671
3014
  * **Rollback Command**: \`${rollbackPlan}\`
2672
3015
  * **Approval Status**: PENDING (Manual approval required before implementation)
2673
3016
  `;
2674
- const proposalFile = join5(proposalsDir, `${id}.md`);
3017
+ const proposalFile = join8(proposalsDir, `${id}.md`);
2675
3018
  if (options.dryRun) {
2676
3019
  console.log(`\x1B[36m[DRY-RUN] WOULD WRITE PROPOSAL TO ${proposalFile}:\x1B[0m`);
2677
3020
  console.log(md);
2678
3021
  } else {
2679
- writeFileSync2(proposalFile, md, "utf8");
3022
+ writeFileSync4(proposalFile, md, "utf8");
2680
3023
  console.log(`\u2714 Created codebase improvement proposal: .ai/proposals/${id}.md`);
2681
3024
  }
2682
3025
  }
2683
3026
  function handleImproveReview(options) {
2684
- const proposalsDir = join5(options.target, ".ai", "proposals");
2685
- if (!existsSync5(proposalsDir)) {
3027
+ const proposalsDir = join8(options.target, ".ai", "proposals");
3028
+ if (!existsSync8(proposalsDir)) {
2686
3029
  console.log("No improvement proposals found.");
2687
3030
  return;
2688
3031
  }
@@ -2696,8 +3039,8 @@ function handleImproveReview(options) {
2696
3039
  \u{1F4CB} \x1B[36mCodebase Improvement Proposals\x1B[0m`);
2697
3040
  console.log("==================================================");
2698
3041
  files.forEach((file) => {
2699
- const fullPath = join5(proposalsDir, file);
2700
- const content = readFileSync6(fullPath, "utf8");
3042
+ const fullPath = join8(proposalsDir, file);
3043
+ const content = readFileSync9(fullPath, "utf8");
2701
3044
  const fmMatch = content.match(/^---([\s\S]*?)---/);
2702
3045
  if (!fmMatch)
2703
3046
  return;
@@ -2720,8 +3063,8 @@ function handleImproveReview(options) {
2720
3063
  }
2721
3064
  }
2722
3065
  function handleImproveStatus(options) {
2723
- const proposalsDir = join5(options.target, ".ai", "proposals");
2724
- if (!existsSync5(proposalsDir)) {
3066
+ const proposalsDir = join8(options.target, ".ai", "proposals");
3067
+ if (!existsSync8(proposalsDir)) {
2725
3068
  console.log("Improvement Proposal Engine Status:");
2726
3069
  console.log(" Total Proposals: 0");
2727
3070
  console.log(" Pending Approval: 0");
@@ -2733,7 +3076,7 @@ function handleImproveStatus(options) {
2733
3076
  let approved = 0;
2734
3077
  let rejected = 0;
2735
3078
  files.forEach((file) => {
2736
- const content = readFileSync6(join5(proposalsDir, file), "utf8");
3079
+ const content = readFileSync9(join8(proposalsDir, file), "utf8");
2737
3080
  const fmMatch = content.match(/^---([\s\S]*?)---/);
2738
3081
  if (fmMatch) {
2739
3082
  const metadata = parseYaml(fmMatch[1]) || {};
@@ -2769,7 +3112,7 @@ function validatePath(targetRoot, relPath) {
2769
3112
  }
2770
3113
  const resolved = resolve3(targetRoot, relPath);
2771
3114
  const relativeFromRoot = relative(targetRoot, resolved);
2772
- if (relativeFromRoot.startsWith("..") || isAbsolute(relativeFromRoot) || resolved === targetRoot) {
3115
+ if (relativeFromRoot.startsWith("..") || isAbsolute2(relativeFromRoot) || resolved === targetRoot) {
2773
3116
  return { valid: false, reason: `Path '${relPath}' resolves outside the target root.`, type: "outside" };
2774
3117
  }
2775
3118
  const parts = relativeFromRoot.replace(/\\/g, "/").split("/");
@@ -2809,11 +3152,11 @@ function validateProposal(proposalFile, targetRoot) {
2809
3152
  permissions: { status: "skip" },
2810
3153
  constraints: { status: "skip" }
2811
3154
  };
2812
- if (!existsSync5(proposalFile)) {
3155
+ if (!existsSync8(proposalFile)) {
2813
3156
  gates.frontmatter = { status: "fail", reason: "missing frontmatter" };
2814
3157
  return { valid: false, reason: "missing frontmatter", gates };
2815
3158
  }
2816
- const content = readFileSync6(proposalFile, "utf8");
3159
+ const content = readFileSync9(proposalFile, "utf8");
2817
3160
  const fmMatch = content.match(/^---([\s\S]*?)---/);
2818
3161
  if (!fmMatch) {
2819
3162
  gates.frontmatter = { status: "fail", reason: "missing frontmatter" };
@@ -2924,7 +3267,7 @@ function validateProposal(proposalFile, targetRoot) {
2924
3267
  constraintsStatus = "fail";
2925
3268
  constraintsReason = `unsupported operation type`;
2926
3269
  }
2927
- } else if (existsSync5(resolvedPath) && !op.overwrite) {
3270
+ } else if (existsSync8(resolvedPath) && !op.overwrite) {
2928
3271
  if (constraintsStatus === "pass") {
2929
3272
  constraintsStatus = "fail";
2930
3273
  constraintsReason = `create_file target exists without overwrite`;
@@ -2943,13 +3286,13 @@ function validateProposal(proposalFile, targetRoot) {
2943
3286
  constraintsStatus = "fail";
2944
3287
  constraintsReason = `unsupported operation type`;
2945
3288
  }
2946
- } else if (!existsSync5(resolvedPath)) {
3289
+ } else if (!existsSync8(resolvedPath)) {
2947
3290
  if (constraintsStatus === "pass") {
2948
3291
  constraintsStatus = "fail";
2949
3292
  constraintsReason = `replace_text zero matches`;
2950
3293
  }
2951
3294
  } else {
2952
- const fileContent = readFileSync6(resolvedPath, "utf8");
3295
+ const fileContent = readFileSync9(resolvedPath, "utf8");
2953
3296
  let count = 0;
2954
3297
  let pos = fileContent.indexOf(op.find);
2955
3298
  while (pos !== -1) {
@@ -3118,7 +3461,7 @@ function handleImproveDiff(proposalFile, options) {
3118
3461
  console.log(`
3119
3462
  \x1B[33m[Operation #${idx + 1}] Target: ${op.path}\x1B[0m`);
3120
3463
  if (type === "create_file") {
3121
- const exists = existsSync5(op.resolvedPath);
3464
+ const exists = existsSync8(op.resolvedPath);
3122
3465
  if (exists) {
3123
3466
  console.log(` \x1B[31m\u26A0\uFE0F [Overwriting existing file]\x1B[0m`);
3124
3467
  } else {
@@ -3128,10 +3471,10 @@ function handleImproveDiff(proposalFile, options) {
3128
3471
  console.log(` + [File content: ${linesCount} line(s), overwrite: ${!!op.overwrite}]`);
3129
3472
  printTruncatedLines(op.content, " +", "\x1B[32m");
3130
3473
  } else if (type === "append_line") {
3131
- const exists = existsSync5(op.resolvedPath);
3474
+ const exists = existsSync8(op.resolvedPath);
3132
3475
  let currentFileContent = "";
3133
3476
  if (exists) {
3134
- currentFileContent = readFileSync6(op.resolvedPath, "utf8");
3477
+ currentFileContent = readFileSync9(op.resolvedPath, "utf8");
3135
3478
  }
3136
3479
  const fileLines = currentFileContent.split(/\r?\n/);
3137
3480
  const lineExists = fileLines.some((l) => l.trim() === op.line.trim());
@@ -3164,14 +3507,14 @@ function handleImproveApply(proposalFile, options) {
3164
3507
  if (!validation.valid) {
3165
3508
  console.error(`\x1B[31mValidation FAILED: ${validation.reason}\x1B[0m`);
3166
3509
  const applyId2 = `apply-${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:T.Z]/g, "").slice(0, 14)}`;
3167
- const logDir2 = join5(options.target, ".ai", "proposals");
3168
- if (!existsSync5(logDir2)) {
3510
+ const logDir2 = join8(options.target, ".ai", "proposals");
3511
+ if (!existsSync8(logDir2)) {
3169
3512
  try {
3170
- mkdirSync(logDir2, { recursive: true });
3513
+ mkdirSync3(logDir2, { recursive: true });
3171
3514
  } catch (e) {
3172
3515
  }
3173
3516
  }
3174
- const logFile2 = join5(logDir2, "apply-log.jsonl");
3517
+ const logFile2 = join8(logDir2, "apply-log.jsonl");
3175
3518
  const record2 = {
3176
3519
  id: applyId2,
3177
3520
  proposal_id: validation.proposalId || basename(proposalFile, ".md"),
@@ -3186,7 +3529,7 @@ function handleImproveApply(proposalFile, options) {
3186
3529
  notes: `Validation failed: ${validation.reason}`
3187
3530
  };
3188
3531
  try {
3189
- writeFileSync2(logFile2, JSON.stringify(record2) + "\n", { flag: "a", encoding: "utf8" });
3532
+ writeFileSync4(logFile2, JSON.stringify(record2) + "\n", { flag: "a", encoding: "utf8" });
3190
3533
  } catch (err) {
3191
3534
  }
3192
3535
  process.exit(1);
@@ -3214,8 +3557,8 @@ Applying changes...`);
3214
3557
  if (!filesChanged.includes(relPath)) {
3215
3558
  filesChanged.push(relPath);
3216
3559
  }
3217
- if (existsSync5(op.resolvedPath)) {
3218
- const fileContent = readFileSync6(op.resolvedPath, "utf8");
3560
+ if (existsSync8(op.resolvedPath)) {
3561
+ const fileContent = readFileSync9(op.resolvedPath, "utf8");
3219
3562
  beforeHashes[relPath] = getSha256(fileContent);
3220
3563
  } else {
3221
3564
  beforeHashes[relPath] = null;
@@ -3225,12 +3568,12 @@ Applying changes...`);
3225
3568
  const relPath = relative(options.target, op.resolvedPath).replace(/\\/g, "/");
3226
3569
  console.log(` Executing Operation #${idx + 1} (${op.type}) on '${relPath}'...`);
3227
3570
  if (op.type === "create_file") {
3228
- const dir = dirname2(op.resolvedPath);
3229
- if (!existsSync5(dir)) {
3230
- mkdirSync(dir, { recursive: true });
3571
+ const dir = dirname4(op.resolvedPath);
3572
+ if (!existsSync8(dir)) {
3573
+ mkdirSync3(dir, { recursive: true });
3231
3574
  }
3232
- const exists = existsSync5(op.resolvedPath);
3233
- writeFileSync2(op.resolvedPath, op.content, "utf8");
3575
+ const exists = existsSync8(op.resolvedPath);
3576
+ writeFileSync4(op.resolvedPath, op.content, "utf8");
3234
3577
  if (exists) {
3235
3578
  console.log(` [OVERWRITTEN] Overwrote existing file '${relPath}'.`);
3236
3579
  } else {
@@ -3238,8 +3581,8 @@ Applying changes...`);
3238
3581
  }
3239
3582
  } else if (op.type === "append_line") {
3240
3583
  let content = "";
3241
- if (existsSync5(op.resolvedPath)) {
3242
- content = readFileSync6(op.resolvedPath, "utf8");
3584
+ if (existsSync8(op.resolvedPath)) {
3585
+ content = readFileSync9(op.resolvedPath, "utf8");
3243
3586
  }
3244
3587
  const fileLines = content.split(/\r?\n/);
3245
3588
  const lineExists = fileLines.some((l) => l.trim() === op.line.trim());
@@ -3249,17 +3592,17 @@ Applying changes...`);
3249
3592
  newContent += "\n";
3250
3593
  }
3251
3594
  newContent += op.line + "\n";
3252
- const dir = dirname2(op.resolvedPath);
3253
- if (!existsSync5(dir)) {
3254
- mkdirSync(dir, { recursive: true });
3595
+ const dir = dirname4(op.resolvedPath);
3596
+ if (!existsSync8(dir)) {
3597
+ mkdirSync3(dir, { recursive: true });
3255
3598
  }
3256
- writeFileSync2(op.resolvedPath, newContent, "utf8");
3599
+ writeFileSync4(op.resolvedPath, newContent, "utf8");
3257
3600
  console.log(` [APPENDED] Appended 1 line to '${relPath}'.`);
3258
3601
  } else {
3259
3602
  console.log(` [IDEMPOTENT] Line already exists in '${relPath}'. Skipping append.`);
3260
3603
  }
3261
3604
  } else if (op.type === "replace_text") {
3262
- const fileContent = readFileSync6(op.resolvedPath, "utf8");
3605
+ const fileContent = readFileSync9(op.resolvedPath, "utf8");
3263
3606
  let count = 0;
3264
3607
  let pos = fileContent.indexOf(op.find);
3265
3608
  while (pos !== -1) {
@@ -3274,14 +3617,14 @@ Applying changes...`);
3274
3617
  if (count > 0)
3275
3618
  count = 1;
3276
3619
  }
3277
- writeFileSync2(op.resolvedPath, newContent, "utf8");
3620
+ writeFileSync4(op.resolvedPath, newContent, "utf8");
3278
3621
  console.log(` [REPLACED] Replaced ${count} occurrence(s) of find text in '${relPath}'.`);
3279
3622
  }
3280
3623
  });
3281
3624
  filesChanged.forEach((relPath) => {
3282
3625
  const fullPath = resolve3(options.target, relPath);
3283
- if (existsSync5(fullPath)) {
3284
- const fileContent = readFileSync6(fullPath, "utf8");
3626
+ if (existsSync8(fullPath)) {
3627
+ const fileContent = readFileSync9(fullPath, "utf8");
3285
3628
  afterHashes[relPath] = getSha256(fileContent);
3286
3629
  } else {
3287
3630
  afterHashes[relPath] = null;
@@ -3293,11 +3636,11 @@ Applying changes...`);
3293
3636
  notes = `Execution error: ${e.message}`;
3294
3637
  console.error(`\x1B[31mError applying proposal: ${e.message}\x1B[0m`);
3295
3638
  }
3296
- const logDir = join5(options.target, ".ai", "proposals");
3297
- if (!existsSync5(logDir)) {
3298
- mkdirSync(logDir, { recursive: true });
3639
+ const logDir = join8(options.target, ".ai", "proposals");
3640
+ if (!existsSync8(logDir)) {
3641
+ mkdirSync3(logDir, { recursive: true });
3299
3642
  }
3300
- const logFile = join5(logDir, "apply-log.jsonl");
3643
+ const logFile = join8(logDir, "apply-log.jsonl");
3301
3644
  const record = {
3302
3645
  id: applyId,
3303
3646
  proposal_id: proposalId,
@@ -3312,7 +3655,7 @@ Applying changes...`);
3312
3655
  notes
3313
3656
  };
3314
3657
  try {
3315
- writeFileSync2(logFile, JSON.stringify(record) + "\n", { flag: "a", encoding: "utf8" });
3658
+ writeFileSync4(logFile, JSON.stringify(record) + "\n", { flag: "a", encoding: "utf8" });
3316
3659
  } catch (err) {
3317
3660
  console.error(`\x1B[31mFailed to write to audit log: ${err.message}\x1B[0m`);
3318
3661
  }
@@ -3327,13 +3670,13 @@ Applying changes...`);
3327
3670
  }
3328
3671
  }
3329
3672
  function handleImproveLog(options) {
3330
- const logFile = join5(options.target, ".ai", "proposals", "apply-log.jsonl");
3331
- if (!existsSync5(logFile)) {
3673
+ const logFile = join8(options.target, ".ai", "proposals", "apply-log.jsonl");
3674
+ if (!existsSync8(logFile)) {
3332
3675
  console.log("No apply log found.");
3333
3676
  return;
3334
3677
  }
3335
3678
  try {
3336
- const lines = readFileSync6(logFile, "utf8").trim().split(/\r?\n/);
3679
+ const lines = readFileSync9(logFile, "utf8").trim().split(/\r?\n/);
3337
3680
  console.log(`
3338
3681
  \u{1F4DC} \x1B[36mApplied Proposals Audit Log\x1B[0m`);
3339
3682
  console.log("==================================================");
@@ -3363,9 +3706,9 @@ function handleStatus(options) {
3363
3706
  let pkgName = "unknown";
3364
3707
  let pkgVersion2 = "unknown";
3365
3708
  try {
3366
- const pkgPath = join5(options.target, "package.json");
3367
- if (existsSync5(pkgPath)) {
3368
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
3709
+ const pkgPath = join8(options.target, "package.json");
3710
+ if (existsSync8(pkgPath)) {
3711
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
3369
3712
  pkgName = pkg.name || pkgName;
3370
3713
  pkgVersion2 = pkg.version || pkgVersion2;
3371
3714
  }
@@ -3380,12 +3723,12 @@ function handleStatus(options) {
3380
3723
  console.log(` \x1B[33mFramework & Dependency Signals:\x1B[0m`);
3381
3724
  console.log(` Frameworks: ${frameworkSignals.join(", ") || "None"}`);
3382
3725
  console.log(` Dependencies: ${dependencySignals.join(", ") || "None"}`);
3383
- const memoryHashPath = join5(options.target, ".ai", "intelligence", "memory.hash.json");
3726
+ const memoryHashPath = join8(options.target, ".ai", "intelligence", "memory.hash.json");
3384
3727
  let memoryStatus = "\x1B[31mMISSING\x1B[0m";
3385
3728
  let lastBuildTime = "N/A";
3386
- if (existsSync5(memoryHashPath)) {
3729
+ if (existsSync8(memoryHashPath)) {
3387
3730
  try {
3388
- const memObj = JSON.parse(readFileSync6(memoryHashPath, "utf8"));
3731
+ const memObj = JSON.parse(readFileSync9(memoryHashPath, "utf8"));
3389
3732
  lastBuildTime = memObj.generated_at || "N/A";
3390
3733
  const diff = diffMemory(options.target);
3391
3734
  if (diff) {
@@ -3402,30 +3745,30 @@ function handleStatus(options) {
3402
3745
  console.log(` \x1B[33mMemory State:\x1B[0m`);
3403
3746
  console.log(` Status: ${memoryStatus}`);
3404
3747
  console.log(` Last Built: ${lastBuildTime}`);
3405
- const feedbackPath = join5(options.target, ".ai", "intelligence", "feedback-log.jsonl");
3748
+ const feedbackPath = join8(options.target, ".ai", "intelligence", "feedback-log.jsonl");
3406
3749
  let feedbackCount = 0;
3407
- if (existsSync5(feedbackPath)) {
3750
+ if (existsSync8(feedbackPath)) {
3408
3751
  try {
3409
- feedbackCount = readFileSync6(feedbackPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
3752
+ feedbackCount = readFileSync9(feedbackPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
3410
3753
  } catch (e) {
3411
3754
  }
3412
3755
  }
3413
- const rulesPath = join5(options.target, ".ai", "intelligence", "learning-rules.md");
3414
- const rulesStatus = existsSync5(rulesPath) ? "\x1B[32mPRESENT\x1B[0m" : "\x1B[31mMISSING\x1B[0m";
3756
+ const rulesPath = join8(options.target, ".ai", "intelligence", "learning-rules.md");
3757
+ const rulesStatus = existsSync8(rulesPath) ? "\x1B[32mPRESENT\x1B[0m" : "\x1B[31mMISSING\x1B[0m";
3415
3758
  console.log(` \x1B[33mFeedback Loop & Rules:\x1B[0m`);
3416
3759
  console.log(` Feedback Count: ${feedbackCount}`);
3417
3760
  console.log(` Learning Rules: ${rulesStatus}`);
3418
- const proposalsDir = join5(options.target, ".ai", "proposals");
3761
+ const proposalsDir = join8(options.target, ".ai", "proposals");
3419
3762
  let pendingCount = 0;
3420
3763
  let approvedCount = 0;
3421
3764
  let rejectedCount = 0;
3422
3765
  let totalProposals = 0;
3423
- if (existsSync5(proposalsDir)) {
3766
+ if (existsSync8(proposalsDir)) {
3424
3767
  try {
3425
3768
  const propFiles = readdirSync(proposalsDir).filter((f) => f.startsWith("proposal-") && f.endsWith(".md"));
3426
3769
  totalProposals = propFiles.length;
3427
3770
  propFiles.forEach((file) => {
3428
- const content = readFileSync6(join5(proposalsDir, file), "utf8");
3771
+ const content = readFileSync9(join8(proposalsDir, file), "utf8");
3429
3772
  const fmMatch = content.match(/^---([\s\S]*?)---/);
3430
3773
  if (fmMatch) {
3431
3774
  const metadata = parseYaml(fmMatch[1]) || {};
@@ -3446,26 +3789,26 @@ function handleStatus(options) {
3446
3789
  console.log(` Pending: \x1B[33m${pendingCount}\x1B[0m`);
3447
3790
  console.log(` Approved: \x1B[32m${approvedCount}\x1B[0m`);
3448
3791
  console.log(` Rejected: \x1B[31m${rejectedCount}\x1B[0m`);
3449
- const applyLogPath = join5(options.target, ".ai", "proposals", "apply-log.jsonl");
3792
+ const applyLogPath = join8(options.target, ".ai", "proposals", "apply-log.jsonl");
3450
3793
  let applyLogCount = 0;
3451
- if (existsSync5(applyLogPath)) {
3794
+ if (existsSync8(applyLogPath)) {
3452
3795
  try {
3453
- applyLogCount = readFileSync6(applyLogPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
3796
+ applyLogCount = readFileSync9(applyLogPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
3454
3797
  } catch (e) {
3455
3798
  }
3456
3799
  }
3457
3800
  console.log(` \x1B[33mApply Audit Log:\x1B[0m`);
3458
3801
  console.log(` Apply Count: ${applyLogCount}`);
3459
3802
  let nextMove = "mmdo status";
3460
- if (!existsSync5(join5(options.target, ".ai", "config.yaml"))) {
3803
+ if (!existsSync8(join8(options.target, ".ai", "config.yaml"))) {
3461
3804
  nextMove = "\x1B[36mnpx multimodel-dev-os init\x1B[0m (initialize MultiModel Dev OS first)";
3462
- } else if (!existsSync5(memoryHashPath)) {
3805
+ } else if (!existsSync8(memoryHashPath)) {
3463
3806
  nextMove = "\x1B[36mnpx multimodel-dev-os memory build\x1B[0m (initialize memory index)";
3464
3807
  } else {
3465
3808
  const diff = diffMemory(options.target);
3466
3809
  if (diff && (diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0)) {
3467
3810
  nextMove = "\x1B[36mnpx multimodel-dev-os memory refresh\x1B[0m (update memory with changes)";
3468
- } else if (feedbackCount > 0 && !existsSync5(rulesPath)) {
3811
+ } else if (feedbackCount > 0 && !existsSync8(rulesPath)) {
3469
3812
  nextMove = "\x1B[36mnpx multimodel-dev-os feedback summarize\x1B[0m (compile feedback into learning rules)";
3470
3813
  } else if (pendingCount > 0) {
3471
3814
  nextMove = "\x1B[36mnpx multimodel-dev-os improve review\x1B[0m (review pending proposals)";
@@ -3479,11 +3822,11 @@ function handleStatus(options) {
3479
3822
  `);
3480
3823
  }
3481
3824
  function getWorkflowsPath(target) {
3482
- let workflowsPath = join5(target, ".ai", "registries", "workflows.yaml");
3825
+ let workflowsPath = join8(target, ".ai", "registries", "workflows.yaml");
3483
3826
  let usingFallback = false;
3484
- if (!existsSync5(workflowsPath)) {
3485
- const fallbackPath = join5(sourceRoot, ".ai", "registries", "workflows.yaml");
3486
- if (existsSync5(fallbackPath)) {
3827
+ if (!existsSync8(workflowsPath)) {
3828
+ const fallbackPath = join8(sourceRoot, ".ai", "registries", "workflows.yaml");
3829
+ if (existsSync8(fallbackPath)) {
3487
3830
  workflowsPath = fallbackPath;
3488
3831
  usingFallback = true;
3489
3832
  }
@@ -3492,7 +3835,7 @@ function getWorkflowsPath(target) {
3492
3835
  }
3493
3836
  function handleWorkflowList(options) {
3494
3837
  const { workflowsPath, usingFallback } = getWorkflowsPath(options.target);
3495
- if (!existsSync5(workflowsPath)) {
3838
+ if (!existsSync8(workflowsPath)) {
3496
3839
  console.log("No workflows registry found.");
3497
3840
  return;
3498
3841
  }
@@ -3500,7 +3843,7 @@ function handleWorkflowList(options) {
3500
3843
  console.log("\x1B[33mNotice: Local workflows registry not found. Using bundled workflows registry fallback.\x1B[0m");
3501
3844
  }
3502
3845
  try {
3503
- const registry = parseYaml(readFileSync6(workflowsPath, "utf8")) || {};
3846
+ const registry = parseYaml(readFileSync9(workflowsPath, "utf8")) || {};
3504
3847
  const workflows = registry.workflows || {};
3505
3848
  console.log(`
3506
3849
  \u2699 \x1B[36mRegistered Workflows\x1B[0m`);
@@ -3522,7 +3865,7 @@ function handleWorkflowList(options) {
3522
3865
  }
3523
3866
  function handleWorkflowShow(wName, options) {
3524
3867
  const { workflowsPath, usingFallback } = getWorkflowsPath(options.target);
3525
- if (!existsSync5(workflowsPath)) {
3868
+ if (!existsSync8(workflowsPath)) {
3526
3869
  console.log("No workflows registry found.");
3527
3870
  return;
3528
3871
  }
@@ -3530,7 +3873,7 @@ function handleWorkflowShow(wName, options) {
3530
3873
  console.log("\x1B[33mNotice: Local workflows registry not found. Using bundled workflows registry fallback.\x1B[0m");
3531
3874
  }
3532
3875
  try {
3533
- const registry = parseYaml(readFileSync6(workflowsPath, "utf8")) || {};
3876
+ const registry = parseYaml(readFileSync9(workflowsPath, "utf8")) || {};
3534
3877
  const workflows = registry.workflows || {};
3535
3878
  const wf = workflows[wName];
3536
3879
  if (!wf) {
@@ -3563,7 +3906,7 @@ function handleWorkflowShow(wName, options) {
3563
3906
  }
3564
3907
  function handleWorkflowPlan(wName, options) {
3565
3908
  const { workflowsPath, usingFallback } = getWorkflowsPath(options.target);
3566
- if (!existsSync5(workflowsPath)) {
3909
+ if (!existsSync8(workflowsPath)) {
3567
3910
  console.log("No workflows registry found.");
3568
3911
  return;
3569
3912
  }
@@ -3571,7 +3914,7 @@ function handleWorkflowPlan(wName, options) {
3571
3914
  console.log("\x1B[33mNotice: Local workflows registry not found. Using bundled workflows registry fallback.\x1B[0m");
3572
3915
  }
3573
3916
  try {
3574
- const registry = parseYaml(readFileSync6(workflowsPath, "utf8")) || {};
3917
+ const registry = parseYaml(readFileSync9(workflowsPath, "utf8")) || {};
3575
3918
  const workflows = registry.workflows || {};
3576
3919
  const wf = workflows[wName];
3577
3920
  if (!wf) {
@@ -3598,7 +3941,7 @@ function handleWorkflowPlan(wName, options) {
3598
3941
  }
3599
3942
  function handleWorkflowRun(wName, options) {
3600
3943
  const { workflowsPath, usingFallback } = getWorkflowsPath(options.target);
3601
- if (!existsSync5(workflowsPath)) {
3944
+ if (!existsSync8(workflowsPath)) {
3602
3945
  console.log("No workflows registry found.");
3603
3946
  return;
3604
3947
  }
@@ -3606,7 +3949,7 @@ function handleWorkflowRun(wName, options) {
3606
3949
  console.log("\x1B[33mNotice: Local workflows registry not found. Using bundled workflows registry fallback.\x1B[0m");
3607
3950
  }
3608
3951
  try {
3609
- const registry = parseYaml(readFileSync6(workflowsPath, "utf8")) || {};
3952
+ const registry = parseYaml(readFileSync9(workflowsPath, "utf8")) || {};
3610
3953
  const workflows = registry.workflows || {};
3611
3954
  const wf = workflows[wName];
3612
3955
  if (!wf) {
@@ -3658,17 +4001,17 @@ function handleWorkflowRun(wName, options) {
3658
4001
  }
3659
4002
  }
3660
4003
  function handleHandoffBuild(options) {
3661
- const intelDir = join5(options.target, ".ai", "intelligence");
3662
- if (!existsSync5(intelDir)) {
3663
- mkdirSync(intelDir, { recursive: true });
4004
+ const intelDir = join8(options.target, ".ai", "intelligence");
4005
+ if (!existsSync8(intelDir)) {
4006
+ mkdirSync3(intelDir, { recursive: true });
3664
4007
  }
3665
- const handoffPath = join5(intelDir, "handoff.md");
4008
+ const handoffPath = join8(intelDir, "handoff.md");
3666
4009
  let pkgName = "unknown";
3667
4010
  let pkgVersion2 = "unknown";
3668
4011
  try {
3669
- const pkgPath = join5(options.target, "package.json");
3670
- if (existsSync5(pkgPath)) {
3671
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
4012
+ const pkgPath = join8(options.target, "package.json");
4013
+ if (existsSync8(pkgPath)) {
4014
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
3672
4015
  pkgName = pkg.name || pkgName;
3673
4016
  pkgVersion2 = pkg.version || pkgVersion2;
3674
4017
  }
@@ -3677,12 +4020,12 @@ function handleHandoffBuild(options) {
3677
4020
  const { files } = scanTarget(options.target);
3678
4021
  const frameworkSignals = detectFrameworkSignals(files, options.target);
3679
4022
  const dependencySignals = detectDependencySignals(files, options.target);
3680
- const memoryHashPath = join5(intelDir, "memory.hash.json");
4023
+ const memoryHashPath = join8(intelDir, "memory.hash.json");
3681
4024
  let memoryStatus = "MISSING";
3682
4025
  let memoryTime = "N/A";
3683
- if (existsSync5(memoryHashPath)) {
4026
+ if (existsSync8(memoryHashPath)) {
3684
4027
  try {
3685
- const memObj = JSON.parse(readFileSync6(memoryHashPath, "utf8"));
4028
+ const memObj = JSON.parse(readFileSync9(memoryHashPath, "utf8"));
3686
4029
  memoryTime = memObj.generated_at || "N/A";
3687
4030
  const diff = diffMemory(options.target);
3688
4031
  if (diff) {
@@ -3692,25 +4035,25 @@ function handleHandoffBuild(options) {
3692
4035
  memoryStatus = "CORRUPT";
3693
4036
  }
3694
4037
  }
3695
- const feedbackPath = join5(intelDir, "feedback-log.jsonl");
4038
+ const feedbackPath = join8(intelDir, "feedback-log.jsonl");
3696
4039
  let feedbackCount = 0;
3697
- if (existsSync5(feedbackPath)) {
4040
+ if (existsSync8(feedbackPath)) {
3698
4041
  try {
3699
- feedbackCount = readFileSync6(feedbackPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
4042
+ feedbackCount = readFileSync9(feedbackPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "").length;
3700
4043
  } catch (e) {
3701
4044
  }
3702
4045
  }
3703
- const rulesPath = join5(intelDir, "learning-rules.md");
3704
- const rulesStatus = existsSync5(rulesPath) ? "PRESENT" : "MISSING";
3705
- const proposalsDir = join5(options.target, ".ai", "proposals");
4046
+ const rulesPath = join8(intelDir, "learning-rules.md");
4047
+ const rulesStatus = existsSync8(rulesPath) ? "PRESENT" : "MISSING";
4048
+ const proposalsDir = join8(options.target, ".ai", "proposals");
3706
4049
  let pendingCount = 0;
3707
4050
  let approvedCount = 0;
3708
4051
  let rejectedCount = 0;
3709
- if (existsSync5(proposalsDir)) {
4052
+ if (existsSync8(proposalsDir)) {
3710
4053
  try {
3711
4054
  const propFiles = readdirSync(proposalsDir).filter((f) => f.startsWith("proposal-") && f.endsWith(".md"));
3712
4055
  propFiles.forEach((file) => {
3713
- const content = readFileSync6(join5(proposalsDir, file), "utf8");
4056
+ const content = readFileSync9(join8(proposalsDir, file), "utf8");
3714
4057
  const fmMatch = content.match(/^---([\s\S]*?)---/);
3715
4058
  if (fmMatch) {
3716
4059
  const metadata = parseYaml(fmMatch[1]) || {};
@@ -3726,12 +4069,12 @@ function handleHandoffBuild(options) {
3726
4069
  } catch (e) {
3727
4070
  }
3728
4071
  }
3729
- const applyLogPath = join5(proposalsDir, "apply-log.jsonl");
4072
+ const applyLogPath = join8(proposalsDir, "apply-log.jsonl");
3730
4073
  let applyLogCount = 0;
3731
4074
  let lastApplyId = "None";
3732
- if (existsSync5(applyLogPath)) {
4075
+ if (existsSync8(applyLogPath)) {
3733
4076
  try {
3734
- const lines = readFileSync6(applyLogPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "");
4077
+ const lines = readFileSync9(applyLogPath, "utf8").trim().split(/\r?\n/).filter((l) => l.trim() !== "");
3735
4078
  applyLogCount = lines.length;
3736
4079
  if (applyLogCount > 0) {
3737
4080
  const lastRecord = JSON.parse(lines[lines.length - 1]);
@@ -3741,9 +4084,9 @@ function handleHandoffBuild(options) {
3741
4084
  }
3742
4085
  }
3743
4086
  let rulesSummary = "No learning rules defined yet.";
3744
- if (existsSync5(rulesPath)) {
4087
+ if (existsSync8(rulesPath)) {
3745
4088
  try {
3746
- const rulesContent = readFileSync6(rulesPath, "utf8");
4089
+ const rulesContent = readFileSync9(rulesPath, "utf8");
3747
4090
  const lines = rulesContent.split(/\r?\n/);
3748
4091
  const summaryLines = [];
3749
4092
  for (const line of lines) {
@@ -3760,7 +4103,7 @@ function handleHandoffBuild(options) {
3760
4103
  }
3761
4104
  }
3762
4105
  let recs = "1. Run `npx multimodel-dev-os workflow run repo-health` to check the directory hygiene.\n2. Review pending proposals if any exist.";
3763
- if (!existsSync5(join5(options.target, ".ai", "config.yaml"))) {
4106
+ if (!existsSync8(join8(options.target, ".ai", "config.yaml"))) {
3764
4107
  recs = "1. Run `npx multimodel-dev-os init` to bootstrap MultiModel Dev OS.\n2. Run `npx multimodel-dev-os memory build` to initialize codebase memory.";
3765
4108
  } else if (memoryStatus === "MISSING") {
3766
4109
  recs = "1. Run `npx multimodel-dev-os memory build` to initialize codebase index.\n2. Verify package safety boundaries.";
@@ -3798,7 +4141,7 @@ ${rulesSummary}
3798
4141
  ${recs}
3799
4142
  `;
3800
4143
  try {
3801
- writeFileSync2(handoffPath, handoffContent, "utf8");
4144
+ writeFileSync4(handoffPath, handoffContent, "utf8");
3802
4145
  console.log(`
3803
4146
  \u2714 Handoff context built successfully in: .ai/intelligence/handoff.md`);
3804
4147
  } catch (e) {
@@ -3806,13 +4149,13 @@ ${recs}
3806
4149
  }
3807
4150
  }
3808
4151
  function handleHandoffShow(options) {
3809
- const handoffPath = join5(options.target, ".ai", "intelligence", "handoff.md");
3810
- if (!existsSync5(handoffPath)) {
4152
+ const handoffPath = join8(options.target, ".ai", "intelligence", "handoff.md");
4153
+ if (!existsSync8(handoffPath)) {
3811
4154
  console.log("No compiled handoff file exists. Building first...");
3812
4155
  handleHandoffBuild(options);
3813
4156
  }
3814
4157
  try {
3815
- const content = readFileSync6(handoffPath, "utf8");
4158
+ const content = readFileSync9(handoffPath, "utf8");
3816
4159
  console.log("\n" + content);
3817
4160
  } catch (e) {
3818
4161
  console.error(`\x1B[31mError reading handoff: ${e.message}\x1B[0m`);
@@ -3827,8 +4170,8 @@ function handleDoctorIntelligence(options) {
3827
4170
  console.warn(` \x1B[33m[WARNING]\x1B[0m ${msg}`);
3828
4171
  warnings++;
3829
4172
  };
3830
- const memoryHashPath = join5(options.target, ".ai", "intelligence", "memory.hash.json");
3831
- if (!existsSync5(memoryHashPath)) {
4173
+ const memoryHashPath = join8(options.target, ".ai", "intelligence", "memory.hash.json");
4174
+ if (!existsSync8(memoryHashPath)) {
3832
4175
  warn("Memory hash index (.ai/intelligence/memory.hash.json) is MISSING. Run `memory build` first.");
3833
4176
  } else {
3834
4177
  try {
@@ -3842,23 +4185,23 @@ function handleDoctorIntelligence(options) {
3842
4185
  warn("Failed to diff memory index.");
3843
4186
  }
3844
4187
  }
3845
- const feedbackPath = join5(options.target, ".ai", "intelligence", "feedback-log.jsonl");
3846
- if (!existsSync5(feedbackPath)) {
4188
+ const feedbackPath = join8(options.target, ".ai", "intelligence", "feedback-log.jsonl");
4189
+ if (!existsSync8(feedbackPath)) {
3847
4190
  warn("Feedback log (.ai/intelligence/feedback-log.jsonl) is MISSING.");
3848
4191
  }
3849
- const rulesPath = join5(options.target, ".ai", "intelligence", "learning-rules.md");
3850
- if (!existsSync5(rulesPath)) {
4192
+ const rulesPath = join8(options.target, ".ai", "intelligence", "learning-rules.md");
4193
+ if (!existsSync8(rulesPath)) {
3851
4194
  warn("Learning rules (.ai/intelligence/learning-rules.md) are MISSING. Run `feedback summarize` to compile logs.");
3852
4195
  }
3853
- const proposalsDir = join5(options.target, ".ai", "proposals");
3854
- if (!existsSync5(proposalsDir)) {
4196
+ const proposalsDir = join8(options.target, ".ai", "proposals");
4197
+ if (!existsSync8(proposalsDir)) {
3855
4198
  warn("Proposals directory (.ai/proposals) is MISSING.");
3856
4199
  } else {
3857
4200
  try {
3858
4201
  const files = readdirSync(proposalsDir).filter((f) => f.startsWith("proposal-") && f.endsWith(".md"));
3859
4202
  let pending = 0;
3860
4203
  files.forEach((file) => {
3861
- const content = readFileSync6(join5(proposalsDir, file), "utf8");
4204
+ const content = readFileSync9(join8(proposalsDir, file), "utf8");
3862
4205
  const fmMatch = content.match(/^---([\s\S]*?)---/);
3863
4206
  if (fmMatch) {
3864
4207
  const metadata = parseYaml(fmMatch[1]) || {};
@@ -3873,13 +4216,13 @@ function handleDoctorIntelligence(options) {
3873
4216
  } catch (e) {
3874
4217
  }
3875
4218
  }
3876
- const applyLogPath = join5(options.target, ".ai", "proposals", "apply-log.jsonl");
3877
- if (!existsSync5(applyLogPath)) {
4219
+ const applyLogPath = join8(options.target, ".ai", "proposals", "apply-log.jsonl");
4220
+ if (!existsSync8(applyLogPath)) {
3878
4221
  warn("Apply audit log (.ai/proposals/apply-log.jsonl) is MISSING.");
3879
4222
  }
3880
- const gitignorePath = join5(options.target, ".gitignore");
3881
- if (existsSync5(gitignorePath)) {
3882
- const gitignoreContent = readFileSync6(gitignorePath, "utf8");
4223
+ const gitignorePath = join8(options.target, ".gitignore");
4224
+ if (existsSync8(gitignorePath)) {
4225
+ const gitignoreContent = readFileSync9(gitignorePath, "utf8");
3883
4226
  const checkIgnore = (pattern) => {
3884
4227
  if (!gitignoreContent.includes(pattern)) {
3885
4228
  warn(`.gitignore is missing rules ignoring: ${pattern}`);
@@ -3893,9 +4236,9 @@ function handleDoctorIntelligence(options) {
3893
4236
  } else {
3894
4237
  warn(".gitignore file is missing in target root.");
3895
4238
  }
3896
- if (existsSync5(memoryHashPath)) {
4239
+ if (existsSync8(memoryHashPath)) {
3897
4240
  try {
3898
- const memObj = JSON.parse(readFileSync6(memoryHashPath, "utf8"));
4241
+ const memObj = JSON.parse(readFileSync9(memoryHashPath, "utf8"));
3899
4242
  const fingerprints = memObj.file_fingerprints || {};
3900
4243
  Object.keys(fingerprints).forEach((file) => {
3901
4244
  const name = file.toLowerCase();
@@ -3955,7 +4298,7 @@ function getAnalysis(target) {
3955
4298
  repoType = "docs";
3956
4299
  } else if (files.some((f) => f.relPath === "package.json")) {
3957
4300
  try {
3958
- const pkg = JSON.parse(readFileSync6(join5(target, "package.json"), "utf8"));
4301
+ const pkg = JSON.parse(readFileSync9(join8(target, "package.json"), "utf8"));
3959
4302
  if (pkg.main && (pkg.main.includes("dist/") || pkg.main.includes("lib/"))) {
3960
4303
  repoType = "library";
3961
4304
  }
@@ -3976,7 +4319,7 @@ function getAnalysis(target) {
3976
4319
  const packageScripts = [];
3977
4320
  if (files.some((f) => f.relPath === "package.json")) {
3978
4321
  try {
3979
- const pkg = JSON.parse(readFileSync6(join5(target, "package.json"), "utf8"));
4322
+ const pkg = JSON.parse(readFileSync9(join8(target, "package.json"), "utf8"));
3980
4323
  if (pkg.scripts) {
3981
4324
  Object.keys(pkg.scripts).forEach((k) => packageScripts.push(k));
3982
4325
  }
@@ -3984,8 +4327,8 @@ function getAnalysis(target) {
3984
4327
  }
3985
4328
  }
3986
4329
  const githubWorkflows = [];
3987
- const githubDir = join5(target, ".github", "workflows");
3988
- if (existsSync5(githubDir)) {
4330
+ const githubDir = join8(target, ".github", "workflows");
4331
+ if (existsSync8(githubDir)) {
3989
4332
  try {
3990
4333
  readdirSync(githubDir).forEach((f) => {
3991
4334
  if (f.endsWith(".yml") || f.endsWith(".yaml"))
@@ -4093,8 +4436,8 @@ function handleOnboardPlan(options) {
4093
4436
  console.log("==================================================");
4094
4437
  const analysis = getAnalysis(options.target);
4095
4438
  const rec = getRecommendation(analysis);
4096
- const planPath = join5(options.target, ".ai", "intelligence", "onboarding.plan.json");
4097
- const reportPath = join5(options.target, ".ai", "intelligence", "onboarding.report.md");
4439
+ const planPath = join8(options.target, ".ai", "intelligence", "onboarding.plan.json");
4440
+ const reportPath = join8(options.target, ".ai", "intelligence", "onboarding.report.md");
4098
4441
  const plannedFiles = [
4099
4442
  { action: "CREATE", path: "AGENTS.md", source_template: `examples/${rec.template}/AGENTS.md` },
4100
4443
  { action: "CREATE", path: "MEMORY.md", source_template: `examples/${rec.template}/MEMORY.md` },
@@ -4170,13 +4513,13 @@ function handleOnboardPlan(options) {
4170
4513
  reportMd += `\`\`\`
4171
4514
  `;
4172
4515
  try {
4173
- const intelDir = join5(options.target, ".ai", "intelligence");
4174
- if (!options.dryRun && !existsSync5(intelDir)) {
4175
- mkdirSync(intelDir, { recursive: true });
4516
+ const intelDir = join8(options.target, ".ai", "intelligence");
4517
+ if (!options.dryRun && !existsSync8(intelDir)) {
4518
+ mkdirSync3(intelDir, { recursive: true });
4176
4519
  }
4177
4520
  if (!options.dryRun) {
4178
- writeFileSync2(planPath, JSON.stringify(planData, null, 2), "utf8");
4179
- writeFileSync2(reportPath, reportMd, "utf8");
4521
+ writeFileSync4(planPath, JSON.stringify(planData, null, 2), "utf8");
4522
+ writeFileSync4(reportPath, reportMd, "utf8");
4180
4523
  }
4181
4524
  console.log(` [SUCCESS] Onboarding plan generated:`);
4182
4525
  console.log(` - Plan JSON: .ai/intelligence/onboarding.plan.json`);
@@ -4194,14 +4537,14 @@ function handleOnboardApply(options) {
4194
4537
  console.log("Example: node bin/multimodel-dev-os.js onboard apply --approved");
4195
4538
  process.exit(1);
4196
4539
  }
4197
- const planPath = join5(options.target, ".ai", "intelligence", "onboarding.plan.json");
4198
- if (!existsSync5(planPath)) {
4540
+ const planPath = join8(options.target, ".ai", "intelligence", "onboarding.plan.json");
4541
+ if (!existsSync8(planPath)) {
4199
4542
  console.error('\x1B[31mError: Onboarding plan not found. Run "npx multimodel-dev-os onboard plan" first.\x1B[0m');
4200
4543
  process.exit(1);
4201
4544
  }
4202
4545
  let plan;
4203
4546
  try {
4204
- plan = JSON.parse(readFileSync6(planPath, "utf8"));
4547
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
4205
4548
  } catch (e) {
4206
4549
  console.error(`\x1B[31mError reading plan JSON: ${e.message}\x1B[0m`);
4207
4550
  process.exit(1);
@@ -4215,23 +4558,23 @@ function handleOnboardApply(options) {
4215
4558
  plan.planned_files.forEach((f) => {
4216
4559
  let srcFile;
4217
4560
  if (f.source_template === "RUNBOOK.md") {
4218
- srcFile = join5(sourceRoot, "RUNBOOK.md");
4561
+ srcFile = join8(sourceRoot, "RUNBOOK.md");
4219
4562
  } else {
4220
- srcFile = join5(sourceRoot, f.source_template);
4563
+ srcFile = join8(sourceRoot, f.source_template);
4221
4564
  }
4222
4565
  operations.push({ dest: f.path, src: srcFile });
4223
4566
  });
4224
- const templateDir = join5(sourceRoot, "examples", template);
4225
- const templateAiDir = join5(templateDir, ".ai");
4226
- if (existsSync5(templateAiDir) && !options.caveman) {
4567
+ const templateDir = join8(sourceRoot, "examples", template);
4568
+ const templateAiDir = join8(templateDir, ".ai");
4569
+ if (existsSync8(templateAiDir) && !options.caveman) {
4227
4570
  const subdirs = ["context", "skills"];
4228
4571
  subdirs.forEach((sub) => {
4229
- const subPath = join5(templateAiDir, sub);
4230
- if (existsSync5(subPath)) {
4572
+ const subPath = join8(templateAiDir, sub);
4573
+ if (existsSync8(subPath)) {
4231
4574
  readdirSync(subPath).forEach((file) => {
4232
4575
  operations.push({
4233
- dest: join5(".ai", sub, file),
4234
- src: join5(subPath, file)
4576
+ dest: join8(".ai", sub, file),
4577
+ src: join8(subPath, file)
4235
4578
  });
4236
4579
  });
4237
4580
  }
@@ -4239,17 +4582,17 @@ function handleOnboardApply(options) {
4239
4582
  }
4240
4583
  const globalAiSubdirs = ["context", "agents", "skills", "prompts", "checks", "templates", "session-logs", "registries", "proposals", "intelligence"];
4241
4584
  globalAiSubdirs.forEach((sub) => {
4242
- const globalPath = join5(sourceRoot, ".ai", sub);
4243
- if (existsSync5(globalPath)) {
4585
+ const globalPath = join8(sourceRoot, ".ai", sub);
4586
+ if (existsSync8(globalPath)) {
4244
4587
  readdirSync(globalPath).forEach((file) => {
4245
- const destRel = join5(".ai", sub, file);
4588
+ const destRel = join8(".ai", sub, file);
4246
4589
  if (!operations.some((op) => op.dest === destRel)) {
4247
4590
  if (options.caveman && (sub === "context" || sub === "skills" || sub === "prompts" || sub === "checks")) {
4248
4591
  return;
4249
4592
  }
4250
4593
  operations.push({
4251
4594
  dest: destRel,
4252
- src: join5(globalPath, file)
4595
+ src: join8(globalPath, file)
4253
4596
  });
4254
4597
  }
4255
4598
  });
@@ -4259,16 +4602,16 @@ function handleOnboardApply(options) {
4259
4602
  let skippedCount = 0;
4260
4603
  let updatedCount = 0;
4261
4604
  operations.forEach((op) => {
4262
- const destPath = join5(options.target, op.dest);
4263
- const destDir = dirname2(destPath);
4264
- if (existsSync5(destPath)) {
4605
+ const destPath = join8(options.target, op.dest);
4606
+ const destDir = dirname4(destPath);
4607
+ if (existsSync8(destPath)) {
4265
4608
  if (options.force) {
4266
4609
  if (!options.dryRun) {
4267
4610
  const backupPath = destPath + ".bak";
4268
- writeFileSync2(backupPath, readFileSync6(destPath));
4269
- if (!existsSync5(destDir))
4270
- mkdirSync(destDir, { recursive: true });
4271
- writeFileSync2(destPath, readFileSync6(op.src));
4611
+ writeFileSync4(backupPath, readFileSync9(destPath));
4612
+ if (!existsSync8(destDir))
4613
+ mkdirSync3(destDir, { recursive: true });
4614
+ writeFileSync4(destPath, readFileSync9(op.src));
4272
4615
  console.log(` \x1B[33mOVERWRITE (BACKUP CREATED):\x1B[0m ${op.dest} -> ${op.dest}.bak`);
4273
4616
  } else {
4274
4617
  console.log(` \x1B[36m[DRY-RUN] WOULD OVERWRITE & BACKUP:\x1B[0m ${op.dest}`);
@@ -4280,9 +4623,9 @@ function handleOnboardApply(options) {
4280
4623
  }
4281
4624
  } else {
4282
4625
  if (!options.dryRun) {
4283
- if (!existsSync5(destDir))
4284
- mkdirSync(destDir, { recursive: true });
4285
- writeFileSync2(destPath, readFileSync6(op.src));
4626
+ if (!existsSync8(destDir))
4627
+ mkdirSync3(destDir, { recursive: true });
4628
+ writeFileSync4(destPath, readFileSync9(op.src));
4286
4629
  console.log(` \x1B[32mCREATE:\x1B[0m ${op.dest}`);
4287
4630
  } else {
4288
4631
  console.log(` \x1B[36m[DRY-RUN] WOULD CREATE:\x1B[0m ${op.dest}`);
@@ -4307,8 +4650,8 @@ function handleOnboardStatus(options) {
4307
4650
  ];
4308
4651
  let presentCount = 0;
4309
4652
  crucialFiles.forEach((f) => {
4310
- const fullPath = join5(options.target, f);
4311
- const exists = existsSync5(fullPath);
4653
+ const fullPath = join8(options.target, f);
4654
+ const exists = existsSync8(fullPath);
4312
4655
  if (exists)
4313
4656
  presentCount++;
4314
4657
  console.log(` [${exists ? "\u2714" : " "}] ${f}`);
@@ -4325,10 +4668,10 @@ function handleOnboardStatus(options) {
4325
4668
  }
4326
4669
  }
4327
4670
  function getEnabledAdapters(target) {
4328
- const configPath = join5(target, ".ai", "config.yaml");
4329
- if (existsSync5(configPath)) {
4671
+ const configPath = join8(target, ".ai", "config.yaml");
4672
+ if (existsSync8(configPath)) {
4330
4673
  try {
4331
- const config = parseYaml(readFileSync6(configPath, "utf8")) || {};
4674
+ const config = parseYaml(readFileSync9(configPath, "utf8")) || {};
4332
4675
  return config.adapters || {};
4333
4676
  } catch (e) {
4334
4677
  }
@@ -4344,7 +4687,7 @@ function handleAdapterStatus(options) {
4344
4687
  const a = ADAPTERS[name];
4345
4688
  const isEnabled = enabled[name] || false;
4346
4689
  const rulesFile = a.rules_file;
4347
- const exists = existsSync5(join5(options.target, rulesFile));
4690
+ const exists = existsSync8(join8(options.target, rulesFile));
4348
4691
  let statusStr = "\x1B[31mMISSING\x1B[0m";
4349
4692
  if (exists) {
4350
4693
  statusStr = "\x1B[32mINSTALLED\x1B[0m";
@@ -4403,15 +4746,15 @@ function handleAdapterDiff(aName, options) {
4403
4746
  }
4404
4747
  adaptersToDiff.forEach((name) => {
4405
4748
  const a = ADAPTERS[name];
4406
- const srcFile = join5(sourceRoot, "adapters", name, a.rules_file);
4407
- const destFile = join5(options.target, a.rules_file);
4408
- if (!existsSync5(srcFile)) {
4749
+ const srcFile = join8(sourceRoot, "adapters", name, a.rules_file);
4750
+ const destFile = join8(options.target, a.rules_file);
4751
+ if (!existsSync8(srcFile)) {
4409
4752
  console.warn(`Warning: Source file for adapter '${name}' is missing at: ${srcFile}`);
4410
4753
  return;
4411
4754
  }
4412
- const srcContent = readFileSync6(srcFile, "utf8");
4413
- if (existsSync5(destFile)) {
4414
- const destContent = readFileSync6(destFile, "utf8");
4755
+ const srcContent = readFileSync9(srcFile, "utf8");
4756
+ if (existsSync8(destFile)) {
4757
+ const destContent = readFileSync9(destFile, "utf8");
4415
4758
  printDiff(srcContent, destContent, a.rules_file);
4416
4759
  } else {
4417
4760
  console.log(`
@@ -4450,21 +4793,21 @@ function handleAdapterSync(aName, options) {
4450
4793
  console.log("==================================================");
4451
4794
  adaptersToSync.forEach((name) => {
4452
4795
  const a = ADAPTERS[name];
4453
- const srcFile = join5(sourceRoot, "adapters", name, a.rules_file);
4454
- const destFile = join5(options.target, a.rules_file);
4455
- const destDir = dirname2(destFile);
4456
- if (!existsSync5(srcFile)) {
4796
+ const srcFile = join8(sourceRoot, "adapters", name, a.rules_file);
4797
+ const destFile = join8(options.target, a.rules_file);
4798
+ const destDir = dirname4(destFile);
4799
+ if (!existsSync8(srcFile)) {
4457
4800
  console.warn(`Warning: Source file for adapter '${name}' is missing at: ${srcFile}`);
4458
4801
  return;
4459
4802
  }
4460
- if (existsSync5(destFile)) {
4803
+ if (existsSync8(destFile)) {
4461
4804
  if (options.force) {
4462
4805
  if (!options.dryRun) {
4463
4806
  const backupPath = destFile + ".bak";
4464
- writeFileSync2(backupPath, readFileSync6(destFile));
4465
- if (!existsSync5(destDir))
4466
- mkdirSync(destDir, { recursive: true });
4467
- writeFileSync2(destFile, readFileSync6(srcFile));
4807
+ writeFileSync4(backupPath, readFileSync9(destFile));
4808
+ if (!existsSync8(destDir))
4809
+ mkdirSync3(destDir, { recursive: true });
4810
+ writeFileSync4(destFile, readFileSync9(srcFile));
4468
4811
  console.log(` \x1B[33mOVERWRITE (BACKUP CREATED):\x1B[0m ${a.rules_file} -> ${a.rules_file}.bak`);
4469
4812
  } else {
4470
4813
  console.log(` \x1B[36m[DRY-RUN] WOULD OVERWRITE & BACKUP:\x1B[0m ${a.rules_file}`);
@@ -4474,9 +4817,9 @@ function handleAdapterSync(aName, options) {
4474
4817
  }
4475
4818
  } else {
4476
4819
  if (!options.dryRun) {
4477
- if (!existsSync5(destDir))
4478
- mkdirSync(destDir, { recursive: true });
4479
- writeFileSync2(destFile, readFileSync6(srcFile));
4820
+ if (!existsSync8(destDir))
4821
+ mkdirSync3(destDir, { recursive: true });
4822
+ writeFileSync4(destFile, readFileSync9(srcFile));
4480
4823
  console.log(` \x1B[32mCREATE:\x1B[0m ${a.rules_file}`);
4481
4824
  } else {
4482
4825
  console.log(` \x1B[36m[DRY-RUN] WOULD CREATE:\x1B[0m ${a.rules_file}`);
@@ -4501,29 +4844,29 @@ function handleDoctorOnboarding(options) {
4501
4844
  "RUNBOOK.md"
4502
4845
  ];
4503
4846
  crucialFiles.forEach((f) => {
4504
- if (!existsSync5(join5(options.target, f))) {
4847
+ if (!existsSync8(join8(options.target, f))) {
4505
4848
  warn(`Crucial onboarding file '${f}' is missing from project root.`);
4506
4849
  }
4507
4850
  });
4508
- const configPath = join5(options.target, ".ai", "config.yaml");
4509
- if (!existsSync5(configPath)) {
4851
+ const configPath = join8(options.target, ".ai", "config.yaml");
4852
+ if (!existsSync8(configPath)) {
4510
4853
  warn("MultiModel Dev OS configuration file (.ai/config.yaml) is missing.");
4511
4854
  }
4512
- const registriesDir = join5(options.target, ".ai", "registries");
4513
- if (!existsSync5(registriesDir)) {
4855
+ const registriesDir = join8(options.target, ".ai", "registries");
4856
+ if (!existsSync8(registriesDir)) {
4514
4857
  warn("Registries directory (.ai/registries) is missing.");
4515
4858
  }
4516
- const proposalsDir = join5(options.target, ".ai", "proposals");
4517
- if (!existsSync5(proposalsDir)) {
4859
+ const proposalsDir = join8(options.target, ".ai", "proposals");
4860
+ if (!existsSync8(proposalsDir)) {
4518
4861
  warn("Proposals directory (.ai/proposals) is missing.");
4519
4862
  }
4520
- const intelligenceDir = join5(options.target, ".ai", "intelligence");
4521
- if (!existsSync5(intelligenceDir)) {
4863
+ const intelligenceDir = join8(options.target, ".ai", "intelligence");
4864
+ if (!existsSync8(intelligenceDir)) {
4522
4865
  warn("Intelligence directory (.ai/intelligence) is missing.");
4523
4866
  }
4524
- const gitignorePath = join5(options.target, ".gitignore");
4525
- if (existsSync5(gitignorePath)) {
4526
- const gitignoreContent = readFileSync6(gitignorePath, "utf8");
4867
+ const gitignorePath = join8(options.target, ".gitignore");
4868
+ if (existsSync8(gitignorePath)) {
4869
+ const gitignoreContent = readFileSync9(gitignorePath, "utf8");
4527
4870
  const checkIgnore = (pattern) => {
4528
4871
  if (!gitignoreContent.includes(pattern)) {
4529
4872
  warn(`Generated runtime file '${pattern}' is not ignored in .gitignore.`);
@@ -4687,7 +5030,7 @@ function handleDashboard(options) {
4687
5030
  \x1B[36mRunning Command:\x1B[0m npx multimodel-dev-os ${cmdStr}${targetFlag}`);
4688
5031
  console.log("--------------------------------------------------\n");
4689
5032
  try {
4690
- const cliPath = join5(sourceRoot, "bin", "multimodel-dev-os.js");
5033
+ const cliPath = join8(sourceRoot, "bin", "multimodel-dev-os.js");
4691
5034
  execSync(`node "${cliPath}" ${cmdStr} --target "${options.target}"`, { stdio: "inherit" });
4692
5035
  } catch (e) {
4693
5036
  console.error(`
@@ -4722,13 +5065,13 @@ function handleDashboard(options) {
4722
5065
  showMenu(mainMenu, "MultiModel Dev OS Command Center");
4723
5066
  }
4724
5067
  function getPluginsDir(targetDir) {
4725
- return join5(targetDir, ".ai", "plugins");
5068
+ return join8(targetDir, ".ai", "plugins");
4726
5069
  }
4727
5070
  function handlePluginList(options) {
4728
5071
  const pluginsDir = getPluginsDir(options.target);
4729
- const rawRelPath = relative(process.cwd(), join5(sourceRoot, ".ai", "plugins", "plugin.example.yaml")).replace(/\\/g, "/");
5072
+ const rawRelPath = relative(process.cwd(), join8(sourceRoot, ".ai", "plugins", "plugin.example.yaml")).replace(/\\/g, "/");
4730
5073
  const examplePath = rawRelPath.startsWith(".") ? rawRelPath : `./${rawRelPath}`;
4731
- if (!existsSync5(pluginsDir)) {
5074
+ if (!existsSync8(pluginsDir)) {
4732
5075
  if (options.json) {
4733
5076
  console.log("[]");
4734
5077
  return;
@@ -4749,7 +5092,7 @@ function handlePluginList(options) {
4749
5092
  const plugins = [];
4750
5093
  files.forEach((f) => {
4751
5094
  try {
4752
- const p = parseYaml(readFileSync6(join5(pluginsDir, f), "utf8"));
5095
+ const p = parseYaml(readFileSync9(join8(pluginsDir, f), "utf8"));
4753
5096
  if (p && p.name && p.slug) {
4754
5097
  plugins.push(p);
4755
5098
  }
@@ -4783,11 +5126,11 @@ function handlePluginShow(slug, options) {
4783
5126
  }
4784
5127
  const pluginsDir = getPluginsDir(options.target);
4785
5128
  let p = null;
4786
- if (existsSync5(pluginsDir)) {
5129
+ if (existsSync8(pluginsDir)) {
4787
5130
  const files = readdirSync(pluginsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
4788
5131
  for (const f of files) {
4789
5132
  try {
4790
- const parsed = parseYaml(readFileSync6(join5(pluginsDir, f), "utf8"));
5133
+ const parsed = parseYaml(readFileSync9(join8(pluginsDir, f), "utf8"));
4791
5134
  if (parsed && parsed.slug === slug) {
4792
5135
  p = parsed;
4793
5136
  break;
@@ -4836,7 +5179,7 @@ function handlePluginShow(slug, options) {
4836
5179
  }
4837
5180
  function handlePluginValidate(pluginPath, options) {
4838
5181
  const fullPath = resolve3(process.cwd(), pluginPath);
4839
- if (!existsSync5(fullPath)) {
5182
+ if (!existsSync8(fullPath)) {
4840
5183
  console.error(`\x1B[31mError: Plugin file not found at: ${pluginPath}\x1B[0m`);
4841
5184
  process.exit(1);
4842
5185
  }
@@ -4846,7 +5189,7 @@ function handlePluginValidate(pluginPath, options) {
4846
5189
  let errors = 0;
4847
5190
  let plugin = null;
4848
5191
  try {
4849
- plugin = parseYaml(readFileSync6(fullPath, "utf8"));
5192
+ plugin = parseYaml(readFileSync9(fullPath, "utf8"));
4850
5193
  } catch (e) {
4851
5194
  console.error(` \x1B[31m\u2717 [SYNTAX] Failed to parse YAML: ${e.message}\x1B[0m`);
4852
5195
  errors++;
@@ -4979,7 +5322,7 @@ function handlePluginValidate(pluginPath, options) {
4979
5322
  }
4980
5323
  function handlePluginInstall(pluginPath, options) {
4981
5324
  const fullPath = resolve3(process.cwd(), pluginPath);
4982
- if (!existsSync5(fullPath)) {
5325
+ if (!existsSync8(fullPath)) {
4983
5326
  console.error(`\x1B[31mError: Plugin file not found at: ${pluginPath}\x1B[0m`);
4984
5327
  process.exit(1);
4985
5328
  }
@@ -4989,16 +5332,16 @@ function handlePluginInstall(pluginPath, options) {
4989
5332
  process.exit(1);
4990
5333
  }
4991
5334
  const policy = loadRegistryPolicy(options.target || process.cwd());
4992
- const pluginContent = readFileSync6(fullPath, "utf8");
5335
+ const pluginContent = readFileSync9(fullPath, "utf8");
4993
5336
  const plugin = parseYaml(pluginContent);
4994
5337
  const slug = plugin.slug;
4995
- const sourceDir = dirname2(fullPath);
5338
+ const sourceDir = dirname4(fullPath);
4996
5339
  console.log(`
4997
5340
  \u{1F4E5} \x1B[34mInstalling Plugin: ${plugin.name} [slug: ${slug}]\x1B[0m`);
4998
5341
  const filesToCopy = [];
4999
5342
  filesToCopy.push({
5000
5343
  src: fullPath,
5001
- dest: join5(".ai", "plugins", `${slug}.yaml`),
5344
+ dest: join8(".ai", "plugins", `${slug}.yaml`),
5002
5345
  description: "Plugin Manifest"
5003
5346
  });
5004
5347
  if (Array.isArray(plugin.allowed_file_patterns)) {
@@ -5016,8 +5359,8 @@ function handlePluginInstall(pluginPath, options) {
5016
5359
  console.error(`\x1B[31mError: File extension '${ext}' for asset '${pattern}' is not allowed by policy. Installation aborted.\x1B[0m`);
5017
5360
  process.exit(1);
5018
5361
  }
5019
- const srcFile = join5(sourceDir, normPattern);
5020
- if (existsSync5(srcFile) && statSync(srcFile).isFile()) {
5362
+ const srcFile = join8(sourceDir, normPattern);
5363
+ if (existsSync8(srcFile) && statSync(srcFile).isFile()) {
5021
5364
  filesToCopy.push({
5022
5365
  src: srcFile,
5023
5366
  dest: normPattern,
@@ -5032,7 +5375,7 @@ function handlePluginInstall(pluginPath, options) {
5032
5375
  }
5033
5376
  let totalSize = 0;
5034
5377
  filesToCopy.forEach((item) => {
5035
- if (existsSync5(item.src)) {
5378
+ if (existsSync8(item.src)) {
5036
5379
  totalSize += statSync(item.src).size;
5037
5380
  }
5038
5381
  });
@@ -5042,8 +5385,8 @@ function handlePluginInstall(pluginPath, options) {
5042
5385
  }
5043
5386
  let conflicts = false;
5044
5387
  filesToCopy.forEach((item) => {
5045
- const destPath = join5(options.target, item.dest);
5046
- if (existsSync5(destPath)) {
5388
+ const destPath = join8(options.target, item.dest);
5389
+ if (existsSync8(destPath)) {
5047
5390
  if (!options.force) {
5048
5391
  console.error(` \x1B[31mConflict:\x1B[0m File already exists at destination: ${item.dest}`);
5049
5392
  conflicts = true;
@@ -5063,7 +5406,7 @@ function handlePluginInstall(pluginPath, options) {
5063
5406
  console.log(`
5064
5407
  \x1B[33mPlanned Installation Actions:\x1B[0m`);
5065
5408
  filesToCopy.forEach((item) => {
5066
- const exists = existsSync5(join5(options.target, item.dest));
5409
+ const exists = existsSync8(join8(options.target, item.dest));
5067
5410
  const suffix = exists ? " \x1B[33m(will overwrite)\x1B[0m" : "";
5068
5411
  console.log(` - \x1B[36m[WOULD COPY]\x1B[0m ${item.src} -> ${item.dest}${suffix}`);
5069
5412
  });
@@ -5073,17 +5416,17 @@ function handlePluginInstall(pluginPath, options) {
5073
5416
  process.exit(1);
5074
5417
  }
5075
5418
  filesToCopy.forEach((item) => {
5076
- const destPath = join5(options.target, item.dest);
5077
- const destDir = dirname2(destPath);
5078
- if (!existsSync5(destDir)) {
5079
- mkdirSync(destDir, { recursive: true });
5419
+ const destPath = join8(options.target, item.dest);
5420
+ const destDir = dirname4(destPath);
5421
+ if (!existsSync8(destDir)) {
5422
+ mkdirSync3(destDir, { recursive: true });
5080
5423
  }
5081
- if (existsSync5(destPath)) {
5424
+ if (existsSync8(destPath)) {
5082
5425
  const bakPath = `${destPath}.bak`;
5083
- writeFileSync2(bakPath, readFileSync6(destPath));
5426
+ writeFileSync4(bakPath, readFileSync9(destPath));
5084
5427
  console.log(` \x1B[33mBACKUP:\x1B[0m Created backup: ${item.dest}.bak`);
5085
5428
  }
5086
- writeFileSync2(destPath, readFileSync6(item.src));
5429
+ writeFileSync4(destPath, readFileSync9(item.src));
5087
5430
  console.log(` \x1B[32mCOPY:\x1B[0m ${item.dest}`);
5088
5431
  });
5089
5432
  console.log(`
@@ -5112,7 +5455,7 @@ function handlePluginStatus(options) {
5112
5455
  console.log(`
5113
5456
  \u{1F50C} \x1B[36mAuditing Plugins Status in: ${options.target}\x1B[0m`);
5114
5457
  console.log("==================================================");
5115
- if (!existsSync5(pluginsDir)) {
5458
+ if (!existsSync8(pluginsDir)) {
5116
5459
  console.log(" No plugins directory found. 0 plugins installed.\n");
5117
5460
  return;
5118
5461
  }
@@ -5127,8 +5470,8 @@ function handlePluginStatus(options) {
5127
5470
  }
5128
5471
  files.forEach((f) => {
5129
5472
  try {
5130
- const pPath = join5(pluginsDir, f);
5131
- const p = parseYaml(readFileSync6(pPath, "utf8"));
5473
+ const pPath = join8(pluginsDir, f);
5474
+ const p = parseYaml(readFileSync9(pPath, "utf8"));
5132
5475
  if (p && p.name) {
5133
5476
  console.log(`
5134
5477
  * \x1B[32m${p.name}\x1B[0m (v${p.version || "1.0.0"})`);
@@ -5136,8 +5479,8 @@ function handlePluginStatus(options) {
5136
5479
  let presentCount = 0;
5137
5480
  if (Array.isArray(p.allowed_file_patterns)) {
5138
5481
  p.allowed_file_patterns.forEach((pat) => {
5139
- const destPath = join5(options.target, pat);
5140
- if (existsSync5(destPath) && statSync(destPath).isFile()) {
5482
+ const destPath = join8(options.target, pat);
5483
+ if (existsSync8(destPath) && statSync(destPath).isFile()) {
5141
5484
  presentCount++;
5142
5485
  } else {
5143
5486
  missingCount++;
@@ -5153,8 +5496,8 @@ function handlePluginStatus(options) {
5153
5496
  console.log(` Status: \x1B[33mIncomplete\x1B[0m (${presentCount}/${total} assets present, ${missingCount} missing)`);
5154
5497
  console.log(` Missing Assets:`);
5155
5498
  p.allowed_file_patterns.forEach((pat) => {
5156
- const destPath = join5(options.target, pat);
5157
- if (!existsSync5(destPath) || !statSync(destPath).isFile()) {
5499
+ const destPath = join8(options.target, pat);
5500
+ if (!existsSync8(destPath) || !statSync(destPath).isFile()) {
5158
5501
  console.log(` \x1B[31m\u2717\x1B[0m ${pat}`);
5159
5502
  }
5160
5503
  });
@@ -5184,12 +5527,12 @@ function handleCatalogList(options) {
5184
5527
  }
5185
5528
  const installedSlugs = /* @__PURE__ */ new Set();
5186
5529
  const pluginsDir = getPluginsDir(options.target);
5187
- if (existsSync5(pluginsDir)) {
5530
+ if (existsSync8(pluginsDir)) {
5188
5531
  try {
5189
5532
  const files = readdirSync(pluginsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
5190
5533
  files.forEach((f) => {
5191
5534
  try {
5192
- const parsed = parseYaml(readFileSync6(join5(pluginsDir, f), "utf8"));
5535
+ const parsed = parseYaml(readFileSync9(join8(pluginsDir, f), "utf8"));
5193
5536
  if (parsed && parsed.slug) {
5194
5537
  installedSlugs.add(parsed.slug);
5195
5538
  }
@@ -5303,9 +5646,9 @@ function handleCatalogInstall(slug, options) {
5303
5646
  const policy = loadRegistryPolicy(options.target || process.cwd());
5304
5647
  let srcPath;
5305
5648
  if (p._source === "bundled") {
5306
- srcPath = join5(sourceRoot, ".ai", "plugins", "catalog", `${slug}.yaml`);
5649
+ srcPath = join8(sourceRoot, ".ai", "plugins", "catalog", `${slug}.yaml`);
5307
5650
  } else if (p._source === "local") {
5308
- srcPath = join5(options.target || process.cwd(), ".ai", "plugins", "catalog", `${slug}.yaml`);
5651
+ srcPath = join8(options.target || process.cwd(), ".ai", "plugins", "catalog", `${slug}.yaml`);
5309
5652
  } else if (p._source && p._source.startsWith("remote:")) {
5310
5653
  const regName = p._source.substring(7);
5311
5654
  const sources = loadRegistrySources();
@@ -5318,11 +5661,11 @@ function handleCatalogInstall(slug, options) {
5318
5661
  process.exit(1);
5319
5662
  }
5320
5663
  }
5321
- srcPath = join5(sourceRoot, ".ai", "registry-cache", regName, "catalog", `${slug}.yaml`);
5664
+ srcPath = join8(sourceRoot, ".ai", "registry-cache", regName, "catalog", `${slug}.yaml`);
5322
5665
  } else {
5323
- srcPath = join5(sourceRoot, ".ai", "plugins", "catalog", `${slug}.yaml`);
5666
+ srcPath = join8(sourceRoot, ".ai", "plugins", "catalog", `${slug}.yaml`);
5324
5667
  }
5325
- if (!existsSync5(srcPath)) {
5668
+ if (!existsSync8(srcPath)) {
5326
5669
  console.error(`\x1B[31mError: Packed plugin manifest not found at: ${srcPath}\x1B[0m`);
5327
5670
  process.exit(1);
5328
5671
  }
@@ -5341,19 +5684,19 @@ function handleCatalogStatus(options) {
5341
5684
  }
5342
5685
  plugins.forEach((p) => {
5343
5686
  const slug = p.slug;
5344
- const destManifest = join5(pluginsDir, `${slug}.yaml`);
5345
- if (!existsSync5(destManifest)) {
5687
+ const destManifest = join8(pluginsDir, `${slug}.yaml`);
5688
+ if (!existsSync8(destManifest)) {
5346
5689
  console.log(` - \x1B[33m${p.name}\x1B[0m (v${p.version}): \x1B[90mNot installed\x1B[0m`);
5347
5690
  console.log(` Install via: \x1B[36mnpx multimodel-dev-os catalog install ${slug} --approved\x1B[0m`);
5348
5691
  } else {
5349
5692
  let missingCount = 0;
5350
5693
  let presentCount = 0;
5351
5694
  try {
5352
- const targetP = parseYaml(readFileSync6(destManifest, "utf8"));
5695
+ const targetP = parseYaml(readFileSync9(destManifest, "utf8"));
5353
5696
  if (Array.isArray(targetP.allowed_file_patterns)) {
5354
5697
  targetP.allowed_file_patterns.forEach((pat) => {
5355
- const destPath = join5(options.target, pat);
5356
- if (existsSync5(destPath) && statSync(destPath).isFile()) {
5698
+ const destPath = join8(options.target, pat);
5699
+ if (existsSync8(destPath) && statSync(destPath).isFile()) {
5357
5700
  presentCount++;
5358
5701
  } else {
5359
5702
  missingCount++;
@@ -5510,22 +5853,28 @@ function handleRegistryList(options) {
5510
5853
  console.log("==================================================");
5511
5854
  console.log(`Policy Status: allow_remote_registries = \x1B[${policy.allow_remote_registries ? "32mtrue" : "33mfalse"}\x1B[0m (Remote registries are disabled by default for safety)
5512
5855
  `);
5856
+ const lockfile = loadRegistryLockfile(options.target || process.cwd());
5513
5857
  sources.forEach((s) => {
5514
5858
  const status = s.enabled ? "\x1B[32m\u25CF enabled\x1B[0m" : "\x1B[90m\u25CB disabled\x1B[0m";
5515
5859
  const label = s.name === "bundled" ? "bundled" : s.type === "local" ? `local:${s.name}` : `remote:${s.name}`;
5516
- console.log(` \x1B[32m${s.name}\x1B[0m [${label}] ${status}`);
5860
+ const lockEntry = lockfile.entries[s.name];
5861
+ const lockBadge = lockEntry ? lockEntry.signature ? " \x1B[32m[signed]\x1B[0m" : " \x1B[33m[unsigned]\x1B[0m" : " \x1B[90m[no lockfile entry]\x1B[0m";
5862
+ console.log(` \x1B[32m${s.name}\x1B[0m [${label}] ${status}${lockBadge}`);
5517
5863
  console.log(` type: ${s.type}`);
5518
5864
  console.log(` url: ${s.url}`);
5519
5865
  console.log(` trust_level: ${s.trust_level}`);
5520
5866
  console.log(` safety_policy: ${s.safety_policy}`);
5521
5867
  console.log(` checksum: ${s.checksum_required ? "required (SHA-256 integrity)" : "not required"}`);
5522
- console.log(` signature: ${s.signature_required ? "required" : "not required (v3.0.1)"}`);
5868
+ console.log(` signature: ${s.signature_required ? "required (HMAC-SHA256)" : "not required"}`);
5523
5869
  if (s.last_synced_at)
5524
5870
  console.log(` last_synced: ${s.last_synced_at}`);
5871
+ if (lockEntry)
5872
+ console.log(` lockfile: synced ${lockEntry.synced_at}, hash ${lockEntry.catalog_sha256.slice(0, 16)}...`);
5525
5873
  });
5526
5874
  console.log("\nUse \x1B[36mregistry show <name>\x1B[0m to view detailed source configuration.");
5527
5875
  console.log("Use \x1B[36mregistry status\x1B[0m to see policy states and cache health.");
5528
- console.log("Use \x1B[36mregistry verify <name>\x1B[0m to perform integrity checks.\n");
5876
+ console.log("Use \x1B[36mregistry verify <name>\x1B[0m to perform integrity checks.");
5877
+ console.log("Use \x1B[36mregistry lock\x1B[0m to inspect the provenance lockfile.\n");
5529
5878
  }
5530
5879
  function handleRegistryAdd(name, url, options) {
5531
5880
  const policy = loadRegistryPolicy(options.target);
@@ -5610,14 +5959,14 @@ Run with --approved to apply:
5610
5959
  }
5611
5960
  sources.splice(idx, 1);
5612
5961
  saveRegistrySources(sources);
5613
- const cacheDir = join5(sourceRoot, ".ai", "registry-cache", name);
5614
- if (existsSync5(cacheDir)) {
5962
+ const cacheDir = join8(sourceRoot, ".ai", "registry-cache", name);
5963
+ if (existsSync8(cacheDir)) {
5615
5964
  try {
5616
5965
  const files = readdirSync(cacheDir);
5617
5966
  files.forEach((f) => {
5618
- const fp = join5(cacheDir, f);
5967
+ const fp = join8(cacheDir, f);
5619
5968
  if (statSync(fp).isFile()) {
5620
- writeFileSync2(fp, "");
5969
+ writeFileSync4(fp, "");
5621
5970
  }
5622
5971
  });
5623
5972
  } catch (e) {
@@ -5626,7 +5975,7 @@ Run with --approved to apply:
5626
5975
  console.log(`
5627
5976
  \x1B[32m\u2714 Registry '${name}' removed successfully.\x1B[0m`);
5628
5977
  console.log(` Source entry removed from .ai/registries/sources.yaml`);
5629
- if (existsSync5(cacheDir)) {
5978
+ if (existsSync8(cacheDir)) {
5630
5979
  console.log(` Cache directory cleared: .ai/registry-cache/${name}/`);
5631
5980
  }
5632
5981
  console.log("");
@@ -5687,9 +6036,9 @@ To execute this sync operation, run:`);
5687
6036
  `);
5688
6037
  process.exit(1);
5689
6038
  }
5690
- const cacheDir = join5(sourceRoot, ".ai", "registry-cache", name);
5691
- if (!existsSync5(cacheDir)) {
5692
- mkdirSync(cacheDir, { recursive: true });
6039
+ const cacheDir = join8(sourceRoot, ".ai", "registry-cache", name);
6040
+ if (!existsSync8(cacheDir)) {
6041
+ mkdirSync3(cacheDir, { recursive: true });
5693
6042
  }
5694
6043
  console.log(`
5695
6044
  \u{1F504} \x1B[36mSyncing Registry: ${name}\x1B[0m`);
@@ -5698,8 +6047,8 @@ To execute this sync operation, run:`);
5698
6047
  const catalogUrl = url.endsWith("/") ? `${url}catalog.yaml` : url;
5699
6048
  const manifestUrl = catalogUrl.replace(/catalog\.yaml$/, "manifest.json");
5700
6049
  try {
5701
- const catalogDest = join5(cacheDir, "catalog.yaml");
5702
- const manifestDest = join5(cacheDir, "manifest.json");
6050
+ const catalogDest = join8(cacheDir, "catalog.yaml");
6051
+ const manifestDest = join8(cacheDir, "manifest.json");
5703
6052
  const fetchUrlSync = (targetUrl) => {
5704
6053
  validateRegistryUrl(targetUrl, policy);
5705
6054
  const script = `
@@ -5721,7 +6070,7 @@ To execute this sync operation, run:`);
5721
6070
  console.log(`Downloading: ${catalogUrl}`);
5722
6071
  console.log(` \u2192 .ai/registry-cache/${name}/catalog.yaml ...`);
5723
6072
  const catalogData = fetchUrlSync(catalogUrl);
5724
- writeFileSync2(catalogDest, catalogData, "utf8");
6073
+ writeFileSync4(catalogDest, catalogData, "utf8");
5725
6074
  const catalogSize = (Buffer.byteLength(catalogData) / 1024).toFixed(1);
5726
6075
  console.log(` \u2192 OK (${catalogSize}KB)`);
5727
6076
  let manifestData = null;
@@ -5729,7 +6078,7 @@ To execute this sync operation, run:`);
5729
6078
  console.log(`Downloading: ${manifestUrl}`);
5730
6079
  console.log(` \u2192 .ai/registry-cache/${name}/manifest.json ...`);
5731
6080
  manifestData = fetchUrlSync(manifestUrl);
5732
- writeFileSync2(manifestDest, manifestData, "utf8");
6081
+ writeFileSync4(manifestDest, manifestData, "utf8");
5733
6082
  const manifestSize = (Buffer.byteLength(manifestData) / 1024).toFixed(1);
5734
6083
  console.log(` \u2192 OK (${manifestSize}KB)`);
5735
6084
  } catch (e) {
@@ -5751,9 +6100,9 @@ To execute this sync operation, run:`);
5751
6100
  for (const [file, hash] of Object.entries(manifestObj.files_hashes)) {
5752
6101
  if (file === "catalog.yaml" || file === "manifest.json")
5753
6102
  continue;
5754
- const fileDest = join5(cacheDir, file);
6103
+ const fileDest = join8(cacheDir, file);
5755
6104
  const relativeToCache = relative(cacheDir, fileDest);
5756
- if (relativeToCache.includes("..") || isAbsolute(relativeToCache)) {
6105
+ if (relativeToCache.includes("..") || isAbsolute2(relativeToCache)) {
5757
6106
  console.error(`\x1B[31mError: Safe path violation in manifest files list: ${file}\x1B[0m`);
5758
6107
  process.exit(1);
5759
6108
  }
@@ -5765,11 +6114,11 @@ To execute this sync operation, run:`);
5765
6114
  console.error(`\x1B[31mError: Registry cache size limit exceeded (max: ${policy.max_registry_cache_size_kb}KB).\x1B[0m`);
5766
6115
  process.exit(1);
5767
6116
  }
5768
- const fileDir = dirname2(fileDest);
5769
- if (!existsSync5(fileDir)) {
5770
- mkdirSync(fileDir, { recursive: true });
6117
+ const fileDir = dirname4(fileDest);
6118
+ if (!existsSync8(fileDir)) {
6119
+ mkdirSync3(fileDir, { recursive: true });
5771
6120
  }
5772
- writeFileSync2(fileDest, fileData, "utf8");
6121
+ writeFileSync4(fileDest, fileData, "utf8");
5773
6122
  const fileSize = (Buffer.byteLength(fileData) / 1024).toFixed(1);
5774
6123
  console.log(` \u2192 OK (${fileSize}KB)`);
5775
6124
  const actualHash = computeSHA256(fileData);
@@ -5789,7 +6138,7 @@ To execute this sync operation, run:`);
5789
6138
  }
5790
6139
  }
5791
6140
  const checksumsJson = JSON.stringify(checksums, null, 2);
5792
- writeFileSync2(join5(cacheDir, "checksums.json"), checksumsJson, "utf8");
6141
+ writeFileSync4(join8(cacheDir, "checksums.json"), checksumsJson, "utf8");
5793
6142
  console.log(` \u2192 .ai/registry-cache/${name}/checksums.json ... OK`);
5794
6143
  if (policy.require_checksum && manifestData) {
5795
6144
  try {
@@ -5811,9 +6160,96 @@ To execute this sync operation, run:`);
5811
6160
  } catch (e) {
5812
6161
  }
5813
6162
  }
5814
- source.last_synced_at = (/* @__PURE__ */ new Date()).toISOString();
6163
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
6164
+ source.last_synced_at = syncedAt;
5815
6165
  source.pinned_commit_or_hash = computeSHA256(catalogData);
5816
6166
  saveRegistrySources(sources);
6167
+ const catalogHash = computeSHA256(catalogData);
6168
+ const manifestHash = manifestData ? computeSHA256(manifestData) : null;
6169
+ const projectDir = options.target || process.cwd();
6170
+ let signingKey = null;
6171
+ let signature = null;
6172
+ try {
6173
+ signingKey = loadSigningKey(projectDir);
6174
+ } catch (sigKeyErr) {
6175
+ console.log(` \x1B[33mWarning: Signing key error \u2014 ${sigKeyErr.message}\x1B[0m`);
6176
+ }
6177
+ if (signingKey) {
6178
+ try {
6179
+ signature = signPayload(signingKey, catalogHash);
6180
+ console.log(" \x1B[32m\u2713 Catalog signed with project signing key (HMAC-SHA256)\x1B[0m");
6181
+ } catch (signErr) {
6182
+ console.log(` \x1B[33mWarning: Signing failed \u2014 ${signErr.message}\x1B[0m`);
6183
+ }
6184
+ } else {
6185
+ if (policy.require_signature) {
6186
+ console.error(`\x1B[31mError: policy require_signature is true but no signing key found.\x1B[0m`);
6187
+ console.error(` Generate a key with: npx multimodel-dev-os registry keygen --approved`);
6188
+ process.exit(1);
6189
+ }
6190
+ console.log(" \x1B[33m\u26A0 No signing key \u2014 provenance recorded without signature.\x1B[0m");
6191
+ console.log(" Generate a key with: npx multimodel-dev-os registry keygen --approved");
6192
+ }
6193
+ const trustedKeys = loadTrustedKeys(projectDir, policy);
6194
+ let verifyRes = { verified: true, status: "unsigned" };
6195
+ let parsedManifest = null;
6196
+ if (manifestData) {
6197
+ try {
6198
+ parsedManifest = JSON.parse(manifestData);
6199
+ verifyRes = verifySignatureBlock({
6200
+ manifest: parsedManifest,
6201
+ trustedKeys,
6202
+ policy,
6203
+ hmacKey: signingKey,
6204
+ source
6205
+ });
6206
+ } catch (_e) {
6207
+ }
6208
+ } else {
6209
+ if (policy.require_signature || policy.allow_unsigned_remote === false) {
6210
+ verifyRes = { verified: false, error: "Manifest missing but signature is required by policy." };
6211
+ }
6212
+ }
6213
+ const firstSig = parsedManifest && (parsedManifest.signature || Array.isArray(parsedManifest.signatures) && parsedManifest.signatures[0]);
6214
+ const sigBlock = firstSig && typeof firstSig === "object" ? firstSig : null;
6215
+ let trustedPublisherStatus = "unknown";
6216
+ if (sigBlock && sigBlock.key_id) {
6217
+ const tk = trustedKeys.find((k) => k.key_id === sigBlock.key_id);
6218
+ if (tk) {
6219
+ trustedPublisherStatus = tk.status || "inactive";
6220
+ }
6221
+ }
6222
+ let trustVerdict = "failed";
6223
+ if (verifyRes.verified) {
6224
+ if (verifyRes.status === "verified") {
6225
+ trustVerdict = "verified";
6226
+ } else {
6227
+ trustVerdict = "unsigned_allowed";
6228
+ }
6229
+ }
6230
+ const verificationErrors = verifyRes.errors || (verifyRes.error ? [verifyRes.error] : []);
6231
+ const verificationWarnings = verifyRes.warning ? [verifyRes.warning] : [];
6232
+ const lockfile = loadRegistryLockfile(projectDir);
6233
+ updateLockfileEntry(lockfile, name, {
6234
+ url: source.url,
6235
+ synced_at: options.synced_at || syncedAt,
6236
+ // Allow override for test determinism
6237
+ catalog_sha256: catalogHash,
6238
+ manifest_sha256: manifestHash,
6239
+ signature,
6240
+ signature_alg: "hmac-sha256",
6241
+ public_signature_status: verifyRes.status || "unsigned",
6242
+ public_signature_algorithm: sigBlock ? sigBlock.algorithm : null,
6243
+ public_signature_key_id: sigBlock ? sigBlock.key_id : null,
6244
+ trusted_publisher_status: trustedPublisherStatus,
6245
+ trust_store_path: policy.trusted_keys_file || ".ai/registries/trusted-keys.yaml",
6246
+ trust_verdict: trustVerdict,
6247
+ lockfile_verdict: "verified",
6248
+ verification_errors: verificationErrors,
6249
+ verification_warnings: verificationWarnings
6250
+ });
6251
+ saveRegistryLockfile(projectDir, lockfile);
6252
+ console.log(` \x1B[32m\u2713 Provenance lockfile updated: .ai/registry-lock.json\x1B[0m`);
5817
6253
  let pluginCount = 0;
5818
6254
  try {
5819
6255
  const catParsed = parseYaml(catalogData);
@@ -5825,11 +6261,13 @@ To execute this sync operation, run:`);
5825
6261
  console.log(` Cache location: .ai/registry-cache/${name}/`);
5826
6262
  console.log(` Plugins cached: ${pluginCount} entries`);
5827
6263
  console.log(` Checksum status: VERIFIED (SHA256)`);
5828
- console.log(` Last synced: ${source.last_synced_at}`);
6264
+ console.log(` Provenance: ${signature ? "SIGNED (HMAC-SHA256)" : "Unsigned (no signing key)"}`);
6265
+ console.log(` Last synced: ${syncedAt}`);
5829
6266
  console.log(`
5830
6267
  Next steps:`);
5831
6268
  console.log(` \u2022 Browse: npx multimodel-dev-os catalog list --source remote:${name}`);
5832
6269
  console.log(` \u2022 Verify: npx multimodel-dev-os registry verify ${name}`);
6270
+ console.log(` \u2022 Lock: npx multimodel-dev-os registry lock`);
5833
6271
  console.log(` \u2022 Install: npx multimodel-dev-os catalog install <slug> --approved
5834
6272
  `);
5835
6273
  } catch (e) {
@@ -5847,28 +6285,58 @@ function handleRegistryStatus(options) {
5847
6285
  const sources = loadRegistrySources();
5848
6286
  const policy = loadRegistryPolicy(options.target);
5849
6287
  if (options.json) {
5850
- console.log(JSON.stringify({ sources, policy: { allow_remote_registries: policy.allow_remote_registries, require_checksum: policy.require_checksum } }, null, 2));
6288
+ console.log(JSON.stringify({ sources, policy }, null, 2));
5851
6289
  return;
5852
6290
  }
6291
+ const projectDir = options.target || process.cwd();
6292
+ let signingKeyStatus = "\x1B[90mnot configured\x1B[0m";
6293
+ try {
6294
+ const sk = loadSigningKey(projectDir);
6295
+ signingKeyStatus = sk ? `\x1B[32mconfigured\x1B[0m (${getSigningKeyPath(projectDir)})` : "\x1B[90mnot configured\x1B[0m";
6296
+ } catch (e) {
6297
+ signingKeyStatus = `\x1B[31merror: ${e.message}\x1B[0m`;
6298
+ }
6299
+ const lockfile = loadRegistryLockfile(projectDir);
6300
+ const lockfileEntryCount = Object.keys(lockfile.entries).length;
6301
+ const lockfilePath = getLockfilePath(projectDir);
6302
+ const lockfileStatus = existsSync8(lockfilePath) ? `\x1B[32mpresent\x1B[0m (${lockfileEntryCount} entr${lockfileEntryCount === 1 ? "y" : "ies"})` : "\x1B[90mnot present\x1B[0m";
5853
6303
  console.log(`
5854
6304
  \u{1F4CA} \x1B[36mRegistry Status [v${version}]\x1B[0m`);
5855
6305
  console.log("==================================================");
5856
6306
  console.log(`\x1B[33mPolicy State:\x1B[0m`);
5857
- console.log(` allow_remote_registries: \x1B[${policy.allow_remote_registries ? "32mtrue" : "33mfalse"}\x1B[0m (Disabled by default)`);
5858
- console.log(` require_checksum: ${policy.require_checksum ? "\x1B[32mtrue\x1B[0m (SHA256 integrity enforced)" : "\x1B[33mfalse\x1B[0m"}`);
5859
- console.log(` require_signature: ${policy.require_signature ? "\x1B[32mtrue\x1B[0m" : "\x1B[90mfalse (not enforced in v3.0)\x1B[0m"}`);
5860
- console.log(` allow_untrusted_install: ${policy.allow_untrusted_install ? "\x1B[33mtrue\x1B[0m" : "\x1B[32mfalse\x1B[0m (secured)"}`);
5861
- console.log(` max_plugin_files: ${policy.max_plugin_files}`);
5862
- console.log(` max_plugin_size_kb: ${policy.max_plugin_size_kb}KB`);
5863
- console.log(` max_registry_cache_size: ${policy.max_registry_cache_size_kb}KB`);
6307
+ console.log(` allow_remote_registries: \x1B[${policy.allow_remote_registries ? "32mtrue" : "33mfalse"}\x1B[0m (Disabled by default)`);
6308
+ console.log(` require_checksum: ${policy.require_checksum ? "\x1B[32mtrue\x1B[0m (SHA256 integrity enforced)" : "\x1B[33mfalse\x1B[0m"}`);
6309
+ console.log(` require_signature: ${policy.require_signature ? "\x1B[32mtrue\x1B[0m (HMAC-SHA256 enforced)" : "\x1B[90mfalse\x1B[0m"}`);
6310
+ console.log(` require_lockfile_on_verify: ${policy.require_lockfile_on_verify ? "\x1B[32mtrue\x1B[0m" : "\x1B[90mfalse\x1B[0m"}`);
6311
+ console.log(` allow_untrusted_install: ${policy.allow_untrusted_install ? "\x1B[33mtrue\x1B[0m" : "\x1B[32mfalse\x1B[0m (secured)"}`);
6312
+ console.log(` allow_unsigned_local: ${policy.allow_unsigned_local ? "\x1B[32mtrue\x1B[0m" : "\x1B[33mfalse\x1B[0m"}`);
6313
+ console.log(` allow_unsigned_bundled: ${policy.allow_unsigned_bundled ? "\x1B[32mtrue\x1B[0m" : "\x1B[33mfalse\x1B[0m"}`);
6314
+ console.log(` allow_unsigned_remote: ${policy.allow_unsigned_remote ? "\x1B[32mtrue\x1B[0m" : "\x1B[33mfalse\x1B[0m"}`);
6315
+ console.log(` require_trusted_publisher: ${policy.require_trusted_publisher ? "\x1B[32mtrue\x1B[0m" : "\x1B[90mfalse\x1B[0m"}`);
6316
+ console.log(` provenance_required: ${policy.provenance_required ? "\x1B[32mtrue\x1B[0m" : "\x1B[90mfalse\x1B[0m"}`);
6317
+ console.log(` trusted_keys_file: \x1B[36m${policy.trusted_keys_file}\x1B[0m`);
6318
+ console.log(` allowed_signature_algs: \x1B[36m${(policy.allowed_signature_algorithms || []).join(", ")}\x1B[0m`);
6319
+ console.log(` max_plugin_files: ${policy.max_plugin_files}`);
6320
+ console.log(` max_plugin_size_kb: ${policy.max_plugin_size_kb}KB`);
6321
+ console.log(` max_registry_cache_size: ${policy.max_registry_cache_size_kb}KB`);
6322
+ console.log(`
6323
+ \x1B[33mSigning & Provenance:\x1B[0m`);
6324
+ console.log(` Signing key: ${signingKeyStatus}`);
6325
+ console.log(` Lockfile: ${lockfileStatus}`);
6326
+ if (lockfileEntryCount > 0) {
6327
+ Object.entries(lockfile.entries).forEach(([rName, entry]) => {
6328
+ const sigBadge = entry.signature ? "\x1B[32m[signed]\x1B[0m" : "\x1B[33m[unsigned]\x1B[0m";
6329
+ console.log(` ${rName}: ${sigBadge} synced ${entry.synced_at || "unknown"}`);
6330
+ });
6331
+ }
5864
6332
  console.log(`
5865
6333
  \x1B[33mSources:\x1B[0m`);
5866
6334
  sources.forEach((s) => {
5867
6335
  const status = s.enabled ? "\x1B[32m\u25CF enabled\x1B[0m" : "\x1B[90m\u25CB disabled\x1B[0m";
5868
6336
  const label = s.name === "bundled" ? "bundled" : s.type === "local" ? `local:${s.name}` : `remote:${s.name}`;
5869
6337
  const synced = s.last_synced_at ? `synced: ${s.last_synced_at}` : "never synced";
5870
- const cacheDir = join5(sourceRoot, ".ai", "registry-cache", s.name);
5871
- const hasCache = s.type !== "local" && existsSync5(cacheDir);
6338
+ const cacheDir = join8(sourceRoot, ".ai", "registry-cache", s.name);
6339
+ const hasCache = s.type !== "local" && existsSync8(cacheDir);
5872
6340
  console.log(` ${s.name} ${status} [${label}] (${s.type}, ${s.trust_level})`);
5873
6341
  if (s.type !== "local") {
5874
6342
  console.log(` URL: ${s.url}`);
@@ -5884,87 +6352,335 @@ function handleRegistryVerify(name, options) {
5884
6352
  console.log(`
5885
6353
  \u{1F50D} \x1B[36mVerifying Registry: ${name}\x1B[0m`);
5886
6354
  console.log("==================================================");
5887
- if (name === "bundled") {
5888
- const catalogPath = join5(sourceRoot, ".ai", "plugins", "catalog.yaml");
5889
- if (!existsSync5(catalogPath)) {
5890
- console.error("\x1B[31mError: Bundled catalog.yaml not found.\x1B[0m");
5891
- process.exit(1);
5892
- }
5893
- const content = readFileSync6(catalogPath, "utf8");
5894
- const hash = computeSHA256(content);
5895
- console.log(` Verification Type: Local verification (no network required)`);
5896
- console.log(` File: .ai/plugins/catalog.yaml`);
5897
- console.log(` SHA256 Checksum: ${hash}`);
5898
- console.log(` Status: \x1B[32m\u2713 Present and readable (Integrity Verified)\x1B[0m`);
5899
- try {
5900
- const parsed = parseYaml(content);
5901
- const pluginCount = ((parsed.catalog || {}).plugins || []).length;
5902
- console.log(` Plugins: ${pluginCount} entries parsed successfully`);
5903
- console.log(`
5904
- \x1B[32m\u2714 Bundled registry verification passed. (Offline & Secure)\x1B[0m
5905
- `);
5906
- } catch (e) {
5907
- console.error(`
5908
- \x1B[31m\u2717 Bundled registry verification failed: ${e.message}\x1B[0m
5909
- `);
5910
- process.exit(1);
5911
- }
5912
- return;
5913
- }
6355
+ const projectDir = options.target || process.cwd();
6356
+ const policy = loadRegistryPolicy(projectDir);
5914
6357
  const sources = loadRegistrySources();
5915
6358
  const source = sources.find((s) => s.name === name);
5916
- if (source && source.type !== "local") {
5917
- const policy = loadRegistryPolicy(options.target || process.cwd());
6359
+ let isBundled = name === "bundled";
6360
+ let isLocal = source ? source.type === "local" : false;
6361
+ let isRemote = source ? source.type === "remote" : false;
6362
+ let url = source ? source.url : isBundled ? ".ai/plugins/catalog.yaml" : null;
6363
+ if (!source && !isBundled) {
6364
+ console.error(`\x1B[31mError: Registry '${name}' is not configured.\x1B[0m`);
6365
+ process.exit(1);
6366
+ }
6367
+ let urlValidationStatus = "N/A";
6368
+ if (isRemote) {
5918
6369
  try {
5919
- validateRegistryUrl(source.url, policy);
6370
+ validateRegistryUrl(url, policy);
6371
+ urlValidationStatus = "\x1B[32m\u2713 Valid HTTPS\x1B[0m";
5920
6372
  } catch (err) {
5921
- console.error(`\x1B[31mError: Registry '${name}' has an invalid URL: ${err.message}\x1B[0m`);
5922
- process.exit(1);
6373
+ urlValidationStatus = `\x1B[31m\u2717 Invalid: ${err.message}\x1B[0m`;
5923
6374
  }
6375
+ } else {
6376
+ urlValidationStatus = "\x1B[32m\u2713 Valid Local Path\x1B[0m";
5924
6377
  }
5925
- const cacheDir = join5(sourceRoot, ".ai", "registry-cache", name);
5926
- if (!existsSync5(cacheDir)) {
6378
+ let cacheDir;
6379
+ if (isBundled) {
6380
+ cacheDir = join8(sourceRoot, ".ai", "plugins");
6381
+ } else {
6382
+ cacheDir = join8(sourceRoot, ".ai", "registry-cache", name);
6383
+ }
6384
+ const catalogDest = join8(cacheDir, "catalog.yaml");
6385
+ const manifestDest = join8(cacheDir, "manifest.json");
6386
+ const checksumPath = join8(cacheDir, "checksums.json");
6387
+ if (!isBundled && !existsSync8(cacheDir)) {
5927
6388
  console.error(`\x1B[31mError: No cache found for registry '${name}'. Run registry sync first.\x1B[0m`);
5928
6389
  process.exit(1);
5929
6390
  }
5930
- const checksumPath = join5(cacheDir, "checksums.json");
5931
- if (!existsSync5(checksumPath)) {
5932
- console.error(`\x1B[31mError: No checksums.json found in cache for '${name}'.\x1B[0m`);
6391
+ if (isBundled && !existsSync8(catalogDest)) {
6392
+ console.error(`\x1B[31mError: Bundled catalog.yaml not found.\x1B[0m`);
5933
6393
  process.exit(1);
5934
6394
  }
5935
- try {
5936
- const checksums = JSON.parse(readFileSync6(checksumPath, "utf8"));
5937
- let allPassed = true;
5938
- Object.entries(checksums).forEach(([file, expectedHash]) => {
5939
- const filePath = join5(cacheDir, file);
5940
- if (!existsSync5(filePath)) {
5941
- console.log(` \x1B[31m\u2717 ${file}: MISSING\x1B[0m`);
5942
- allPassed = false;
5943
- return;
6395
+ let catalogContent = "";
6396
+ let catalogHash = "N/A";
6397
+ if (existsSync8(catalogDest)) {
6398
+ catalogContent = readFileSync9(catalogDest, "utf8");
6399
+ catalogHash = computeSHA256(catalogContent);
6400
+ }
6401
+ let manifestObj = null;
6402
+ let manifestHash = "N/A";
6403
+ if (existsSync8(manifestDest)) {
6404
+ const manifestData = readFileSync9(manifestDest, "utf8");
6405
+ manifestHash = computeSHA256(manifestData);
6406
+ try {
6407
+ manifestObj = JSON.parse(manifestData);
6408
+ } catch (e) {
6409
+ console.warn(`\x1B[33mWarning: Failed to parse manifest.json: ${e.message}\x1B[0m`);
6410
+ }
6411
+ }
6412
+ let integrityVerified = true;
6413
+ if (!isBundled) {
6414
+ if (!existsSync8(checksumPath)) {
6415
+ console.log(` \x1B[33m\u26A0 Checksums: Missing checksums.json in cache\x1B[0m`);
6416
+ integrityVerified = false;
6417
+ } else {
6418
+ try {
6419
+ const checksums = JSON.parse(readFileSync9(checksumPath, "utf8"));
6420
+ Object.entries(checksums).forEach(([file, expectedHash]) => {
6421
+ const filePath = join8(cacheDir, file);
6422
+ if (!existsSync8(filePath)) {
6423
+ console.log(` \x1B[31m\u2717 File missing in cache: ${file}\x1B[0m`);
6424
+ integrityVerified = false;
6425
+ return;
6426
+ }
6427
+ const content = readFileSync9(filePath, "utf8");
6428
+ const actualHash = `sha256:${computeSHA256(content)}`;
6429
+ if (actualHash === expectedHash) {
6430
+ console.log(` \x1B[32m\u2713 ${file}: VERIFIED (Integrity check matched via SHA-256)\x1B[0m`);
6431
+ } else {
6432
+ console.log(` \x1B[31m\u2717 ${file}: MISMATCH\x1B[0m`);
6433
+ console.log(` Expected: ${expectedHash}`);
6434
+ console.log(` Actual: ${actualHash}`);
6435
+ integrityVerified = false;
6436
+ }
6437
+ });
6438
+ } catch (e) {
6439
+ console.log(` \x1B[31m\u2717 Integrity: Failed to verify checksums: ${e.message}\x1B[0m`);
6440
+ integrityVerified = false;
5944
6441
  }
5945
- const content = readFileSync6(filePath, "utf8");
5946
- const actualHash = `sha256:${computeSHA256(content)}`;
5947
- if (actualHash === expectedHash) {
5948
- console.log(` \x1B[32m\u2713 ${file}: VERIFIED (Integrity check matched via SHA-256)\x1B[0m`);
6442
+ }
6443
+ }
6444
+ const lockfile = loadRegistryLockfile(projectDir);
6445
+ const lockEntry = lockfile.entries[name];
6446
+ let lockfileStatus = "N/A";
6447
+ let provenanceStatus = "N/A";
6448
+ let lockfileVerdict = "N/A";
6449
+ if (!isBundled) {
6450
+ const lockfilePath = getLockfilePath(projectDir);
6451
+ lockfileStatus = existsSync8(lockfilePath) ? `\x1B[32mpresent\x1B[0m` : `\x1B[33mmissing\x1B[0m`;
6452
+ if (!lockEntry) {
6453
+ if (policy.require_lockfile_on_verify) {
6454
+ provenanceStatus = `\x1B[31m\u2717 Failed (require_lockfile_on_verify is true but entry missing)\x1B[0m`;
6455
+ lockfileVerdict = "Failed";
6456
+ } else {
6457
+ provenanceStatus = `\x1B[33m\u26A0 Missing provenance entry (no sync lock)\x1B[0m`;
6458
+ lockfileVerdict = "Missing";
6459
+ }
6460
+ } else {
6461
+ let isProvMatch = true;
6462
+ if (catalogHash !== lockEntry.catalog_sha256) {
6463
+ isProvMatch = false;
6464
+ console.log(` \x1B[31m\u2717 Lockfile catalog hash mismatch: Expected ${lockEntry.catalog_sha256}, got ${catalogHash}\x1B[0m`);
6465
+ }
6466
+ if (manifestHash !== "N/A" && lockEntry.manifest_sha256 && manifestHash !== lockEntry.manifest_sha256) {
6467
+ isProvMatch = false;
6468
+ console.log(` \x1B[31m\u2717 Lockfile manifest hash mismatch: Expected ${lockEntry.manifest_sha256}, got ${manifestHash}\x1B[0m`);
6469
+ }
6470
+ if (isProvMatch) {
6471
+ provenanceStatus = `\x1B[32m\u2713 Matched lockfile entry\x1B[0m`;
6472
+ lockfileVerdict = "Verified";
5949
6473
  } else {
5950
- console.log(` \x1B[31m\u2717 ${file}: MISMATCH\x1B[0m`);
5951
- console.log(` Expected: ${expectedHash}`);
5952
- console.log(` Actual: ${actualHash}`);
5953
- allPassed = false;
6474
+ provenanceStatus = `\x1B[31m\u2717 Tampering detected: hashes do not match lockfile\x1B[0m`;
6475
+ lockfileVerdict = "Tampered";
5954
6476
  }
6477
+ }
6478
+ } else {
6479
+ lockfileStatus = "N/A (Bundled)";
6480
+ provenanceStatus = "\x1B[32m\u2713 Implicit Trust\x1B[0m";
6481
+ lockfileVerdict = "Verified";
6482
+ }
6483
+ const trustedKeys = loadTrustedKeys(projectDir, policy);
6484
+ let hmacKey = null;
6485
+ try {
6486
+ hmacKey = loadSigningKey(projectDir);
6487
+ } catch (_e) {
6488
+ }
6489
+ let signatureAlgorithm = "None";
6490
+ let signatureKeyId = "None";
6491
+ let trustedPublisherStatus = "N/A";
6492
+ let signatureValidity = "N/A";
6493
+ let signatureResult = { verified: true, status: "unsigned" };
6494
+ if (manifestObj) {
6495
+ signatureResult = verifySignatureBlock({
6496
+ manifest: manifestObj,
6497
+ trustedKeys,
6498
+ policy,
6499
+ hmacKey,
6500
+ source: source || { name: "bundled", type: "local" }
5955
6501
  });
5956
- if (allPassed) {
5957
- console.log(`
6502
+ const signatureBlocks = [];
6503
+ if (manifestObj.signature && typeof manifestObj.signature === "object") {
6504
+ signatureBlocks.push(manifestObj.signature);
6505
+ }
6506
+ if (Array.isArray(manifestObj.signatures)) {
6507
+ signatureBlocks.push(...manifestObj.signatures);
6508
+ }
6509
+ if (signatureBlocks.length > 0) {
6510
+ const firstSig = signatureBlocks[0];
6511
+ signatureAlgorithm = firstSig.algorithm || "unknown";
6512
+ signatureKeyId = firstSig.key_id || "unknown";
6513
+ const tk = trustedKeys.find((k) => k.key_id === signatureKeyId);
6514
+ if (tk) {
6515
+ trustedPublisherStatus = tk.status === "active" ? `\x1B[32m\u2713 Trusted (${tk.name})\x1B[0m` : `\x1B[31m\u2717 ${tk.status} (${tk.name})\x1B[0m`;
6516
+ } else {
6517
+ trustedPublisherStatus = `\x1B[33m\u26A0 Unknown key_id (Not in trust store)\x1B[0m`;
6518
+ }
6519
+ if (signatureResult.verified) {
6520
+ signatureValidity = `\x1B[32m\u2713 Valid Signature\x1B[0m`;
6521
+ } else {
6522
+ const errorMsg = signatureResult.errors ? signatureResult.errors.join(", ") : signatureResult.error || "signature verification failed";
6523
+ signatureValidity = `\x1B[31m\u2717 Invalid Signature (${errorMsg})\x1B[0m`;
6524
+ }
6525
+ } else {
6526
+ if (policy.require_signature || isRemote && policy.allow_unsigned_remote === false) {
6527
+ signatureValidity = `\x1B[31m\u2717 Missing Signature (Enforced by policy)\x1B[0m`;
6528
+ } else {
6529
+ signatureValidity = `\x1B[90mUnsigned\x1B[0m`;
6530
+ }
6531
+ }
6532
+ } else {
6533
+ if (!isBundled && (policy.require_signature || isRemote && policy.allow_unsigned_remote === false)) {
6534
+ signatureResult = { verified: false, error: "Manifest missing but signature is required by policy." };
6535
+ signatureValidity = `\x1B[31m\u2717 Manifest missing (Enforced by policy)\x1B[0m`;
6536
+ } else {
6537
+ signatureValidity = `\x1B[90mUnsigned (No manifest)\x1B[0m`;
6538
+ }
6539
+ }
6540
+ console.log(` Source Type: ${isBundled ? "bundled" : source.type}`);
6541
+ console.log(` Source URL/Path: ${url}`);
6542
+ console.log(` URL Validation: ${urlValidationStatus}`);
6543
+ console.log(` Manifest SHA256: ${manifestHash}`);
6544
+ console.log(` Catalog SHA256: ${catalogHash}`);
6545
+ console.log(` Lockfile Status: ${lockfileStatus}`);
6546
+ console.log(` Provenance Status: ${provenanceStatus}`);
6547
+ console.log(` Signature Alg: ${signatureAlgorithm}`);
6548
+ console.log(` Signature Key ID: ${signatureKeyId}`);
6549
+ console.log(` Trusted Publisher: ${trustedPublisherStatus}`);
6550
+ console.log(` Signature Validity: ${signatureValidity}`);
6551
+ let finalVerdict = "\u2717 Failed";
6552
+ let passed = true;
6553
+ if (!integrityVerified)
6554
+ passed = false;
6555
+ if (!isBundled && lockfileVerdict === "Failed")
6556
+ passed = false;
6557
+ if (!isBundled && lockfileVerdict === "Tampered")
6558
+ passed = false;
6559
+ if (!signatureResult.verified)
6560
+ passed = false;
6561
+ if (passed) {
6562
+ if (signatureResult.status === "verified") {
6563
+ finalVerdict = `\x1B[32m\u2713 Verified (Signature matches trusted key)\x1B[0m`;
6564
+ } else if (isBundled || isLocal) {
6565
+ finalVerdict = `\x1B[32m\u2713 Verified (Implicit local trust)\x1B[0m`;
6566
+ } else {
6567
+ finalVerdict = `\x1B[33m\u26A0 Unsigned (Allowed by policy)\x1B[0m`;
6568
+ }
6569
+ } else {
6570
+ const reason = !integrityVerified ? "Integrity check failed" : lockfileVerdict === "Tampered" ? "Lockfile tampering detected" : signatureResult.error || signatureResult.errors && signatureResult.errors.join(", ") || "Signature verification failed";
6571
+ finalVerdict = `\x1B[31m\u2717 Failed (${reason})\x1B[0m`;
6572
+ }
6573
+ console.log(` Final Trust: ${finalVerdict}`);
6574
+ console.log("==================================================");
6575
+ try {
6576
+ const parsed = parseYaml(catalogContent);
6577
+ const pluginCount = ((parsed.catalog || {}).plugins || []).length;
6578
+ console.log(` Plugins Parsed: ${pluginCount} entries`);
6579
+ } catch (e) {
6580
+ console.error(`\x1B[31m\u2717 Catalog parsing failed: ${e.message}\x1B[0m`);
6581
+ process.exit(1);
6582
+ }
6583
+ const verdict = createTrustVerdict({
6584
+ source: name,
6585
+ source_type: isBundled ? "bundled" : source ? source.type : "remote",
6586
+ manifest_hash_status: manifestHash !== "N/A" ? lockEntry && manifestHash === lockEntry.manifest_sha256 ? "verified" : manifestObj ? "unverified" : "missing" : "N/A",
6587
+ catalog_hash_status: catalogHash !== "N/A" ? lockEntry && catalogHash === lockEntry.catalog_sha256 ? "verified" : "unverified" : "N/A",
6588
+ lockfile_status: isBundled ? "N/A" : lockEntry ? "present" : "missing",
6589
+ provenance_status: isBundled ? "N/A" : lockEntry ? lockfileVerdict === "Verified" ? "matched" : lockfileVerdict === "Tampered" ? "mismatch" : "missing" : "N/A",
6590
+ signature_status: signatureResult.status || "unsigned",
6591
+ trusted_publisher_status: signatureResult.status === "verified" ? "trusted" : "N/A",
6592
+ errors: signatureResult.errors || (signatureResult.error ? [signatureResult.error] : []),
6593
+ warnings: signatureResult.warning ? [signatureResult.warning] : [],
6594
+ final_status: passed ? signatureResult.status === "verified" ? "trusted" : isBundled || isLocal ? "trusted" : "warning" : "untrusted"
6595
+ });
6596
+ if (!isBundled && lockEntry) {
6597
+ lockEntry.trust_verdict = passed ? signatureResult.status === "verified" ? "verified" : "unsigned_allowed" : "failed";
6598
+ lockEntry.lockfile_verdict = lockfileVerdict.toLowerCase();
6599
+ lockEntry.verification_errors = verdict.errors;
6600
+ lockEntry.verification_warnings = verdict.warnings;
6601
+ lockEntry.verdict = verdict;
6602
+ saveRegistryLockfile(projectDir, lockfile);
6603
+ }
6604
+ if (passed) {
6605
+ console.log(`
5958
6606
  \x1B[32m\u2714 Registry '${name}' verification passed.\x1B[0m
5959
6607
  `);
5960
- } else {
5961
- console.error(`
5962
- \x1B[31m\u2717 Registry '${name}' verification failed. Re-sync recommended.\x1B[0m
6608
+ } else {
6609
+ console.error(`
6610
+ \x1B[31m\u2717 Registry '${name}' verification failed.\x1B[0m
5963
6611
  `);
5964
- process.exit(1);
6612
+ process.exit(1);
6613
+ }
6614
+ }
6615
+ function handleRegistryTrustList(options) {
6616
+ const projectDir = options.target || process.cwd();
6617
+ const policy = loadRegistryPolicy(projectDir);
6618
+ const keys = loadTrustedKeys(projectDir, policy);
6619
+ console.log(`
6620
+ \u{1F511} \x1B[36mRegistry Trust Store \u2014 Trusted Keys\x1B[0m`);
6621
+ console.log("==================================================");
6622
+ console.log(`Trust Store Path: \x1B[36m${policy.trusted_keys_file || ".ai/registries/trusted-keys.yaml"}\x1B[0m`);
6623
+ console.log(`Total Keys: ${keys.length}
6624
+ `);
6625
+ if (keys.length === 0) {
6626
+ console.log(" No trusted keys configured.");
6627
+ } else {
6628
+ keys.forEach((k) => {
6629
+ const statusBadge = k.status === "active" ? "\x1B[32m\u25CF active\x1B[0m" : `\x1B[31m\u25CB ${k.status}\x1B[0m`;
6630
+ console.log(` * \x1B[33m${k.key_id}\x1B[0m [${statusBadge}]`);
6631
+ console.log(` Publisher: ${k.name}`);
6632
+ console.log(` Algorithm: ${k.algorithm}`);
6633
+ console.log(` Scopes: ${(k.scopes || []).join(", ")}`);
6634
+ });
6635
+ }
6636
+ console.log("");
6637
+ }
6638
+ function handleRegistryTrustShow(keyId, options) {
6639
+ const projectDir = options.target || process.cwd();
6640
+ const policy = loadRegistryPolicy(projectDir);
6641
+ const keys = loadTrustedKeys(projectDir, policy);
6642
+ const k = keys.find((key) => key.key_id === keyId);
6643
+ if (!k) {
6644
+ console.error(`\x1B[31mError: Trusted key '${keyId}' not found in the trust store.\x1B[0m`);
6645
+ process.exit(1);
6646
+ }
6647
+ console.log(`
6648
+ \u{1F511} \x1B[36mTrusted Key: ${keyId}\x1B[0m`);
6649
+ console.log("==================================================");
6650
+ console.log(`\x1B[33mKey ID:\x1B[0m ${k.key_id}`);
6651
+ console.log(`\x1B[33mPublisher:\x1B[0m ${k.name}`);
6652
+ console.log(`\x1B[33mAlgorithm:\x1B[0m ${k.algorithm}`);
6653
+ console.log(`\x1B[33mStatus:\x1B[0m ${k.status === "active" ? "\x1B[32mactive\x1B[0m" : `\x1B[31m${k.status}\x1B[0m`}`);
6654
+ console.log(`\x1B[33mScopes:\x1B[0m ${(k.scopes || []).join(", ")}`);
6655
+ console.log(`\x1B[33mPublic Key:\x1B[0m
6656
+ ${k.public_key.trim()}`);
6657
+ console.log("");
6658
+ }
6659
+ function handleRegistryTrustVerify(options) {
6660
+ const projectDir = options.target || process.cwd();
6661
+ const policy = loadRegistryPolicy(projectDir);
6662
+ const keys = loadTrustedKeys(projectDir, policy);
6663
+ console.log(`
6664
+ \u{1F511} \x1B[36mVerifying Trust Store Integrity...\x1B[0m`);
6665
+ console.log("==================================================");
6666
+ let passed = true;
6667
+ keys.forEach((k) => {
6668
+ try {
6669
+ normalizePublicKey(k.public_key);
6670
+ console.log(` \x1B[32m\u2713\x1B[0m Key '${k.key_id}' public key format is valid.`);
6671
+ } catch (e) {
6672
+ console.log(` \x1B[31m\u2717\x1B[0m Key '${k.key_id}' public key format error: ${e.message}`);
6673
+ passed = false;
5965
6674
  }
5966
- } catch (e) {
5967
- console.error(`\x1B[31mError: Failed to read checksums: ${e.message}\x1B[0m`);
6675
+ });
6676
+ if (passed) {
6677
+ console.log(`
6678
+ \x1B[32m\u2714 Trust store verification passed.\x1B[0m
6679
+ `);
6680
+ } else {
6681
+ console.error(`
6682
+ \x1B[31m\u2717 Trust store verification failed.\x1B[0m
6683
+ `);
5968
6684
  process.exit(1);
5969
6685
  }
5970
6686
  }
@@ -6012,12 +6728,12 @@ function handleRegistryShow(name, options) {
6012
6728
  console.log(`\x1B[33mPinned Hash:\x1B[0m ${source.pinned_commit_or_hash}`);
6013
6729
  }
6014
6730
  if (source.type !== "local") {
6015
- const cacheDir = join5(sourceRoot, ".ai", "registry-cache", name);
6016
- if (existsSync5(cacheDir)) {
6017
- const catalogPath = join5(cacheDir, "catalog.yaml");
6018
- if (existsSync5(catalogPath)) {
6731
+ const cacheDir = join8(sourceRoot, ".ai", "registry-cache", name);
6732
+ if (existsSync8(cacheDir)) {
6733
+ const catalogPath = join8(cacheDir, "catalog.yaml");
6734
+ if (existsSync8(catalogPath)) {
6019
6735
  try {
6020
- const parsed = parseYaml(readFileSync6(catalogPath, "utf8"));
6736
+ const parsed = parseYaml(readFileSync9(catalogPath, "utf8"));
6021
6737
  const count = ((parsed.catalog || {}).plugins || []).length;
6022
6738
  console.log(`\x1B[33mCached Plugins:\x1B[0m ${count} entries`);
6023
6739
  } catch (e) {
@@ -6043,8 +6759,8 @@ function handleRegistryShow(name, options) {
6043
6759
  function handleRegistryCacheClear(options) {
6044
6760
  if (!options.approved) {
6045
6761
  console.error("\x1B[31mError: Cache cannot be cleared without explicit approval. Pass the --approved flag.\x1B[0m");
6046
- const cacheRoot2 = join5(sourceRoot, ".ai", "registry-cache");
6047
- if (existsSync5(cacheRoot2)) {
6762
+ const cacheRoot2 = join8(sourceRoot, ".ai", "registry-cache");
6763
+ if (existsSync8(cacheRoot2)) {
6048
6764
  const dirs = readdirSync(cacheRoot2).filter((d) => d !== "README.md");
6049
6765
  console.log(`
6050
6766
  \x1B[33mPlanned Action:\x1B[0m Clear ${dirs.length} cached registry directories:`);
@@ -6058,22 +6774,22 @@ Run with --approved to apply:
6058
6774
  `);
6059
6775
  process.exit(1);
6060
6776
  }
6061
- const cacheRoot = join5(sourceRoot, ".ai", "registry-cache");
6062
- if (!existsSync5(cacheRoot)) {
6777
+ const cacheRoot = join8(sourceRoot, ".ai", "registry-cache");
6778
+ if (!existsSync8(cacheRoot)) {
6063
6779
  console.log("\n\x1B[33mNo registry cache directory found. Nothing to clear.\x1B[0m\n");
6064
6780
  return;
6065
6781
  }
6066
6782
  const entries = readdirSync(cacheRoot).filter((d) => d !== "README.md");
6067
6783
  let cleared = 0;
6068
6784
  entries.forEach((d) => {
6069
- const dirPath = join5(cacheRoot, d);
6785
+ const dirPath = join8(cacheRoot, d);
6070
6786
  try {
6071
6787
  if (statSync(dirPath).isDirectory()) {
6072
6788
  const files = readdirSync(dirPath);
6073
6789
  files.forEach((f) => {
6074
- const fp = join5(dirPath, f);
6790
+ const fp = join8(dirPath, f);
6075
6791
  if (statSync(fp).isFile()) {
6076
- writeFileSync2(fp, "");
6792
+ writeFileSync4(fp, "");
6077
6793
  }
6078
6794
  });
6079
6795
  cleared++;
@@ -6087,3 +6803,105 @@ Run with --approved to apply:
6087
6803
  console.log(` Cache root: .ai/registry-cache/
6088
6804
  `);
6089
6805
  }
6806
+ function handleRegistryKeygen(options) {
6807
+ const projectDir = options.target || process.cwd();
6808
+ const keyPath = getSigningKeyPath(projectDir);
6809
+ console.log(`
6810
+ \u{1F511} \x1B[36mRegistry Signing Key Generator\x1B[0m`);
6811
+ console.log("==================================================");
6812
+ if (!options.approved) {
6813
+ console.error("\x1B[31mError: Signing key generation requires explicit approval. Pass the --approved flag.\x1B[0m");
6814
+ console.log(`
6815
+ \x1B[33mPlanned Action:\x1B[0m Generate a 32-byte random HMAC-SHA256 signing key.`);
6816
+ console.log(` Destination: ${keyPath}`);
6817
+ console.log(` Mode: 0o600 (owner read/write only)`);
6818
+ console.log(`
6819
+ \x1B[33mSecurity Notes:\x1B[0m`);
6820
+ console.log(` \u2022 Add .ai/registry-signing-key to your .gitignore`);
6821
+ console.log(` \u2022 Share the key securely with trusted team members for co-verification`);
6822
+ console.log(` \u2022 The key is used for HMAC-SHA256 signing of catalog checksums only`);
6823
+ console.log(`
6824
+ To generate, run:`);
6825
+ console.log(` \x1B[36mnpx multimodel-dev-os registry keygen --approved\x1B[0m
6826
+ `);
6827
+ process.exit(1);
6828
+ }
6829
+ let existingKey = null;
6830
+ try {
6831
+ existingKey = loadSigningKey(projectDir);
6832
+ } catch (_e) {
6833
+ }
6834
+ if (existingKey && !options.force) {
6835
+ console.error(`\x1B[31mError: A signing key already exists at: ${keyPath}\x1B[0m`);
6836
+ console.log(`
6837
+ To overwrite, run with --force:`);
6838
+ console.log(` \x1B[36mnpx multimodel-dev-os registry keygen --approved --force\x1B[0m`);
6839
+ console.log(`
6840
+ \x1B[33mWarning:\x1B[0m Overwriting will invalidate all existing signatures in the lockfile.
6841
+ `);
6842
+ process.exit(1);
6843
+ }
6844
+ const newKey = generateSigningKey();
6845
+ saveSigningKey(projectDir, newKey);
6846
+ console.log(`
6847
+ \x1B[32m\u2714 Signing key generated successfully!\x1B[0m`);
6848
+ console.log(` Location: ${keyPath}`);
6849
+ console.log(` Mode: 0o600 (restricted permissions)`);
6850
+ console.log(`
6851
+ \x1B[33mNext steps:\x1B[0m`);
6852
+ console.log(` 1. Add to .gitignore: echo '.ai/registry-signing-key' >> .gitignore`);
6853
+ console.log(` 2. Re-sync registries to generate signed lockfile entries:`);
6854
+ console.log(` npx multimodel-dev-os registry sync <name> --approved`);
6855
+ console.log(` 3. Verify signed provenance:`);
6856
+ console.log(` npx multimodel-dev-os registry verify <name>
6857
+ `);
6858
+ }
6859
+ function handleRegistryLock(options) {
6860
+ const projectDir = options.target || process.cwd();
6861
+ const lockfilePath = getLockfilePath(projectDir);
6862
+ console.log(`
6863
+ \u{1F512} \x1B[36mRegistry Provenance Lockfile\x1B[0m`);
6864
+ console.log("==================================================");
6865
+ if (!existsSync8(lockfilePath)) {
6866
+ console.log(` \x1B[90mNo lockfile found at: ${lockfilePath}\x1B[0m`);
6867
+ console.log(` Sync a remote registry to create it:`);
6868
+ console.log(` npx multimodel-dev-os registry sync <name> --approved
6869
+ `);
6870
+ return;
6871
+ }
6872
+ const lockfile = loadRegistryLockfile(projectDir);
6873
+ const entries = Object.entries(lockfile.entries);
6874
+ if (options.json) {
6875
+ console.log(JSON.stringify(lockfile, null, 2));
6876
+ return;
6877
+ }
6878
+ console.log(` Lockfile version: ${lockfile.lockfile_version}`);
6879
+ console.log(` Generated at: ${lockfile.generated_at}`);
6880
+ console.log(` Path: ${lockfilePath}`);
6881
+ console.log(` Entries: ${entries.length}
6882
+ `);
6883
+ if (entries.length === 0) {
6884
+ console.log(` \x1B[90mNo registry entries recorded yet.\x1B[0m`);
6885
+ console.log(` Sync a remote registry to populate:
6886
+ npx multimodel-dev-os registry sync <name> --approved
6887
+ `);
6888
+ return;
6889
+ }
6890
+ entries.forEach(([name, entry]) => {
6891
+ const sigBadge = entry.signature ? `\x1B[32m[SIGNED \u2014 HMAC-SHA256]\x1B[0m` : `\x1B[33m[UNSIGNED]\x1B[0m`;
6892
+ console.log(` \x1B[32m${name}\x1B[0m ${sigBadge}`);
6893
+ console.log(` URL: ${entry.url}`);
6894
+ console.log(` Synced at: ${entry.synced_at}`);
6895
+ console.log(` Catalog SHA-256: ${entry.catalog_sha256}`);
6896
+ if (entry.manifest_sha256) {
6897
+ console.log(` Manifest SHA256: ${entry.manifest_sha256}`);
6898
+ }
6899
+ if (entry.signature) {
6900
+ console.log(` Signature: ${entry.signature.slice(0, 24)}...`);
6901
+ console.log(` Sig algorithm: ${entry.signature_alg}`);
6902
+ }
6903
+ console.log("");
6904
+ });
6905
+ console.log("Use \x1B[36mregistry verify <name>\x1B[0m to re-verify cached files against the lockfile.");
6906
+ console.log("Use \x1B[36mregistry keygen --approved\x1B[0m to generate a signing key for HMAC signatures.\n");
6907
+ }