compound-workflow 1.6.4 → 1.6.6

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-workflow",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Clarify -> plan -> execute -> verify -> capture workflow: commands, skills, and agents for Claude Code",
5
5
  "author": {
6
6
  "name": "Compound Workflow"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-workflow",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Clarify -> plan -> execute -> verify -> capture workflow for Cursor",
5
5
  "author": {
6
6
  "name": "Compound Workflow"
@@ -0,0 +1,5 @@
1
+ {
2
+ "pluginId": "compound-workflow@local",
3
+ "scope": "user",
4
+ "installPath": "/Users/cjerochim/Documents/DEVELOPMENT/compound-workflow"
5
+ }
package/README.md CHANGED
@@ -42,6 +42,8 @@ If your package manager didn’t run postinstall, run once:
42
42
  npx compound-workflow install
43
43
  ```
44
44
 
45
+ Restart Cursor after install; enable third-party plugins in Settings if skills/commands don't appear.
46
+
45
47
  **4. After updating the package**
46
48
 
47
49
  To get the latest commands and wiring (e.g. after `npm update compound-workflow` or a new release), run install again so your project’s `opencode.json` is refreshed:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-workflow",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Clarify → plan → execute → verify → capture. One Install action for Cursor, Claude, and OpenCode.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,6 +18,7 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "postinstall": "node scripts/postinstall.mjs",
21
+ "prepublishOnly": "npm run generate:artifacts",
21
22
  "generate:artifacts": "node scripts/generate-platform-artifacts.mjs",
22
23
  "check:artifacts": "node scripts/generate-platform-artifacts.mjs --check",
23
24
  "check:version-parity": "node scripts/check-version-parity.mjs",
@@ -7,6 +7,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  const repoRoot = path.resolve(__dirname, "..");
8
8
  const agentsRoot = path.join(repoRoot, "src", ".agents");
9
9
 
10
+ function loadRegistry() {
11
+ const registryPath = path.join(agentsRoot, "registry.json");
12
+ if (!fs.existsSync(registryPath)) {
13
+ throw new Error(`Registry not found at ${registryPath}`);
14
+ }
15
+ return JSON.parse(fs.readFileSync(registryPath, "utf8"));
16
+ }
17
+
10
18
  function walkFiles(dirAbs, predicate) {
11
19
  const out = [];
12
20
  const stack = [dirAbs];
@@ -42,44 +50,64 @@ function parseFrontmatter(md) {
42
50
  return out;
43
51
  }
44
52
 
45
- function discoverCommands() {
46
- const commandsDir = path.join(agentsRoot, "commands");
47
- const files = walkFiles(commandsDir, (p) => p.endsWith(".md"));
48
- const commands = [];
49
-
50
- for (const fileAbs of files) {
51
- const relWithin = path.relative(commandsDir, fileAbs).replaceAll(path.sep, "/");
52
- const frontmatter = parseFrontmatter(fs.readFileSync(fileAbs, "utf8"));
53
- const id = (frontmatter.invocation || frontmatter.name || path.basename(fileAbs, ".md")).trim();
54
- if (!id) continue;
55
- commands.push({
56
- id,
57
- description: (frontmatter.description || id).trim(),
58
- rel: relWithin,
59
- });
53
+ /**
54
+ * Resolve id from frontmatter and config. idFrom = list of frontmatter keys; idFallback = "basename" | "dirname".
55
+ */
56
+ function resolveId(frontmatter, fileAbs, dirAbs, config) {
57
+ for (const key of config.idFrom || []) {
58
+ if (key === "dirname") {
59
+ const relDir = path.relative(dirAbs, path.dirname(fileAbs));
60
+ return (relDir ? relDir.replaceAll(path.sep, "/") : path.basename(path.dirname(fileAbs))).trim();
61
+ }
62
+ const v = frontmatter[key];
63
+ if (typeof v === "string" && v.trim()) return v.trim();
60
64
  }
65
+ if (config.idFallback === "basename") return path.basename(fileAbs, ".md").trim();
66
+ if (config.idFallback === "dirname") return path.basename(path.dirname(fileAbs)).trim();
67
+ return path.basename(fileAbs, ".md").trim();
68
+ }
61
69
 
62
- return commands.sort((a, b) => a.id.localeCompare(b.id));
70
+ function resolveDescription(frontmatter, config, id) {
71
+ const key = config.descriptionFrom;
72
+ if (key && frontmatter[key] != null) return String(frontmatter[key]).trim();
73
+ if (config.descriptionFallback === "id") return id || "";
74
+ return id || "";
63
75
  }
64
76
 
65
- function discoverAgents() {
66
- const agentDir = path.join(agentsRoot, "agents");
67
- const files = walkFiles(agentDir, (p) => p.endsWith(".md"));
68
- const agents = [];
77
+ /**
78
+ * Discover assets of one type from registry config. Returns array of { id, description, rel }.
79
+ */
80
+ function discoverByType(agentsRootAbs, typeKey, config) {
81
+ const dirAbs = path.join(agentsRootAbs, config.dir);
82
+ if (!fs.existsSync(dirAbs)) return [];
83
+
84
+ let files = [];
85
+ if (config.glob === "**/*.md") {
86
+ files = walkFiles(dirAbs, (p) => p.endsWith(".md"));
87
+ } else if (config.glob === "*/SKILL.md") {
88
+ const entries = fs.readdirSync(dirAbs, { withFileTypes: true });
89
+ for (const e of entries) {
90
+ if (e.isDirectory()) {
91
+ const skillMd = path.join(dirAbs, e.name, "SKILL.md");
92
+ if (fs.existsSync(skillMd)) files.push(skillMd);
93
+ }
94
+ }
95
+ files.sort();
96
+ } else {
97
+ files = walkFiles(dirAbs, (p) => p.endsWith(".md"));
98
+ }
69
99
 
100
+ const out = [];
70
101
  for (const fileAbs of files) {
71
- const relWithin = path.relative(agentDir, fileAbs).replaceAll(path.sep, "/");
72
- const frontmatter = parseFrontmatter(fs.readFileSync(fileAbs, "utf8"));
73
- const id = (frontmatter.name || path.basename(fileAbs, ".md")).trim();
102
+ const rel = path.relative(dirAbs, fileAbs).replaceAll(path.sep, "/");
103
+ const raw = fs.readFileSync(fileAbs, "utf8");
104
+ const frontmatter = parseFrontmatter(raw);
105
+ const id = resolveId(frontmatter, fileAbs, dirAbs, config);
74
106
  if (!id) continue;
75
- agents.push({
76
- id,
77
- description: (frontmatter.description || id).trim(),
78
- rel: relWithin,
79
- });
107
+ const description = resolveDescription(frontmatter, config, id);
108
+ out.push({ id, description, rel });
80
109
  }
81
-
82
- return agents.sort((a, b) => a.id.localeCompare(b.id));
110
+ return out.sort((a, b) => a.id.localeCompare(b.id));
83
111
  }
84
112
 
85
113
  function normalizeRepoUrl(repoValue) {
@@ -105,13 +133,25 @@ function writeJson(absPath, value, checkOnly, changed) {
105
133
 
106
134
  function main() {
107
135
  const checkOnly = process.argv.includes("--check");
136
+ const registry = loadRegistry();
137
+ const roots = registry.roots?.consumer || {};
138
+ const assetTypes = registry.assetTypes || {};
139
+
140
+ const commands =
141
+ assetTypes.command != null
142
+ ? discoverByType(agentsRoot, "command", assetTypes.command)
143
+ : [];
144
+ const agents =
145
+ assetTypes.agent != null ? discoverByType(agentsRoot, "agent", assetTypes.agent) : [];
108
146
 
109
147
  const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
110
148
  const repositoryUrl = normalizeRepoUrl(pkg.repository);
111
- const commands = discoverCommands();
112
- const agents = discoverAgents();
113
149
  const changed = [];
114
150
 
151
+ const commandRoot = roots.commands || "node_modules/compound-workflow/src/.agents/commands";
152
+ const agentRoot = roots.agents || "node_modules/compound-workflow/src/.agents/agents";
153
+ const skillsPath = roots.skills || "node_modules/compound-workflow/src/.agents/skills";
154
+
115
155
  const claudePlugin = {
116
156
  name: pkg.name,
117
157
  version: pkg.version,
@@ -140,9 +180,9 @@ function main() {
140
180
 
141
181
  const openCodeManaged = {
142
182
  $schema: "https://opencode.ai/config.json",
143
- skillsPath: "node_modules/compound-workflow/src/.agents/skills",
144
- commandRoot: "node_modules/compound-workflow/src/.agents/commands",
145
- agentRoot: "node_modules/compound-workflow/src/.agents/agents",
183
+ skillsPath,
184
+ commandRoot,
185
+ agentRoot,
146
186
  commands,
147
187
  agents,
148
188
  };
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
+ import os from "node:os";
9
10
  import { fileURLToPath } from "node:url";
10
11
  import { spawnSync } from "node:child_process";
11
12
 
@@ -15,25 +16,30 @@ function usage(exitCode = 0) {
15
16
  const msg = `
16
17
  Usage:
17
18
  (automatic) npm install compound-workflow # runs install via postinstall; no npx needed
18
- (manual) npx compound-workflow install [--root <projectDir>] [--dry-run] [--no-config]
19
+ (manual) npx compound-workflow install [--root <projectDir>] [--dry-run] [--no-config] [--no-register-cursor] [--register-cursor]
19
20
 
20
21
  Install writes opencode.json (from package), merges AGENTS.md, creates standard
21
22
  docs/todos directories, and prompts for Repo Config Block (unless --no-config).
23
+ When Cursor is detected (~/.cursor), registers the plugin so skills/commands appear.
22
24
 
23
- --root <dir> Project directory (default: cwd)
24
- --dry-run Print planned changes only
25
- --no-config Skip Repo Config Block reminder
25
+ --root <dir> Project directory (default: cwd)
26
+ --dry-run Print planned changes only
27
+ --no-config Skip Repo Config Block reminder
28
+ --no-register-cursor Do not register plugin with Cursor (skip apply to ~/.claude/)
29
+ --register-cursor Force registration with Cursor even if ~/.cursor not found
26
30
  `;
27
31
  (exitCode === 0 ? console.log : console.error)(msg.trimStart());
28
32
  process.exit(exitCode);
29
33
  }
30
34
 
31
35
  function parseArgs(argv) {
32
- const out = { root: process.cwd(), dryRun: false, noConfig: false };
36
+ const out = { root: process.cwd(), dryRun: false, noConfig: false, noRegisterCursor: false, registerCursor: false };
33
37
  for (let i = 2; i < argv.length; i++) {
34
38
  const arg = argv[i];
35
39
  if (arg === "--dry-run") out.dryRun = true;
36
40
  else if (arg === "--no-config") out.noConfig = true;
41
+ else if (arg === "--no-register-cursor") out.noRegisterCursor = true;
42
+ else if (arg === "--register-cursor") out.registerCursor = true;
37
43
  else if (arg === "--root") {
38
44
  const value = argv[i + 1];
39
45
  if (!value) usage(1);
@@ -331,15 +337,80 @@ function writePluginManifests(targetRoot, dryRun, isSelfInstall) {
331
337
  const cursorDir = path.join(targetRoot, ".cursor-plugin");
332
338
  const claudeDir = path.join(targetRoot, ".claude-plugin");
333
339
 
340
+ const installPathAbs = realpathSafe(targetRoot);
341
+ const registrationDescriptor = {
342
+ pluginId: "compound-workflow@local",
343
+ scope: "user",
344
+ installPath: installPathAbs,
345
+ };
346
+
334
347
  if (dryRun) {
335
- console.log("[dry-run] Would write .cursor-plugin/plugin.json and .claude-plugin/plugin.json");
348
+ console.log("[dry-run] Would write .cursor-plugin/plugin.json, .claude-plugin/plugin.json, .cursor-plugin/registration.json");
336
349
  return;
337
350
  }
338
351
  fs.mkdirSync(cursorDir, { recursive: true });
339
352
  fs.mkdirSync(claudeDir, { recursive: true });
340
353
  fs.writeFileSync(path.join(cursorDir, "plugin.json"), JSON.stringify(cursorOut, null, 2) + "\n", "utf8");
341
354
  fs.writeFileSync(path.join(claudeDir, "plugin.json"), JSON.stringify(claudeOut, null, 2) + "\n", "utf8");
342
- console.log("Wrote: .cursor-plugin/plugin.json, .claude-plugin/plugin.json");
355
+ fs.writeFileSync(path.join(cursorDir, "registration.json"), JSON.stringify(registrationDescriptor, null, 2) + "\n", "utf8");
356
+ console.log("Wrote: .cursor-plugin/plugin.json, .claude-plugin/plugin.json, .cursor-plugin/registration.json");
357
+ }
358
+
359
+ function cursorDetected() {
360
+ return fs.existsSync(path.join(os.homedir(), ".cursor"));
361
+ }
362
+
363
+ function applyCursorRegistration(targetRoot, dryRun, noRegisterCursor, forceRegister) {
364
+ if (dryRun) return;
365
+ if (noRegisterCursor && !forceRegister) return;
366
+ const shouldApply = forceRegister || (cursorDetected() && !noRegisterCursor);
367
+ if (!shouldApply) {
368
+ console.log("[cursor] Cursor not detected; skipped plugin registration. Use --register-cursor to force.");
369
+ return;
370
+ }
371
+
372
+ const registrationPath = path.join(targetRoot, ".cursor-plugin", "registration.json");
373
+ if (!fs.existsSync(registrationPath)) return;
374
+ let descriptor;
375
+ try {
376
+ descriptor = readJsonMaybe(registrationPath);
377
+ } catch {
378
+ return;
379
+ }
380
+ if (!descriptor?.pluginId || !descriptor?.installPath) return;
381
+
382
+ const claudePluginsDir = path.join(os.homedir(), ".claude", "plugins");
383
+ const installedPath = path.join(claudePluginsDir, "installed_plugins.json");
384
+ const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
385
+
386
+ let installed = {};
387
+ if (fs.existsSync(installedPath)) {
388
+ try {
389
+ installed = readJsonMaybe(installedPath) ?? {};
390
+ } catch {
391
+ installed = {};
392
+ }
393
+ }
394
+ const plugins = ensureObject(installed.plugins);
395
+ plugins[descriptor.pluginId] = [{ scope: descriptor.scope || "user", installPath: descriptor.installPath }];
396
+ installed.plugins = plugins;
397
+
398
+ let settings = {};
399
+ if (fs.existsSync(settingsPath)) {
400
+ try {
401
+ settings = readJsonMaybe(settingsPath) ?? {};
402
+ } catch {
403
+ settings = {};
404
+ }
405
+ }
406
+ settings.enabledPlugins = ensureObject(settings.enabledPlugins);
407
+ settings.enabledPlugins[descriptor.pluginId] = true;
408
+
409
+ fs.mkdirSync(claudePluginsDir, { recursive: true });
410
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
411
+ fs.writeFileSync(installedPath, JSON.stringify(installed, null, 2) + "\n", "utf8");
412
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
413
+ console.log("Registered compound-workflow with Cursor. Restart Cursor; enable 'Include third-party Plugins, Skills, and other configs' in Settings if needed.");
343
414
  }
344
415
 
345
416
  function reportOpenCodeIntegration(targetRoot, dryRun) {
@@ -406,6 +477,7 @@ function main() {
406
477
 
407
478
  writeOpenCodeJson(targetRoot, args.dryRun, isSelfInstall);
408
479
  writePluginManifests(targetRoot, args.dryRun, isSelfInstall);
480
+ applyCursorRegistration(targetRoot, args.dryRun, args.noRegisterCursor, args.registerCursor);
409
481
  reportOpenCodeIntegration(targetRoot, args.dryRun);
410
482
  writeAgentsMd(targetRoot, args.dryRun);
411
483
  ensureDirs(targetRoot, args.dryRun);
@@ -2,7 +2,7 @@
2
2
  name: install
3
3
  invocation: install
4
4
  description: Install compound-workflow in this project (native mode): writes opencode.json, merges AGENTS.md, and creates docs/todo dirs.
5
- argument-hint: "[--dry-run] [--root <path>] [--no-config]"
5
+ argument-hint: "[--dry-run] [--root <path>] [--no-config] [--no-register-cursor] [--register-cursor]"
6
6
  ---
7
7
 
8
8
  # /install
@@ -32,9 +32,15 @@ npx compound-workflow install --no-config
32
32
  - `--dry-run`: Print planned changes only; no writes.
33
33
  - `--root <path>`: Target project directory (default: current directory).
34
34
  - `--no-config`: Skip Repo Config Block reminder; still writes opencode.json, AGENTS.md, and dirs.
35
+ - `--no-register-cursor`: Do not register the plugin with Cursor (skip writing to ~/.claude/).
36
+ - `--register-cursor`: Force registration with Cursor even if Cursor is not detected in the default location.
35
37
 
36
38
  After running, suggest `opencode debug config` in the project to verify OpenCode resolution.
37
39
 
40
+ ## Cursor
41
+
42
+ One command installs and registers the plugin with Cursor when Cursor is detected (`~/.cursor` exists). Restart Cursor after install; if skills or commands do not appear, enable "Include third-party Plugins, Skills, and other configs" in Cursor Settings > Features. Use `--no-register-cursor` to skip registration (e.g. in CI).
43
+
38
44
  ## What Install does
39
45
 
40
46
  1. Ensures `compound-workflow` is installed in the project.
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "https://compound-workflow.dev/registry.schema.json",
3
+ "roots": {
4
+ "package": {
5
+ "commands": "src/.agents/commands",
6
+ "agents": "src/.agents/agents",
7
+ "skills": "src/.agents/skills",
8
+ "references": "src/.agents/references"
9
+ },
10
+ "consumer": {
11
+ "commands": "node_modules/compound-workflow/src/.agents/commands",
12
+ "agents": "node_modules/compound-workflow/src/.agents/agents",
13
+ "skills": "node_modules/compound-workflow/src/.agents/skills",
14
+ "references": "node_modules/compound-workflow/src/.agents/references"
15
+ }
16
+ },
17
+ "assetTypes": {
18
+ "command": {
19
+ "dir": "commands",
20
+ "glob": "**/*.md",
21
+ "idFrom": ["invocation", "name"],
22
+ "idFallback": "basename",
23
+ "descriptionFrom": "description",
24
+ "descriptionFallback": "id",
25
+ "output": ["opencode.command", "plugin.commands"]
26
+ },
27
+ "agent": {
28
+ "dir": "agents",
29
+ "glob": "**/*.md",
30
+ "idFrom": ["name"],
31
+ "idFallback": "basename",
32
+ "descriptionFrom": "description",
33
+ "descriptionFallback": "id",
34
+ "output": ["opencode.agent", "plugin.agents"]
35
+ },
36
+ "skill": {
37
+ "dir": "skills",
38
+ "glob": "*/SKILL.md",
39
+ "idFrom": ["dirname"],
40
+ "output": ["opencode.skillsPath", "plugin.skills"]
41
+ },
42
+ "reference": {
43
+ "dir": "references",
44
+ "glob": "**/*.md",
45
+ "output": ["path"]
46
+ }
47
+ }
48
+ }