opencode-thrum-pro 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/installer.js CHANGED
@@ -50,6 +50,68 @@ function copyDirRecursive(src, dest) {
50
50
  }
51
51
  }
52
52
  }
53
+ // Older pro releases (0.1.x) installed flat (skills/<name>) with the marker in
54
+ // skills/thrum, the same layout open-core still uses. OpenCode resolves
55
+ // duplicate skill names to the flat copy, so stale flat pro skills would shadow
56
+ // the nested ones. Remove them only when the install is positively identified
57
+ // as pro; anything else (open-core, unknown version, unreadable) is left alone.
58
+ function isLegacyProInstall(markerDir) {
59
+ const read = (name) => {
60
+ try {
61
+ return fs.readFileSync(path.join(markerDir, name), "utf8").trim();
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ };
67
+ const flavor = read(FLAVOR_FILE);
68
+ if (flavor !== undefined)
69
+ return flavor === "pro";
70
+ const version = read(VERSION_FILE);
71
+ return version !== undefined && /^0\.1\.\d+([-+].*)?$/.test(version);
72
+ }
73
+ async function cleanLegacyFlatInstall(skillsDir, log) {
74
+ const markerDir = path.join(skillsDir, DEFAULT_SKILLS_SUBDIR);
75
+ if (!fs.existsSync(path.join(markerDir, VERSION_FILE)))
76
+ return;
77
+ if (!isLegacyProInstall(markerDir)) {
78
+ await log(`Legacy flat install found at ${markerDir}; not removed (not identified as an old pro install). If it is one, delete it and its sibling skill dirs manually.`);
79
+ return;
80
+ }
81
+ const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
82
+ let removed = 0;
83
+ if (fs.existsSync(assetsSkillsDir)) {
84
+ for (const entry of fs.readdirSync(assetsSkillsDir, { withFileTypes: true })) {
85
+ if (!entry.isDirectory())
86
+ continue;
87
+ const flat = path.join(skillsDir, entry.name);
88
+ if (!fs.existsSync(flat))
89
+ continue;
90
+ try {
91
+ fs.rmSync(flat, { recursive: true });
92
+ removed++;
93
+ }
94
+ catch {
95
+ // best-effort; a locked dir must not block the install
96
+ }
97
+ }
98
+ }
99
+ for (const marker of [VERSION_FILE, FLAVOR_FILE]) {
100
+ try {
101
+ fs.rmSync(path.join(markerDir, marker), { force: true });
102
+ }
103
+ catch {
104
+ // best-effort
105
+ }
106
+ }
107
+ try {
108
+ fs.rmdirSync(markerDir); // only succeeds when empty
109
+ }
110
+ catch {
111
+ // not empty or already gone
112
+ }
113
+ await log(`Removed ${removed} legacy flat pro skill dirs under ${skillsDir}`);
114
+ }
53
115
  export async function installAssets(ctx, opts = {}) {
54
116
  const configDir = getConfigDir();
55
117
  const skillsDir = path.join(configDir, "skills");
@@ -73,12 +135,7 @@ export async function installAssets(ctx, opts = {}) {
73
135
  body: { service: "opencode-thrum", level: "info", message: msg },
74
136
  });
75
137
  if (subdir !== DEFAULT_SKILLS_SUBDIR) {
76
- // Pro build: a pre-existing skills/thrum copy cannot be attributed to pro
77
- // vs open-core (same marker file, no flavor field), so never delete it.
78
- const legacy = path.join(skillsDir, DEFAULT_SKILLS_SUBDIR, VERSION_FILE);
79
- if (fs.existsSync(legacy)) {
80
- await log(`Legacy flat install found at ${path.join(skillsDir, DEFAULT_SKILLS_SUBDIR)}; not removed (cannot tell pro from open-core). If it is an old pro install, delete it and its sibling skill dirs manually.`);
81
- }
138
+ await cleanLegacyFlatInstall(skillsDir, log);
82
139
  }
83
140
  // Copy each skill subdirectory
84
141
  const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
@@ -51,3 +51,52 @@ test("invalid subdir falls back to default", () => {
51
51
  }
52
52
  assert.equal(sanitizeSkillsSubdir("thrum-pro"), "thrum-pro");
53
53
  });
54
+ function seedLegacy(cfg, marker) {
55
+ const skills = path.join(cfg, "skills");
56
+ const thrumDir = path.join(skills, "thrum");
57
+ fs.mkdirSync(thrumDir, { recursive: true });
58
+ if (marker.version !== undefined)
59
+ fs.writeFileSync(path.join(thrumDir, ".thrum-plugin-version"), marker.version);
60
+ if (marker.flavor !== undefined)
61
+ fs.writeFileSync(path.join(thrumDir, ".thrum-plugin-flavor"), marker.flavor);
62
+ for (const n of ["configure-roles", "adversarial-critique", "user-own-skill"]) {
63
+ fs.mkdirSync(path.join(skills, n), { recursive: true });
64
+ fs.writeFileSync(path.join(skills, n, "SKILL.md"), "stale");
65
+ }
66
+ return skills;
67
+ }
68
+ test("pro: legacy flavor=pro removes matching flat dirs, keeps others", withConfig(async (cfg) => {
69
+ const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "pro" });
70
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
71
+ assert.ok(!fs.existsSync(path.join(skills, "configure-roles")));
72
+ assert.ok(!fs.existsSync(path.join(skills, "adversarial-critique")));
73
+ assert.ok(fs.existsSync(path.join(skills, "user-own-skill", "SKILL.md")));
74
+ assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
75
+ assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-flavor")));
76
+ assert.ok(fs.existsSync(path.join(skills, "thrum-pro", "configure-roles")));
77
+ }));
78
+ test("pro: legacy 0.1.16 without flavor is removed", withConfig(async (cfg) => {
79
+ const skills = seedLegacy(cfg, { version: "0.1.16" });
80
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
81
+ assert.ok(!fs.existsSync(path.join(skills, "configure-roles")));
82
+ assert.ok(fs.existsSync(path.join(skills, "user-own-skill")));
83
+ assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
84
+ }));
85
+ test("pro: legacy 0.3.105 without flavor removes nothing", withConfig(async (cfg) => {
86
+ const skills = seedLegacy(cfg, { version: "0.3.105" });
87
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
88
+ assert.ok(fs.existsSync(path.join(skills, "configure-roles", "SKILL.md")));
89
+ assert.ok(fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
90
+ }));
91
+ test("pro: legacy flavor=open-core removes nothing", withConfig(async (cfg) => {
92
+ const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "open-core" });
93
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
94
+ assert.ok(fs.existsSync(path.join(skills, "configure-roles", "SKILL.md")));
95
+ assert.ok(fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-flavor")));
96
+ }));
97
+ test("open-core build: existing flat install is not cleaned", withConfig(async (cfg) => {
98
+ const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "pro" });
99
+ await installAssets(ctx, { skillsSubdir: "thrum" });
100
+ assert.ok(fs.existsSync(path.join(skills, "user-own-skill", "SKILL.md")));
101
+ assert.equal(fs.readFileSync(path.join(skills, "thrum", ".thrum-plugin-flavor"), "utf8"), "open-core");
102
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-thrum-pro",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Thrum multi-agent coordination plugin for Open Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",