opencode-thrum-pro 0.1.15 → 0.1.17

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 (31) hide show
  1. package/assets/commands/thrum-compact-extended.md +14 -12
  2. package/assets/commands/thrum-compact.md +17 -15
  3. package/assets/commands/thrum-prime.md +1 -0
  4. package/assets/skills/configure-roles/SKILL.md +0 -1
  5. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +15 -6
  6. package/assets/skills/coordinator-maintaining-memory/SKILL.md +2 -0
  7. package/assets/skills/coordinator-merging-code/SKILL.md +25 -10
  8. package/assets/skills/coordinator-merging-code/resources/overbuild-check-prompt.md +60 -0
  9. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +5 -4
  10. package/assets/skills/implementer-maintaining-memory/SKILL.md +2 -0
  11. package/assets/skills/memory-curate/SKILL.md +2 -0
  12. package/assets/skills/memory-current-rule/SKILL.md +121 -0
  13. package/assets/skills/memory-maintenance/SKILL.md +2 -0
  14. package/assets/skills/memory-write-discipline/SKILL.md +2 -0
  15. package/assets/skills/persistent-watcher-archetype/SKILL.md +142 -60
  16. package/assets/skills/project-hotpath-gate/SKILL.md +8 -0
  17. package/assets/skills/researcher-maintaining-memory/SKILL.md +2 -0
  18. package/assets/skills/thrum/resources/CLI_REFERENCE.md +14 -6
  19. package/assets/skills/thrum/resources/TMUX_SESSIONS.md +16 -8
  20. package/assets/skills/thrum-memory/SKILL.md +2 -0
  21. package/assets/skills/using-the-queue/SKILL.md +6 -7
  22. package/assets/skills/using-thrum-state/SKILL.md +2 -0
  23. package/assets/skills/using-tmux-ssh/resources/tmux-ssh +23 -2
  24. package/assets/skills/watcher-archetype/SKILL.md +13 -5
  25. package/dist/installer-assets.test.js +5 -2
  26. package/dist/installer.d.ts +5 -1
  27. package/dist/installer.js +38 -7
  28. package/dist/installer.test.js +53 -0
  29. package/package.json +4 -1
  30. package/dist/permission-fixture.integration.test.js +0 -506
  31. /package/dist/{permission-fixture.integration.test.d.ts → installer.test.d.ts} +0 -0
@@ -1,6 +1,10 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
+ export declare function sanitizeSkillsSubdir(sub: string | undefined): string;
3
+ export declare function getSkillsSubdir(): string;
2
4
  export declare function getConfigDir(): string;
3
- export declare function installAssets(ctx: PluginInput): Promise<void>;
5
+ export declare function installAssets(ctx: PluginInput, opts?: {
6
+ skillsSubdir?: string;
7
+ }): Promise<void>;
4
8
  interface AllowlistSource {
5
9
  version: number;
6
10
  command_patterns: string[];
package/dist/installer.js CHANGED
@@ -5,16 +5,32 @@ import { fileURLToPath } from "url";
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
  const ASSETS_DIR = path.resolve(__dirname, "..", "assets");
7
7
  const VERSION_FILE = ".thrum-plugin-version";
8
- function getPluginVersion() {
8
+ // Written next to the version file so a later release can tell pro from
9
+ // open-core installs and clean up safely.
10
+ const FLAVOR_FILE = ".thrum-plugin-flavor";
11
+ const DEFAULT_SKILLS_SUBDIR = "thrum";
12
+ function readPackageJson() {
9
13
  const pkgPath = path.resolve(__dirname, "..", "package.json");
10
14
  try {
11
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
12
- return pkg.version ?? "0.0.0";
15
+ return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
13
16
  }
14
17
  catch {
15
- return "0.0.0";
18
+ return {};
16
19
  }
17
20
  }
21
+ function getPluginVersion() {
22
+ return readPackageJson().version ?? "0.0.0";
23
+ }
24
+ // The pro build sets thrum.skillsSubdir in the packaged package.json to
25
+ // "thrum-pro" so it never shares a directory with the open-core package.
26
+ export function sanitizeSkillsSubdir(sub) {
27
+ return sub && /^[A-Za-z0-9._-]+$/.test(sub) && sub !== "." && sub !== ".."
28
+ ? sub
29
+ : DEFAULT_SKILLS_SUBDIR;
30
+ }
31
+ export function getSkillsSubdir() {
32
+ return sanitizeSkillsSubdir(readPackageJson().thrum?.skillsSubdir);
33
+ }
18
34
  export function getConfigDir() {
19
35
  const xdg = process.env.XDG_CONFIG_HOME;
20
36
  if (xdg)
@@ -34,11 +50,15 @@ function copyDirRecursive(src, dest) {
34
50
  }
35
51
  }
36
52
  }
37
- export async function installAssets(ctx) {
53
+ export async function installAssets(ctx, opts = {}) {
38
54
  const configDir = getConfigDir();
39
55
  const skillsDir = path.join(configDir, "skills");
40
56
  const commandsDir = path.join(configDir, "commands");
41
- const versionPath = path.join(skillsDir, "thrum", VERSION_FILE);
57
+ const subdir = opts.skillsSubdir !== undefined
58
+ ? sanitizeSkillsSubdir(opts.skillsSubdir)
59
+ : getSkillsSubdir();
60
+ const installRoot = path.join(skillsDir, subdir);
61
+ const versionPath = path.join(installRoot, VERSION_FILE);
42
62
  const currentVersion = getPluginVersion();
43
63
  // Check if already installed at this version
44
64
  try {
@@ -52,6 +72,14 @@ export async function installAssets(ctx) {
52
72
  const log = (msg) => ctx.client.app.log({
53
73
  body: { service: "opencode-thrum", level: "info", message: msg },
54
74
  });
75
+ 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
+ }
82
+ }
55
83
  // Copy each skill subdirectory
56
84
  const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
57
85
  if (fs.existsSync(assetsSkillsDir)) {
@@ -61,7 +89,9 @@ export async function installAssets(ctx) {
61
89
  if (!entry.isDirectory())
62
90
  continue;
63
91
  const src = path.join(assetsSkillsDir, entry.name);
64
- const dest = path.join(skillsDir, entry.name);
92
+ // Open-core keeps its historical flat layout (skills/<name>); only a
93
+ // non-default subdir (pro) nests skills under skills/<subdir>/.
94
+ const dest = path.join(subdir === DEFAULT_SKILLS_SUBDIR ? skillsDir : installRoot, entry.name);
65
95
  copyDirRecursive(src, dest);
66
96
  await log(`Installed skill: ${entry.name}`);
67
97
  }
@@ -82,6 +112,7 @@ export async function installAssets(ctx) {
82
112
  // Write version marker
83
113
  fs.mkdirSync(path.dirname(versionPath), { recursive: true });
84
114
  fs.writeFileSync(versionPath, currentVersion);
115
+ fs.writeFileSync(path.join(installRoot, FLAVOR_FILE), subdir === DEFAULT_SKILLS_SUBDIR ? "open-core" : "pro");
85
116
  await log(`opencode-thrum v${currentVersion} assets installed`);
86
117
  }
87
118
  const ALLOWLIST_ASSET_PATH = path.join(ASSETS_DIR, "thrum_allowlist.json");
@@ -0,0 +1,53 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+ import { installAssets, sanitizeSkillsSubdir } from "./installer.js";
7
+ const ctx = { client: { app: { log: async () => { } } } };
8
+ function withConfig(fn) {
9
+ return async () => {
10
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "oc-inst-"));
11
+ const prev = process.env.XDG_CONFIG_HOME;
12
+ process.env.XDG_CONFIG_HOME = dir;
13
+ try {
14
+ await fn(path.join(dir, "opencode"));
15
+ }
16
+ finally {
17
+ if (prev === undefined)
18
+ delete process.env.XDG_CONFIG_HOME;
19
+ else
20
+ process.env.XDG_CONFIG_HOME = prev;
21
+ fs.rmSync(dir, { recursive: true });
22
+ }
23
+ };
24
+ }
25
+ const pkgVersion = JSON.parse(fs.readFileSync(path.resolve(import.meta.dirname, "..", "package.json"), "utf8")).version;
26
+ test("open-core: flat skills/<name>, marker in skills/thrum", withConfig(async (cfg) => {
27
+ await installAssets(ctx, { skillsSubdir: "thrum" });
28
+ assert.ok(fs.existsSync(path.join(cfg, "skills", "thrum", ".thrum-plugin-version")));
29
+ assert.ok(fs.existsSync(path.join(cfg, "skills", "configure-roles")));
30
+ assert.ok(!fs.existsSync(path.join(cfg, "skills", "thrum-pro")));
31
+ assert.equal(fs.readFileSync(path.join(cfg, "skills", "thrum", ".thrum-plugin-flavor"), "utf8"), "open-core");
32
+ }));
33
+ test("pro: skills nested under skills/thrum-pro, marker there", withConfig(async (cfg) => {
34
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
35
+ const v = fs.readFileSync(path.join(cfg, "skills", "thrum-pro", ".thrum-plugin-version"), "utf8");
36
+ assert.equal(v, pkgVersion);
37
+ assert.ok(fs.existsSync(path.join(cfg, "skills", "thrum-pro", "configure-roles")));
38
+ assert.ok(!fs.existsSync(path.join(cfg, "skills", "configure-roles")));
39
+ assert.equal(fs.readFileSync(path.join(cfg, "skills", "thrum-pro", ".thrum-plugin-flavor"), "utf8"), "pro");
40
+ }));
41
+ test("pro: leaves an existing skills/thrum install untouched", withConfig(async (cfg) => {
42
+ const legacy = path.join(cfg, "skills", "thrum");
43
+ fs.mkdirSync(legacy, { recursive: true });
44
+ fs.writeFileSync(path.join(legacy, ".thrum-plugin-version"), "9.9.9");
45
+ await installAssets(ctx, { skillsSubdir: "thrum-pro" });
46
+ assert.equal(fs.readFileSync(path.join(legacy, ".thrum-plugin-version"), "utf8"), "9.9.9");
47
+ }));
48
+ test("invalid subdir falls back to default", () => {
49
+ for (const bad of ["../evil", "a/b", "", "..", ".", undefined]) {
50
+ assert.equal(sanitizeSkillsSubdir(bad), "thrum");
51
+ }
52
+ assert.equal(sanitizeSkillsSubdir("thrum-pro"), "thrum-pro");
53
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-thrum-pro",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Thrum multi-agent coordination plugin for Open Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,5 +46,8 @@
46
46
  "@opencode-ai/plugin": "^1.4.0",
47
47
  "@types/node": "^25.5.2",
48
48
  "typescript": "^5.3.0"
49
+ },
50
+ "thrum": {
51
+ "skillsSubdir": "thrum-pro"
49
52
  }
50
53
  }