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.
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.cursor-plugin/registration.json +5 -0
- package/README.md +2 -0
- package/package.json +2 -1
- package/scripts/generate-platform-artifacts.mjs +75 -35
- package/scripts/install-cli.mjs +79 -7
- package/src/.agents/commands/install.md +7 -1
- package/src/.agents/registry.json +48 -0
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.
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
72
|
-
const
|
|
73
|
-
const
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
144
|
-
commandRoot
|
|
145
|
-
agentRoot
|
|
183
|
+
skillsPath,
|
|
184
|
+
commandRoot,
|
|
185
|
+
agentRoot,
|
|
146
186
|
commands,
|
|
147
187
|
agents,
|
|
148
188
|
};
|
package/scripts/install-cli.mjs
CHANGED
|
@@ -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>
|
|
24
|
-
--dry-run
|
|
25
|
-
--no-config
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|