@hybridlabor-api/aos 4.4.2-beta.0 → 4.4.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/installer.js +84 -0
  2. package/package.json +1 -1
package/installer.js CHANGED
@@ -608,6 +608,88 @@ function initSessionManifest(existingManifest, sourceDirs) {
608
608
  _sessionHashes = buildKnownSourceHashes(sourceDirs);
609
609
  }
610
610
 
611
+ // Skills that left the package in 4.4.2 (Plan 02: migrated to the private
612
+ // aos-internal repo, per ~/dev/_plans/aos-2026-09-14/), by their flattened
613
+ // directory name at a sync destination.
614
+ //
615
+ // This is deliberately a fixed list, not a diff against what the current
616
+ // payload ships. A generic "anything on disk that is not in skills/ right
617
+ // now" pass was tried first and swept up two unrelated things: skill folders
618
+ // that MCP servers bundle inside their own package tree (e.g.
619
+ // mcps/tdmcp/.agents/skills/...), which happen to share the literal path
620
+ // segment "skills" but belong to a different install entirely; and skills
621
+ // under skills/workspace_agents/, which syncSkillsToGlobalHarnesses already
622
+ // deliberately excludes from the six global destinations, so a stray global
623
+ // copy of one says nothing about whether it is safe to delete. Both are real
624
+ // cleanup opportunities, but they are a separate investigation, not a side
625
+ // effect of this migration's beta test.
626
+ const SKILLS_REMOVED_IN_4_4_2 = new Set([
627
+ 'bdbsaastraining', 'bdb-dev-os-skill', 'bdbsaashost', 'bdb-ecosystem-health', 'bdbsaas-ops',
628
+ ]);
629
+
630
+ // The exact roots syncSkillsToGlobalHarnesses and the Gemini/Antigravity
631
+ // config-skills path write into. Pruning only touches a manifest entry whose
632
+ // path starts under one of these -- never one that merely contains a
633
+ // "skills" segment somewhere deeper, which is what let the MCP-bundle and
634
+ // workspace_agents cases above leak in during testing.
635
+ function globalSkillDestRoots() {
636
+ return [
637
+ path.join(homeDir, '.agents', 'skills'),
638
+ path.join(homeDir, '.claude', 'skills'),
639
+ path.join(homeDir, '.codex', 'skills'),
640
+ path.join(homeDir, '.cursor', 'skills'),
641
+ path.join(homeDir, '.roo', 'skills'),
642
+ path.join(geminiDir, 'config', 'skills'),
643
+ ];
644
+ }
645
+
646
+ // syncSkillsToGlobalHarnesses (and its sibling copy loops) only ever add or
647
+ // update -- nothing in that path ever deletes a skill that the current
648
+ // payload no longer ships. That is fine for new skills and updated ones, but
649
+ // it means a skill removed from a release stays on every user's disk
650
+ // forever, through every future update, because nothing after the removal
651
+ // ever looks at what is on disk that no longer has a source. This walks the
652
+ // install manifest -- which already has one entry per file this installer
653
+ // has ever placed -- for the specific skills 4.4.2 removed, under the six
654
+ // known global skill roots, and deletes them. A user-modified file is backed
655
+ // up first, the same way resolveFileConflict backs up a user edit it is
656
+ // about to overwrite, rather than silently deleting local changes.
657
+ function pruneRemovedSkills(manifest) {
658
+ if (!manifest) return;
659
+ const roots = globalSkillDestRoots();
660
+
661
+ const staleDirs = new Set();
662
+ let removedFiles = 0;
663
+ for (const targetPath of Object.keys(manifest)) {
664
+ const root = roots.find((r) => targetPath.startsWith(r + path.sep));
665
+ if (!root) continue;
666
+ const skillDirName = targetPath.slice(root.length + 1).split(path.sep)[0];
667
+ if (!SKILLS_REMOVED_IN_4_4_2.has(skillDirName)) continue;
668
+
669
+ if (fs.existsSync(targetPath)) {
670
+ const diskHash = computeFileHash(targetPath);
671
+ const manifestEntry = manifest[targetPath];
672
+ if (diskHash !== null && manifestEntry && diskHash !== manifestEntry.sha256) {
673
+ const bakPath = `${targetPath}.${timestamp}.bak`;
674
+ try { fs.copyFileSync(targetPath, bakPath); } catch (e) { logDebug(e, 'prune-removed-skill backup'); }
675
+ log.warn(`[manifest] Retired skill left a user-edited file, backed up to ${bakPath} instead of deleting it.`);
676
+ } else {
677
+ try { fs.unlinkSync(targetPath); removedFiles++; } catch (e) { logDebug(e, 'prune-removed-skill unlink'); }
678
+ }
679
+ staleDirs.add(path.join(root, skillDirName));
680
+ }
681
+ delete manifest[targetPath];
682
+ }
683
+
684
+ for (const dir of staleDirs) {
685
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e) { logDebug(e, 'prune-removed-skill rmdir'); }
686
+ }
687
+
688
+ if (staleDirs.size > 0) {
689
+ log.step(`Removed ${removedFiles} file(s) from ${staleDirs.size} retired skill install(s) no longer shipped.`);
690
+ }
691
+ }
692
+
611
693
  function flushSessionManifest() {
612
694
  if (_sessionManifest) saveInstallManifest(_sessionManifest);
613
695
  }
@@ -3464,6 +3546,7 @@ async function runQuickUpdate(installState) {
3464
3546
  }
3465
3547
  }
3466
3548
  syncSkillsToGlobalHarnesses(excludeSkills);
3549
+ pruneRemovedSkills(_sessionManifest);
3467
3550
  s.stop('Skills refreshed');
3468
3551
 
3469
3552
  // Everything injectHarnessRules() delivers -- GEMINI.md, the dispatcher
@@ -3868,6 +3951,7 @@ async function main() {
3868
3951
  }
3869
3952
 
3870
3953
  syncSkillsToGlobalHarnesses(excludeSkills);
3954
+ pruneRemovedSkills(_sessionManifest);
3871
3955
  s.stop('Skills installed.');
3872
3956
 
3873
3957
  injectHarnessRules();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/aos",
3
- "version": "4.4.2-beta.0",
3
+ "version": "4.4.2-beta.1",
4
4
  "description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
5
5
  "main": "installer.js",
6
6
  "bin": {