@hybridlabor-api/aos 4.16.0 → 4.18.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 (147) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude/hooks/aos-bus.mjs +8 -4
  3. package/.claude/hooks/env-file-protection.mjs +19 -1
  4. package/.claude/hooks/go-gate.mjs +788 -69
  5. package/.claude/hooks/go-grant.mjs +88 -0
  6. package/.claude/hooks/go-token.mjs +17 -2
  7. package/.claude/hooks/memb-inject.mjs +61 -37
  8. package/.claude/settings.json +8 -0
  9. package/.codex-plugin/plugin.json +36 -6
  10. package/.opencode/commands/bdb-aos-brainstorm.md +5 -0
  11. package/.opencode/commands/bdb-aos-doctor.md +5 -0
  12. package/.opencode/commands/bdb-aos-graph.md +5 -0
  13. package/.opencode/commands/bdb-aos-init.md +5 -0
  14. package/.opencode/commands/bdb-aos-loop.md +5 -0
  15. package/.opencode/commands/bdb-aos-mastersession.md +5 -0
  16. package/.opencode/commands/bdb-aos-memb.md +5 -0
  17. package/.opencode/commands/bdb-aos-orchestrator.md +5 -0
  18. package/.opencode/commands/bdb-aos-plan.md +5 -0
  19. package/.opencode/commands/bdb-aos-playbooks.md +5 -0
  20. package/.opencode/commands/bdb-aos-setup.md +5 -0
  21. package/.opencode/commands/bdb-aos-shipping.md +5 -0
  22. package/.opencode/commands/bdb-aos-startproject.md +5 -0
  23. package/.opencode/commands/bdb-aos-store.md +5 -0
  24. package/.opencode/plugins/bdb-aos.js +54 -10
  25. package/README.md +5 -4
  26. package/THIRD_PARTY_NOTICES.md +2 -2
  27. package/agy-commands/loop.md +5 -0
  28. package/bin/aos-acp.mjs +27 -1
  29. package/bin/aos-doctor.mjs +41 -4
  30. package/bin/aos-uninstall.mjs +41 -3
  31. package/bin/go-check.mjs +79 -0
  32. package/bin/guarded-patterns.json +106 -0
  33. package/commands/brainstorm.md +5 -0
  34. package/commands/doctor.md +5 -0
  35. package/commands/graph.md +5 -0
  36. package/commands/init.md +5 -0
  37. package/commands/loop.md +5 -0
  38. package/commands/mastersession.md +5 -0
  39. package/commands/memb.md +5 -0
  40. package/commands/orchestrator.md +5 -0
  41. package/commands/plan.md +5 -0
  42. package/commands/playbooks.md +5 -0
  43. package/commands/setup.md +5 -0
  44. package/commands/shipping.md +5 -0
  45. package/commands/startproject.md +5 -0
  46. package/commands/store.md +5 -0
  47. package/docs/codenotch.md +44 -0
  48. package/docs/codex-agy-setup.md +16 -0
  49. package/docs/codex-gate-smoke.md +43 -0
  50. package/docs/delegation-routing.md +32 -0
  51. package/docs/go-check.md +60 -0
  52. package/docs/master-session-acp.md +2 -0
  53. package/docs/opencode-setup.md +54 -0
  54. package/docs/plugin-migration.md +61 -0
  55. package/installer.js +351 -121
  56. package/lib/codenotch.js +389 -0
  57. package/lib/plugin-migration.js +462 -0
  58. package/lib/retired-skills.js +101 -0
  59. package/lib/store-ui/index.html +9 -1
  60. package/lib/store-ui/server.mjs +2 -0
  61. package/mcps/mcsc/README.md +1 -1
  62. package/mcps/mcsc/packages/core/src/adapters/agy.js +3 -1
  63. package/mcps/mcsc/packages/core/src/adapters/codex.js +2 -1
  64. package/mcps/mcsc/packages/core/src/adapters/opencode.js +2 -1
  65. package/mcps/mcsc/packages/core/src/depth.js +16 -0
  66. package/mcps/mcsc/packages/mcp/server.js +15 -2
  67. package/package.json +7 -2
  68. package/plugin-commands.json +143 -0
  69. package/plugin.json +299 -0
  70. package/plugins/bdb-aos-codex/.codex-plugin/plugin.json +38 -0
  71. package/plugins/bdb-aos-codex/skills/brainstorm/SKILL.md +6 -0
  72. package/plugins/bdb-aos-codex/skills/doctor/SKILL.md +6 -0
  73. package/plugins/bdb-aos-codex/skills/graph/SKILL.md +6 -0
  74. package/plugins/bdb-aos-codex/skills/init/SKILL.md +6 -0
  75. package/plugins/bdb-aos-codex/skills/loop/SKILL.md +6 -0
  76. package/plugins/bdb-aos-codex/skills/mastersession/SKILL.md +6 -0
  77. package/plugins/bdb-aos-codex/skills/memb/SKILL.md +6 -0
  78. package/plugins/bdb-aos-codex/skills/orchestrator/SKILL.md +6 -0
  79. package/plugins/bdb-aos-codex/skills/plan/SKILL.md +6 -0
  80. package/plugins/bdb-aos-codex/skills/playbooks/SKILL.md +6 -0
  81. package/plugins/bdb-aos-codex/skills/setup/SKILL.md +6 -0
  82. package/plugins/bdb-aos-codex/skills/shipping/SKILL.md +6 -0
  83. package/plugins/bdb-aos-codex/skills/startproject/SKILL.md +6 -0
  84. package/plugins/bdb-aos-codex/skills/store/SKILL.md +6 -0
  85. package/scripts/build-plugin-manifest.mjs +202 -3
  86. package/scripts/codex-gate-smoke.mjs +73 -0
  87. package/skills/basic/master-session/SKILL.md +11 -0
  88. package/skills/bdb-aos/scripts/list-playbooks.mjs +72 -0
  89. package/skills/global_config/agenttrail/SKILL.md +3 -1
  90. package/skills/global_config/agenttrail/bin/agenttrail.mjs +255 -117
  91. package/skills/global_config/agenttrail/bin/ensure.mjs +60 -40
  92. package/skills/global_config/agenttrail/bin/repoid.mjs +70 -0
  93. package/skills/global_config/agenttrail/public/index.html +9 -1
  94. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  95. package/skills/global_config/bdb-memb-mcp/SKILL.md +5 -4
  96. package/skills/global_config/bdb-visual-edit/SKILL.md +28 -32
  97. package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +2 -2
  98. package/skills/global_config/bdb-visual-edit/scripts/locate-source.mjs +135 -0
  99. package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +30 -2
  100. package/skills/global_config/gogate/SKILL.md +123 -0
  101. package/skills/global_config/loop-templates/SKILL.md +27 -0
  102. package/skills/global_config/loop-templates/references/ci-until-green.md +27 -0
  103. package/skills/global_config/loop-templates/references/daily-summary.md +27 -0
  104. package/skills/global_config/loop-templates/references/pr-to-merge.md +27 -0
  105. package/skills/global_config/loop-templates/references/review-rounds.md +27 -0
  106. package/skills/global_config/mcsc/SKILL.md +9 -1
  107. package/skills/global_config/plan-arbiter/SKILL.md +1 -1
  108. package/skills/global_config/plan-canvas/SKILL.md +42 -4
  109. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +1 -1
  110. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +2 -2
  111. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/geometry.js +76 -0
  112. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/index.js +596 -0
  113. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/model.js +192 -0
  114. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/toolbar.js +99 -0
  115. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-server.js +282 -0
  116. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotation-schema.js +210 -0
  117. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/route.js +10 -0
  118. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sdk.js +6 -230
  119. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +45 -4
  120. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sessions.js +60 -21
  121. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/trail-on-approve.js +103 -0
  122. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +19 -7
  123. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +118 -20
  124. package/skills/global_config/subagent-setup/SKILL.md +6 -0
  125. package/skills/global_config/subagent-setup/scripts/setup-subagents.mjs +20 -1
  126. package/skills/playbooks/pb-idea-to-launch/SKILL.md +2 -2
  127. package/skills/playbooks/pb-redesign-app/SKILL.md +3 -3
  128. package/skills/playbooks/pb-release-aos/SKILL.md +2 -2
  129. package/skills/playbooks/pb-ship/SKILL.md +2 -2
  130. package/skills/playbooks/pb-worktrees-land/SKILL.md +2 -2
  131. package/.codex-plugin/marketplace.json +0 -11
  132. package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +0 -27
  133. package/skills/global_config/visual-edit/README.md +0 -96
  134. package/skills/global_config/visual-edit/SKILL.md +0 -615
  135. package/skills/global_config/visual-plan/README.md +0 -93
  136. package/skills/global_config/visual-plan/SKILL.md +0 -544
  137. package/skills/global_config/visual-plan/references/canvas.md +0 -139
  138. package/skills/global_config/visual-plan/references/connection.md +0 -51
  139. package/skills/global_config/visual-plan/references/document-quality.md +0 -186
  140. package/skills/global_config/visual-plan/references/exemplar.md +0 -62
  141. package/skills/global_config/visual-plan/references/local-files.md +0 -99
  142. package/skills/global_config/visual-plan/references/wireframe.md +0 -319
  143. package/skills/global_config/visual-recap/README.md +0 -103
  144. package/skills/global_config/visual-recap/SKILL.md +0 -560
  145. package/skills/global_config/visual-recap/references/connection.md +0 -51
  146. package/skills/global_config/visual-recap/references/local-files.md +0 -99
  147. package/skills/global_config/visual-recap/references/wireframe.md +0 -319
package/installer.js CHANGED
@@ -23,6 +23,8 @@ const net = require('net');
23
23
  const readline = require('readline');
24
24
  const util = require('util');
25
25
  const crypto = require('crypto');
26
+ const pluginMigration = require('./lib/plugin-migration');
27
+ const { pruneRetiredSkills } = require('./lib/retired-skills');
26
28
 
27
29
  function verifyDaemonListening(port, name, timeoutMs = 4000) {
28
30
  return new Promise((resolve) => {
@@ -682,25 +684,6 @@ function initSessionManifest(existingManifest, sourceDirs) {
682
684
  _sessionHashes = buildKnownSourceHashes(sourceDirs);
683
685
  }
684
686
 
685
- // Skills that left the package in 4.4.2 (Plan 02: migrated to the private
686
- // aos-internal repo, per ~/dev/_plans/aos-2026-09-14/), by their flattened
687
- // directory name at a sync destination.
688
- //
689
- // This is deliberately a fixed list, not a diff against what the current
690
- // payload ships. A generic "anything on disk that is not in skills/ right
691
- // now" pass was tried first and swept up two unrelated things: skill folders
692
- // that MCP servers bundle inside their own package tree (e.g.
693
- // mcps/tdmcp/.agents/skills/...), which happen to share the literal path
694
- // segment "skills" but belong to a different install entirely; and skills
695
- // under skills/workspace_agents/, which syncSkillsToGlobalHarnesses already
696
- // deliberately excludes from the six global destinations, so a stray global
697
- // copy of one says nothing about whether it is safe to delete. Both are real
698
- // cleanup opportunities, but they are a separate investigation, not a side
699
- // effect of this migration's beta test.
700
- const SKILLS_REMOVED_IN_4_4_2 = new Set([
701
- 'bdbsaastraining', 'bdb-dev-os-skill', 'bdbsaashost', 'bdb-ecosystem-health', 'bdbsaas-ops',
702
- ]);
703
-
704
687
  // The exact roots syncSkillsToGlobalHarnesses and the Gemini/Antigravity
705
688
  // config-skills path write into. Pruning only touches a manifest entry whose
706
689
  // path starts under one of these -- never one that merely contains a
@@ -717,51 +700,16 @@ function globalSkillDestRoots() {
717
700
  ];
718
701
  }
719
702
 
720
- // syncSkillsToGlobalHarnesses (and its sibling copy loops) only ever add or
721
- // update -- nothing in that path ever deletes a skill that the current
722
- // payload no longer ships. That is fine for new skills and updated ones, but
723
- // it means a skill removed from a release stays on every user's disk
724
- // forever, through every future update, because nothing after the removal
725
- // ever looks at what is on disk that no longer has a source. This walks the
726
- // install manifest -- which already has one entry per file this installer
727
- // has ever placed -- for the specific skills 4.4.2 removed, under the six
728
- // known global skill roots, and deletes them. A user-modified file is backed
729
- // up first, the same way resolveFileConflict backs up a user edit it is
730
- // about to overwrite, rather than silently deleting local changes.
703
+ // Retires skills the package no longer ships (lib/retired-skills.js). Edited or foreign files are
704
+ // copied to the backup dir before the skill folder is removed.
731
705
  function pruneRemovedSkills(manifest) {
732
- if (!manifest) return;
733
- const roots = globalSkillDestRoots();
734
-
735
- const staleDirs = new Set();
736
- let removedFiles = 0;
737
- for (const targetPath of Object.keys(manifest)) {
738
- const root = roots.find((r) => targetPath.startsWith(r + path.sep));
739
- if (!root) continue;
740
- const skillDirName = targetPath.slice(root.length + 1).split(path.sep)[0];
741
- if (!SKILLS_REMOVED_IN_4_4_2.has(skillDirName)) continue;
742
-
743
- if (fs.existsSync(targetPath)) {
744
- const diskHash = computeFileHash(targetPath);
745
- const manifestEntry = manifest[targetPath];
746
- if (diskHash !== null && manifestEntry && diskHash !== manifestEntry.sha256) {
747
- const bakPath = `${targetPath}.${timestamp}.bak`;
748
- try { fs.copyFileSync(targetPath, bakPath); } catch (e) { logDebug(e, 'prune-removed-skill backup'); }
749
- log.warn(`[manifest] Retired skill left a user-edited file, backed up to ${bakPath} instead of deleting it.`);
750
- } else {
751
- try { fs.unlinkSync(targetPath); removedFiles++; } catch (e) { logDebug(e, 'prune-removed-skill unlink'); }
752
- }
753
- staleDirs.add(path.join(root, skillDirName));
754
- }
755
- delete manifest[targetPath];
756
- }
757
-
758
- for (const dir of staleDirs) {
759
- try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e) { logDebug(e, 'prune-removed-skill rmdir'); }
760
- }
761
-
762
- if (staleDirs.size > 0) {
763
- log.step(`Removed ${removedFiles} file(s) from ${staleDirs.size} retired skill install(s) no longer shipped.`);
764
- }
706
+ pruneRetiredSkills({
707
+ home: homeDir,
708
+ manifest,
709
+ roots: globalSkillDestRoots(),
710
+ backupDir: path.join(homeDir, '.agents', 'backups', `retired-skills-${timestamp}`),
711
+ log,
712
+ });
765
713
  }
766
714
 
767
715
  function flushSessionManifest() {
@@ -1277,9 +1225,94 @@ function reportFatal(stage, e) {
1277
1225
  process.exitCode = 1;
1278
1226
  }
1279
1227
 
1228
+ // AOS_PLUGIN_MIGRATION=off skips plugin registration and loose-copy removal; =check only reports.
1229
+ function pluginMigrationMode(argv = process.argv, env = process.env) {
1230
+ const flag = argv.find((a) => a.startsWith('--plugin-migration='));
1231
+ const raw = String(flag ? flag.slice('--plugin-migration='.length) : (env.AOS_PLUGIN_MIGRATION || '')).trim().toLowerCase();
1232
+ if (raw === 'off' || raw === '0' || raw === 'false') return 'off';
1233
+ return raw === 'check' || DRY_RUN ? 'check' : 'on';
1234
+ }
1235
+
1236
+ // .agents/plugins holds the Codex marketplace file; it belongs to the repo checkout, not to ~/.agents or a project.
1237
+ const AGENTS_COPY_EXCLUDE = ['plugins'];
1238
+
1239
+ let _pluginMigration = null;
1240
+ function runPluginMigration({ targetHome = homeDir, detected = null, mode = pluginMigrationMode(), registrars } = {}) {
1241
+ if (_pluginMigration && targetHome === homeDir) return _pluginMigration;
1242
+ const keys = detected || detectPlatforms().map((d) => d.key);
1243
+ const ownsManifest = !_sessionManifest;
1244
+ const manifest = _sessionManifest || loadInstallManifest();
1245
+ let result;
1246
+ try {
1247
+ result = pluginMigration.migrate({ home: targetHome, manifest, detected: keys, mode, ...(registrars ? { registrars } : {}) });
1248
+ } catch (e) {
1249
+ log.warn(`Plugin migration skipped: ${e.message}`);
1250
+ result = { covered: new Set(), lines: [] };
1251
+ }
1252
+ for (const line of result.lines) log.step(line);
1253
+ if (ownsManifest && mode === 'on') saveInstallManifest(manifest);
1254
+ if (targetHome === homeDir) _pluginMigration = result;
1255
+ return result;
1256
+ }
1257
+
1258
+ // The plugin migration runs first: when it retires Claude's loose copies, ~/.claude/skills is not
1259
+ // written again, so a rerun neither recreates nor re-backs-up what the plugin now delivers.
1260
+ function installTargetSkills(targets, { mode, backupDir, excludeSkills, skillsBase }) {
1261
+ const detected = detectPlatforms().map((d) => d.key);
1262
+ if (targets.some((t) => t.value === '2') && !detected.includes('claudecode')) detected.push('claudecode');
1263
+ const pluginCovered = runPluginMigration({ detected }).covered;
1264
+
1265
+ for (const t of targets) {
1266
+ const viaPlugin = t.value === '2' && pluginCovered.has('claudecode');
1267
+ if (mode === 'replace') {
1268
+ if (!viaPlugin) {
1269
+ moveIfExists(t.targetSkillDir, path.join(backupDir, `config_skills_backup_${t.value}`), `global config skills (${t.value})`);
1270
+ moveIfExists(t.targetLegacyDir, path.join(backupDir, `legacy_skills_backup_${t.value}`), `legacy skills (${t.value})`);
1271
+ }
1272
+ moveIfExists(t.targetWorkspaceDir, path.join(backupDir, `workspace_skills_backup_${t.value}`), `workspace skills (${t.value})`);
1273
+ }
1274
+
1275
+ installStep(`create the skill target directories (${t.value})`, () => {
1276
+ if (!viaPlugin) {
1277
+ fs.mkdirSync(t.targetSkillDir, { recursive: true });
1278
+ retireObsoleteLegacyDir(t.targetLegacyDir);
1279
+ }
1280
+ fs.mkdirSync(t.targetWorkspaceDir, { recursive: true });
1281
+ }, 'The skill copies below will most likely be skipped as well.');
1282
+
1283
+ if (fs.existsSync(skillsBase)) {
1284
+ installStep(`install the skills (${t.value})`, () => {
1285
+ const rawDirs = fs.readdirSync(skillsBase);
1286
+ const dirs = rawDirs.sort((a, b) => {
1287
+ const aIsLeaf = fs.existsSync(path.join(skillsBase, a, 'SKILL.md'));
1288
+ const bIsLeaf = fs.existsSync(path.join(skillsBase, b, 'SKILL.md'));
1289
+ if (aIsLeaf && !bIsLeaf) return 1;
1290
+ if (!aIsLeaf && bIsLeaf) return -1;
1291
+ return 0;
1292
+ });
1293
+ for (const dir of dirs) {
1294
+ const fullPath = path.join(skillsBase, dir);
1295
+ if (!fs.statSync(fullPath).isDirectory()) continue;
1296
+
1297
+ if (viaPlugin && dir !== 'workspace_agents') continue;
1298
+ if (dir === 'global_legacy') {
1299
+ copyDirRecursiveSync(fullPath, t.targetLegacyDir, excludeSkills);
1300
+ } else if (dir === 'workspace_agents') {
1301
+ copyDirRecursiveSync(fullPath, t.targetWorkspaceDir, excludeSkills);
1302
+ } else {
1303
+ syncSkillEntry(fullPath, dir, t.targetSkillDir, excludeSkills);
1304
+ }
1305
+ }
1306
+ log.step(`Installed all global config & core skills to ${t.targetSkillDir}`);
1307
+ }, 'The skills are missing or incomplete; the rest of the installation continues.');
1308
+ }
1309
+ }
1310
+ }
1311
+
1280
1312
  function syncSkillsToGlobalHarnesses(excludeSkills = []) {
1281
1313
  const skillsBase = path.join(srcDir, 'skills');
1282
1314
  if (!fs.existsSync(skillsBase)) return;
1315
+ const pluginCovered = runPluginMigration().covered;
1283
1316
 
1284
1317
  // Mirror only into harnesses that are actually present. This list used to
1285
1318
  // be unconditional, which both wrote skills nobody would read and planted
@@ -1300,7 +1333,7 @@ function syncSkillsToGlobalHarnesses(excludeSkills = []) {
1300
1333
  { dir: path.join(homeDir, '.cursor', 'skills'), key: 'cursor' },
1301
1334
  { dir: path.join(homeDir, '.roo', 'skills'), key: 'vscode' },
1302
1335
  { dir: process.platform === 'win32' ? path.join(process.env.APPDATA || homeDir, 'opencode', 'skills') : path.join(homeDir, '.config', 'opencode', 'skills'), key: 'opencode' },
1303
- ].filter((d) => d.key === null || detectedKeys.has(d.key));
1336
+ ].filter((d) => d.key === null || (detectedKeys.has(d.key) && !pluginCovered.has(d.key)));
1304
1337
 
1305
1338
  for (const { dir: dest } of extraSkillDestinations) {
1306
1339
  try {
@@ -1549,6 +1582,17 @@ async function promptCredentials(referenceMcpDir) {
1549
1582
 
1550
1583
  const DAEMON_LOGON_FALLBACK_EXIT_CODE = 10;
1551
1584
 
1585
+ // The daemon job points at these scripts for good, so they live in the installer-owned shared store
1586
+ // (~/.agents/skills), which the plugin migration never retires, not in ~/.claude/skills.
1587
+ function stableOpenWikiScripts() {
1588
+ const dest = path.join(homeDir, '.agents', 'skills', 'openwiki-skill');
1589
+ if (!fs.existsSync(path.join(dest, 'scripts', 'install_daemon.sh'))) {
1590
+ const src = path.join(srcDir, 'skills', 'global_config', 'openwiki-skill');
1591
+ if (fs.existsSync(src)) copyDirRecursiveSync(src, dest);
1592
+ }
1593
+ return path.join(dest, 'scripts');
1594
+ }
1595
+
1552
1596
  async function installOpenWikiDaemon(apiKey, targetSkillDir, openwikiEnv = {}) {
1553
1597
  const prov = openwikiEnv.provider || "google";
1554
1598
  if (!apiKey && !["ollama", "lmstudio"].includes(prov)) {
@@ -1562,7 +1606,7 @@ async function installOpenWikiDaemon(apiKey, targetSkillDir, openwikiEnv = {}) {
1562
1606
  const s = spinner();
1563
1607
  s.start('Installing OpenWiki Daemon...');
1564
1608
 
1565
- const scriptBase = path.join(targetSkillDir, 'openwiki-skill', 'scripts');
1609
+ const scriptBase = stableOpenWikiScripts();
1566
1610
 
1567
1611
  const daemonEnv = Object.assign({}, process.env, {
1568
1612
  OPENWIKI_PROVIDER: prov,
@@ -3886,7 +3930,7 @@ function injectHarnessRules() {
3886
3930
  const targetPath = path.join(homeDir, dir);
3887
3931
  // settings.json is merged separately below: a wholesale
3888
3932
  // copy would clobber user-owned keys like enabledPlugins.
3889
- const exclude = dir === '.claude' ? ['settings.json'] : [];
3933
+ const exclude = dir === '.claude' ? ['settings.json'] : dir === '.agents' ? AGENTS_COPY_EXCLUDE : [];
3890
3934
  copyDirRecursiveSync(sourcePath, targetPath, exclude);
3891
3935
  log.step(`Copied ${dir} to ${targetPath}`);
3892
3936
  }
@@ -3898,13 +3942,165 @@ function injectHarnessRules() {
3898
3942
  const globalAgentsDir = path.join(os.homedir(), '.agents');
3899
3943
  const agentsDirSrc = path.join(srcDir, '.agents');
3900
3944
  if (fs.existsSync(agentsDirSrc)) {
3901
- copyDirRecursiveSync(agentsDirSrc, globalAgentsDir);
3945
+ copyDirRecursiveSync(agentsDirSrc, globalAgentsDir, AGENTS_COPY_EXCLUDE);
3902
3946
  log.step(`Synced global .agents/ to ${globalAgentsDir}`);
3903
3947
  }
3904
3948
  }, 'agents.md and workflows/startcycle.md may be missing globally.');
3905
3949
  }
3906
3950
  }
3907
3951
 
3952
+ // Optional third-party OpenCode components. Off unless named in
3953
+ // AOS_OPENCODE_OPTIONAL=ponytail,loop,rtk or --opencode-optional=ponytail,loop,rtk.
3954
+ // Pinned entries only; the foreign installers (opencode-loop's npx installer, rtk init)
3955
+ // are never run because they rewrite the user's opencode config.
3956
+ const OPENCODE_OPTIONAL_PINS = {
3957
+ ponytail: '@dietrichgebert/ponytail@4.10.0',
3958
+ loop: '@bybrawe/opencode-loop@0.6.2',
3959
+ };
3960
+ const OPENCODE_RTK_HINT = 'rtk is not installed by AOS. Run yourself: brew install rtk && rtk init -g --opencode';
3961
+ const OPENCODE_LOOP_SHELL_WARNING = 'opencode-loop: /loop-shell style commands run shell commands as child processes. ' +
3962
+ 'tool.execute.before, and so the AOS go-gate, may never see them (unverified). ' +
3963
+ 'Do not schedule git push, publish or other gated commands through them.';
3964
+
3965
+ let codenotchResult = null;
3966
+ async function maybeInstallCodenotch() {
3967
+ if (process.platform !== 'darwin' && process.platform !== 'win32') return;
3968
+ const codenotch = require('./lib/codenotch.js');
3969
+ codenotchResult = await codenotch.runCodenotchStep({
3970
+ interactive: !isAutoYes,
3971
+ dryRun: DRY_RUN,
3972
+ ask: async (message, initialValue = true) => { const a = await askConfirm({ message, initialValue }); return !isCancel(a) && !!a; },
3973
+ log: { info: (m) => log.info(m), warn: (m) => log.warn(m), success: (m) => log.success(m) },
3974
+ });
3975
+ const line = codenotch.codenotchSummaryLine(codenotchResult);
3976
+ if (line) (codenotchResult.status === 'error' || /FAILED/.test(line) ? log.warn : log.info)(line);
3977
+ }
3978
+
3979
+ function parseOpencodeOptional(argv = process.argv, env = process.env) {
3980
+ const flag = argv.find((a) => a.startsWith('--opencode-optional='));
3981
+ const raw = [env.AOS_OPENCODE_OPTIONAL, flag && flag.slice('--opencode-optional='.length)].filter(Boolean).join(',');
3982
+ return new Set(raw.split(',').map((x) => x.trim().toLowerCase()).filter(Boolean));
3983
+ }
3984
+
3985
+ // Opt-in OpenCode permission. Off unless AOS_OPENCODE_PERMISSION=external_directory or
3986
+ // --opencode-permission=external_directory. Only permission.external_directory is ever set,
3987
+ // as path patterns (object form verified in the OpenCode config schema; ~ expands per its docs).
3988
+ const OPENCODE_EXTERNAL_DIRECTORY_RULES = { '~/.agents/**': 'allow', '~/.config/opencode/**': 'allow' };
3989
+
3990
+ function parseOpencodePermission(argv = process.argv, env = process.env) {
3991
+ const flag = argv.find((a) => a.startsWith('--opencode-permission='));
3992
+ const raw = [env.AOS_OPENCODE_PERMISSION, flag && flag.slice('--opencode-permission='.length)].filter(Boolean).join(',');
3993
+ return new Set(raw.split(',').map((x) => x.trim().toLowerCase()).filter(Boolean));
3994
+ }
3995
+
3996
+ function applyOpencodePermission(data, configPath, wanted) {
3997
+ for (const name of wanted) {
3998
+ if (name !== 'external_directory') log.warn(`Unknown OpenCode permission "${name}" ignored.`);
3999
+ }
4000
+ if (!wanted.has('external_directory') || DRY_RUN) return;
4001
+ const perm = data.permission;
4002
+ if (perm !== undefined && (perm === null || typeof perm !== 'object' || Array.isArray(perm))) {
4003
+ log.warn('opencode.jsonc "permission" is not an object (string shorthand?); external_directory not set, left untouched.');
4004
+ return;
4005
+ }
4006
+ if (perm && 'external_directory' in perm) {
4007
+ if (JSON.stringify(perm.external_directory) !== JSON.stringify(OPENCODE_EXTERNAL_DIRECTORY_RULES)) {
4008
+ log.message('opencode.jsonc already has permission.external_directory; left untouched.');
4009
+ }
4010
+ return;
4011
+ }
4012
+ if (configPath && fs.existsSync(configPath)) {
4013
+ if (readJsoncFile(configPath) === null) {
4014
+ log.warn(`Could not parse ${configPath}; permission.external_directory not set.`);
4015
+ return;
4016
+ }
4017
+ const bak = `${configPath}.${timestamp}.bak`;
4018
+ try { fs.copyFileSync(configPath, bak); } catch (e) {
4019
+ log.warn(`Could not back up ${configPath}, skipping permission.external_directory: ${e.message}`);
4020
+ return;
4021
+ }
4022
+ }
4023
+ data.permission = Object.assign(perm || {}, { external_directory: { ...OPENCODE_EXTERNAL_DIRECTORY_RULES } });
4024
+ log.step('Added opt-in OpenCode permission.external_directory for AOS paths (comments in opencode.jsonc are not preserved on rewrite)');
4025
+ }
4026
+
4027
+ const pluginPackageName = (entry) => {
4028
+ const str = typeof entry === 'string' ? entry : (Array.isArray(entry) ? String(entry[0]) : '');
4029
+ return str.replace(/(?<=.)@[^@/]*$/, '');
4030
+ };
4031
+
4032
+ // OpenCode auto-loads plugins/*.js and also loads plugin[] paths, then dedupes by file
4033
+ // URL (verified in the 1.18.30 binary strings, not by running it). Same path = one load;
4034
+ // a second differing spelling would load the gate and loop keeper twice.
4035
+ function warnOnDuplicateAosPlugin(plugins, canonical) {
4036
+ const norm = (v) => String(Array.isArray(v) ? v[0] : v).replace(/\\/g, '/');
4037
+ const others = plugins.filter((p) => /bdb-aos\.js$/.test(norm(p)) && norm(p) !== canonical);
4038
+ if (others.length > 0) {
4039
+ log.warn(`opencode.jsonc lists bdb-aos.js under another path (${others.map(norm).join(', ')}). ` +
4040
+ `plugins/bdb-aos.js is auto-loaded too; remove the extra entry to avoid a double gate.`);
4041
+ }
4042
+ }
4043
+
4044
+ function appendOptionalOpencodePlugins(data, configPath, optional) {
4045
+ const wanted = optional instanceof Set ? optional : new Set(optional || []);
4046
+ for (const name of wanted) {
4047
+ if (!(name in OPENCODE_OPTIONAL_PINS) && name !== 'rtk') log.warn(`Unknown OpenCode optional component "${name}" ignored.`);
4048
+ }
4049
+ if (wanted.has('rtk')) log.message(OPENCODE_RTK_HINT);
4050
+ let backedUp = false;
4051
+ for (const name of Object.keys(OPENCODE_OPTIONAL_PINS)) {
4052
+ if (!wanted.has(name) || DRY_RUN) continue;
4053
+ const pin = OPENCODE_OPTIONAL_PINS[name];
4054
+ if (data.plugin.some((p) => pluginPackageName(p) === pluginPackageName(pin))) continue;
4055
+ if (!backedUp && configPath && fs.existsSync(configPath)) {
4056
+ const bak = `${configPath}.${timestamp}.bak`;
4057
+ try { fs.copyFileSync(configPath, bak); backedUp = true; } catch (e) {
4058
+ log.warn(`Could not back up ${configPath}, skipping optional ${name}: ${e.message}`);
4059
+ continue;
4060
+ }
4061
+ }
4062
+ data.plugin.push(pin);
4063
+ log.step(`Added opt-in OpenCode plugin ${pin}`);
4064
+ }
4065
+ if (data.plugin.some((p) => pluginPackageName(p) === pluginPackageName(OPENCODE_OPTIONAL_PINS.loop))) {
4066
+ log.warn(OPENCODE_LOOP_SHELL_WARNING);
4067
+ }
4068
+ }
4069
+
4070
+ // Idempotent and edit-safe: a file is overwritten only when it is absent, already
4071
+ // identical, or still byte-equal to what this installer last recorded. Anything else
4072
+ // (user edit, or an untracked file with the same name) is kept and the shipped copy
4073
+ // lands as <file>.new. Files the installer did not create are never removed.
4074
+ function installOpencodeCommands(srcDirPath, destDirPath) {
4075
+ if (DRY_RUN) {
4076
+ log.message(`[dry-run] install OpenCode commands: ${srcDirPath} -> ${destDirPath}`);
4077
+ return;
4078
+ }
4079
+ const ownsManifest = !_sessionManifest;
4080
+ const manifest = _sessionManifest || loadInstallManifest();
4081
+ fs.mkdirSync(destDirPath, { recursive: true });
4082
+ let wrote = 0;
4083
+ for (const file of fs.readdirSync(srcDirPath)) {
4084
+ const src = path.join(srcDirPath, file);
4085
+ if (!fs.statSync(src).isFile()) continue;
4086
+ const dest = path.join(destDirPath, file);
4087
+ const srcHash = computeFileHash(src);
4088
+ const diskHash = fs.existsSync(dest) ? computeFileHash(dest) : null;
4089
+ if (diskHash === null || diskHash === srcHash || diskHash === manifest[dest]?.sha256) {
4090
+ if (diskHash !== srcHash) { fs.copyFileSync(src, dest); wrote++; }
4091
+ manifest[dest] = { path: dest, sha256: srcHash, version: pkg.version, installedAt: new Date().toISOString() };
4092
+ } else {
4093
+ fs.copyFileSync(src, `${dest}.new`);
4094
+ keptUserEdits.push(dest);
4095
+ }
4096
+ }
4097
+ if (ownsManifest) {
4098
+ saveInstallManifest(manifest);
4099
+ reportKeptUserEdits();
4100
+ }
4101
+ log.step(`OpenCode commands in ${destDirPath}: ${wrote} written`);
4102
+ }
4103
+
3908
4104
  // Copy the OpenCode plugin + command payload and register both in
3909
4105
  // opencode.jsonc. This is the single copy site: the Quick Update path
3910
4106
  // (injectHarnessRules -> installGlobalHooks) and the fresh-install path
@@ -3915,7 +4111,7 @@ function injectHarnessRules() {
3915
4111
  // Sync merges MCP servers in the same pass and writes once). When `data` is
3916
4112
  // omitted the function loads and saves the config itself, which is what lets the
3917
4113
  // Quick Update path register the plugin as well as copy it.
3918
- function installOpencodePlugin({ targetHome = homeDir, configPath = null, data = null } = {}) {
4114
+ function installOpencodePlugin({ targetHome = homeDir, configPath = null, data = null, optional = parseOpencodeOptional(), permission = parseOpencodePermission() } = {}) {
3919
4115
  const opencodeDir = configPath
3920
4116
  ? path.dirname(configPath)
3921
4117
  : (process.platform === 'win32'
@@ -3952,13 +4148,11 @@ function installOpencodePlugin({ targetHome = homeDir, configPath = null, data =
3952
4148
  try { copyDirRecursiveSync(trailLibSrc, path.join(opencodeDir, 'plugins', 'lib')); } catch (e) { log.warn(`Could not install OpenCode plugin lib: ${e.message}`); }
3953
4149
  }
3954
4150
 
3955
- // Slash-command payloads. Without these the /startcycle-graph command has no
3956
- // native resolution and only survives as a raw-text match in the plugin.
4151
+ // Slash-command payloads (/startcycle-graph and the generated /bdb-aos-<cmd> files).
3957
4152
  const commandsSrc = path.join(srcDir, '.opencode', 'commands');
3958
4153
  if (fs.existsSync(commandsSrc)) {
3959
4154
  try {
3960
- copyDirRecursiveSync(commandsSrc, path.join(opencodeDir, 'commands'));
3961
- log.step(`Installed OpenCode commands to ${path.join(opencodeDir, 'commands')}`);
4155
+ installOpencodeCommands(commandsSrc, path.join(opencodeDir, 'commands'));
3962
4156
  } catch (e) {
3963
4157
  log.warn(`Could not install OpenCode commands: ${e.message}`);
3964
4158
  }
@@ -3985,6 +4179,8 @@ function installOpencodePlugin({ targetHome = homeDir, configPath = null, data =
3985
4179
  return str.includes('bdb-aos');
3986
4180
  });
3987
4181
  if (!alreadyRegistered) data.plugin.push(pluginPathNormalized);
4182
+ warnOnDuplicateAosPlugin(data.plugin, pluginPathNormalized);
4183
+ appendOptionalOpencodePlugins(data, configPath, optional);
3988
4184
 
3989
4185
  // Skill paths. `.agents/skills` stays project-relative on purpose: it is the
3990
4186
  // per-project contract, and OpenCode resolves it against each project, so it
@@ -4002,6 +4198,8 @@ function installOpencodePlugin({ targetHome = homeDir, configPath = null, data =
4002
4198
  }
4003
4199
  }
4004
4200
 
4201
+ applyOpencodePermission(data, configPath, permission instanceof Set ? permission : new Set(permission || []));
4202
+
4005
4203
  if (ownsWrite) {
4006
4204
  // Write only on a real change. readJsoncFile strips comments, so
4007
4205
  // serialising an unchanged config would silently eat a user's comments
@@ -4091,6 +4289,26 @@ function installGlobalHooks({ targetHome = homeDir, targetGemini = geminiDir } =
4091
4289
 
4092
4290
  // 5. Global CLI launcher binaries (aos-config, aos-dashboard, aos-uninstall)
4093
4291
  installGlobalBinaries();
4292
+
4293
+ // 6. Fixed-path GO check for AO and other non-hook callers
4294
+ installGoCheck({ targetHome });
4295
+ }
4296
+
4297
+ // go-check.mjs is the fixed-path GO check for callers that are not a harness hook (AO).
4298
+ // It imports go-gate.mjs from its own directory, so both land in ~/.aos/bin, together with
4299
+ // the guarded patterns as JSON. Same conflict/backup rules as every other installed file.
4300
+ function installGoCheck({ targetHome = homeDir } = {}) {
4301
+ const dest = path.join(targetHome, '.aos', 'bin');
4302
+ const files = [
4303
+ [path.join(srcDir, 'bin', 'go-check.mjs'), 'go-check.mjs'],
4304
+ [path.join(srcDir, '.claude', 'hooks', 'go-gate.mjs'), 'go-gate.mjs'],
4305
+ [path.join(srcDir, 'bin', 'guarded-patterns.json'), 'guarded-patterns.json'],
4306
+ ];
4307
+ if (!files.every(([src]) => fs.existsSync(src))) return false;
4308
+ fs.mkdirSync(dest, { recursive: true });
4309
+ for (const [src, name] of files) copyDirRecursiveSync(src, path.join(dest, name));
4310
+ log.step(`Installed go-check to ${dest}`);
4311
+ return true;
4094
4312
  }
4095
4313
 
4096
4314
  function installGlobalBinaries() {
@@ -4150,7 +4368,7 @@ function installGlobalBinaries() {
4150
4368
  // merged result goes to a .bdb-new.json sidecar -- the same recovery pattern
4151
4369
  // the MCP config merge in installMcpsForTarget uses.
4152
4370
  function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
4153
- const bdbHookScripts = ['go-gate.mjs', 'go-token.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs', 'trail-autostart.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
4371
+ const bdbHookScripts = ['go-gate.mjs', 'go-token.mjs', 'go-grant.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs', 'trail-autostart.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
4154
4372
  // memb-inject reads the machine-global memB store under $HOME and is
4155
4373
  // installed once per machine, so it stays $HOME-anchored even inside a
4156
4374
  // project harness -- unlike the two gate hooks, which are per-checkout by
@@ -4427,6 +4645,21 @@ function mergeCodexTomlMcpServers(configTomlPath, servers) {
4427
4645
  return skipped;
4428
4646
  }
4429
4647
 
4648
+ // The go-gate PreToolUse stanza for Codex's config.toml. Whether it fires under `codex exec`
4649
+ // or under aos-acp is UNVERIFIED (docs/codex-gate-smoke.md).
4650
+ function codexGateSnippet(hooksDir) {
4651
+ return [
4652
+ '[[hooks.PreToolUse]]',
4653
+ 'matcher = "^(Bash|run_command)$"',
4654
+ '[[hooks.PreToolUse.hooks]]',
4655
+ 'type = "command"',
4656
+ `command = ${JSON.stringify(`node "${path.join(hooksDir, 'go-gate.mjs')}"`)}`,
4657
+ 'timeout = 30',
4658
+ ].join('\n');
4659
+ }
4660
+
4661
+ const CODEX_GATE_NOTICE = 'Codex asks you to trust new or changed hooks before it runs them; approve the go-gate hook in Codex, or it stays inactive. Firing under `codex exec` and under aos-acp is UNVERIFIED: run the smoke test in docs/codex-gate-smoke.md.';
4662
+
4430
4663
  // Merge the BDB hooks into ChatGPT Codex CLI's config.toml (~/.codex/config.toml or
4431
4664
  // .codex/config.toml), ensuring [features] hooks = true and preserving existing
4432
4665
  // non-BDB settings, comments, and MCP servers.
@@ -4438,12 +4671,7 @@ function mergeCodexTomlHooks(configTomlPath, { projectLocal = false } = {}) {
4438
4671
 
4439
4672
  const tomlSnippet = [
4440
4673
  '# AOS:HOOKS:START',
4441
- '[[hooks.PreToolUse]]',
4442
- 'matcher = "^(Bash|run_command)$"',
4443
- '[[hooks.PreToolUse.hooks]]',
4444
- 'type = "command"',
4445
- `command = ${JSON.stringify(`node "${path.join(hooksDir, 'go-gate.mjs')}"`)}`,
4446
- 'timeout = 30',
4674
+ codexGateSnippet(hooksDir),
4447
4675
  '',
4448
4676
  '[[hooks.Stop]]',
4449
4677
  '[[hooks.Stop.hooks]]',
@@ -4557,7 +4785,7 @@ function installProjectHarness() {
4557
4785
  installStep('copy .agents/ contract into project', () => {
4558
4786
  const agentsSrc = path.join(srcDir, '.agents');
4559
4787
  if (!fs.existsSync(agentsSrc)) throw new Error(`missing payload: ${agentsSrc}`);
4560
- copyDirRecursiveSync(agentsSrc, projectAgentsDir);
4788
+ copyDirRecursiveSync(agentsSrc, projectAgentsDir, AGENTS_COPY_EXCLUDE);
4561
4789
  log.step(`Copied .agents/ contract to ${projectAgentsDir}`);
4562
4790
  }, 'graph.md / state.schema.json may be missing in the project.');
4563
4791
 
@@ -5577,6 +5805,7 @@ async function runQuickUpdate(installState) {
5577
5805
 
5578
5806
  console.log('');
5579
5807
  verifyEcosystemInstallation();
5808
+ await maybeInstallCodenotch();
5580
5809
  // Fresh installs open this via universalHarnessSync(); Quick Update never
5581
5810
  // did, so a dev-workflow machine only ever saw it once, on day one --
5582
5811
  // every subsequent run is a Quick Update, which is what almost every
@@ -5655,11 +5884,15 @@ function installBinaryAtomically(src, dest) {
5655
5884
 
5656
5885
  async function main() {
5657
5886
  const args = process.argv.slice(2);
5658
- if (args.includes('--version') || args.includes('-V')) {
5887
+ if (args.includes('--version') || args.includes('-V') || args[0] === 'version') {
5659
5888
  console.log(pkg.version);
5660
5889
  return;
5661
5890
  }
5662
- if (args.includes('--help') || args.includes('-h')) {
5891
+ if (args.includes('--codex-gate')) {
5892
+ console.log(`${codexGateSnippet(path.join(homeDir, '.codex', 'hooks'))}\n\n${CODEX_GATE_NOTICE}`);
5893
+ return;
5894
+ }
5895
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
5663
5896
  console.log(`Usage: aos [command] [options]
5664
5897
 
5665
5898
  Commands:
@@ -5667,8 +5900,31 @@ Commands:
5667
5900
  doctor|checkup Check the installation
5668
5901
 
5669
5902
  Options:
5903
+ --codex-gate Print the go-gate [[hooks.PreToolUse]] stanza for ~/.codex/config.toml
5904
+ and the Codex hook-trust note, then exit (writes nothing)
5670
5905
  -y, --yes Non-interactive install (implied without a TTY)
5671
5906
  --dry-run Show what would change
5907
+ --opencode-optional=LIST
5908
+ Opt in to OpenCode extras (comma list: ponytail, loop, rtk), or set
5909
+ AOS_OPENCODE_OPTIONAL. Pinned plugin[] entries are appended after a
5910
+ config backup; rtk only prints a brew hint. Off by default. AOS never
5911
+ runs foreign installers and never touches OpenCode's mcp set.
5912
+ --no-codenotch macOS and Windows only (never Linux): BDB AO Codenotch (desktop app,
5913
+ from hybridlabor-api/bdb-ao-codenotch) installs by default;
5914
+ skip it with this flag or AOS_CODENOTCH=0. --codenotch or
5915
+ AOS_CODENOTCH=1 forces it. Interactive prompt defaults to yes. Failures
5916
+ only warn and never fail the install. SHA-256 verified; Windows runs
5917
+ the per-user installer silently (/S, no admin). Never overwrites an app
5918
+ AOS did not install. See docs/codenotch.md.
5919
+ --opencode-permission=external_directory
5920
+ Opt in to OpenCode's permission.external_directory for ~/.agents and
5921
+ ~/.config/opencode only (path patterns, allow), or set
5922
+ AOS_OPENCODE_PERMISSION. Existing keys are never overwritten; a string
5923
+ "permission" is refused. Config backed up first. Off by default.
5924
+ --plugin-migration=off|check
5925
+ Skip (off) or only report (check) the bdb-aos plugin registration and
5926
+ removal of AOS's own loose skill copies; same as AOS_PLUGIN_MIGRATION.
5927
+ --dry-run implies check. Default: on.
5672
5928
  --verbose, -v Verbose output
5673
5929
  -V, --version Print the version and exit
5674
5930
  -h, --help Print this help and exit`);
@@ -6036,45 +6292,7 @@ Options:
6036
6292
  const s = spinner();
6037
6293
  s.start(`Installing optimized skills${tier === '2' ? ' [Basic Tier]' : ''} to ${targets.length} target(s)...`);
6038
6294
 
6039
- for (const t of targets) {
6040
- if (mode === 'replace') {
6041
- moveIfExists(t.targetSkillDir, path.join(backupDir, `config_skills_backup_${t.value}`), `global config skills (${t.value})`);
6042
- moveIfExists(t.targetLegacyDir, path.join(backupDir, `legacy_skills_backup_${t.value}`), `legacy skills (${t.value})`);
6043
- moveIfExists(t.targetWorkspaceDir, path.join(backupDir, `workspace_skills_backup_${t.value}`), `workspace skills (${t.value})`);
6044
- }
6045
-
6046
- installStep(`create the skill target directories (${t.value})`, () => {
6047
- fs.mkdirSync(t.targetSkillDir, { recursive: true });
6048
- retireObsoleteLegacyDir(t.targetLegacyDir);
6049
- fs.mkdirSync(t.targetWorkspaceDir, { recursive: true });
6050
- }, 'The skill copies below will most likely be skipped as well.');
6051
-
6052
- if (fs.existsSync(skillsBase)) {
6053
- installStep(`install the skills (${t.value})`, () => {
6054
- const rawDirs = fs.readdirSync(skillsBase);
6055
- const dirs = rawDirs.sort((a, b) => {
6056
- const aIsLeaf = fs.existsSync(path.join(skillsBase, a, 'SKILL.md'));
6057
- const bIsLeaf = fs.existsSync(path.join(skillsBase, b, 'SKILL.md'));
6058
- if (aIsLeaf && !bIsLeaf) return 1;
6059
- if (!aIsLeaf && bIsLeaf) return -1;
6060
- return 0;
6061
- });
6062
- for (const dir of dirs) {
6063
- const fullPath = path.join(skillsBase, dir);
6064
- if (!fs.statSync(fullPath).isDirectory()) continue;
6065
-
6066
- if (dir === 'global_legacy') {
6067
- copyDirRecursiveSync(fullPath, t.targetLegacyDir, excludeSkills);
6068
- } else if (dir === 'workspace_agents') {
6069
- copyDirRecursiveSync(fullPath, t.targetWorkspaceDir, excludeSkills);
6070
- } else {
6071
- syncSkillEntry(fullPath, dir, t.targetSkillDir, excludeSkills);
6072
- }
6073
- }
6074
- log.step(`Installed all global config & core skills to ${t.targetSkillDir}`);
6075
- }, 'The skills are missing or incomplete; the rest of the installation continues.');
6076
- }
6077
- }
6295
+ installTargetSkills(targets, { mode, backupDir, excludeSkills, skillsBase });
6078
6296
 
6079
6297
  syncSkillsToGlobalHarnesses(excludeSkills);
6080
6298
  pruneRemovedSkills(_sessionManifest);
@@ -6117,6 +6335,7 @@ Options:
6117
6335
 
6118
6336
  console.log('');
6119
6337
  verifyEcosystemInstallation();
6338
+ await maybeInstallCodenotch();
6120
6339
 
6121
6340
  outro(`🎉 Installation complete! Targets: ${targets.map(t => t.value).join(', ')}${aosCliRequested ? ' + AOS CLI' : ''} · Tier: ${tier === '1' ? 'Pro MEDIA' : 'Basic'}${DRY_RUN ? ' · DRY-RUN (nothing was modified)' : ''}`);
6122
6341
  }
@@ -6153,7 +6372,15 @@ module.exports = {
6153
6372
  mergeCodexHooks: mergeCodexTomlHooks,
6154
6373
  mergeCodexTomlMcpServers,
6155
6374
  installGlobalHooks,
6375
+ installGoCheck,
6376
+ codexGateSnippet,
6377
+ CODEX_GATE_NOTICE,
6156
6378
  installOpencodePlugin,
6379
+ installOpencodeCommands,
6380
+ parseOpencodeOptional,
6381
+ maybeInstallCodenotch,
6382
+ pluginMigrationMode,
6383
+ runPluginMigration,
6157
6384
  installProjectHarness,
6158
6385
  promptMcpSelection,
6159
6386
  mirrorMcpServersTo,
@@ -6166,6 +6393,9 @@ module.exports = {
6166
6393
  resolveFileConflict,
6167
6394
  buildKnownSourceHashes,
6168
6395
  initSessionManifest,
6396
+ installTargetSkills,
6397
+ stableOpenWikiScripts,
6398
+ AGENTS_COPY_EXCLUDE,
6169
6399
  flushSessionManifest,
6170
6400
  copyDirRecursiveSync,
6171
6401
  INSTALL_MANIFEST_PATH,