compound-workflow 1.4.4 → 1.4.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compound-workflow",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"description": "Clarify → plan → execute → verify → capture. One Install action for Cursor, Claude, and OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"postinstall": "node scripts/postinstall.mjs",
|
|
22
|
-
"check:pack-readme": "node scripts/check-pack-readme.mjs"
|
|
22
|
+
"check:pack-readme": "node scripts/check-pack-readme.mjs",
|
|
23
|
+
"test:install": "node --test tests/install-cli.test.mjs"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=18"
|
|
@@ -87,6 +87,16 @@ const requiredChecks = [
|
|
|
87
87
|
pattern: "HARD GATE - WORKTREE FIRST",
|
|
88
88
|
description: "worktree hard-gate wording in work command",
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
file: "src/.agents/commands/workflow/work.md",
|
|
92
|
+
pattern: "required prompt/create gate",
|
|
93
|
+
description: "mandatory worktree decision prompt/create gate in work command",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
file: "src/.agents/commands/workflow/work.md",
|
|
97
|
+
pattern: "Do not infer or assume an answer when the user has not answered.",
|
|
98
|
+
description: "worktree decision cannot be silently assumed",
|
|
99
|
+
},
|
|
90
100
|
{
|
|
91
101
|
file: "src/.agents/commands/workflow/work.md",
|
|
92
102
|
pattern:
|
package/scripts/install-cli.mjs
CHANGED
|
@@ -14,29 +14,32 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
14
14
|
function usage(exitCode = 0) {
|
|
15
15
|
const msg = `
|
|
16
16
|
Usage:
|
|
17
|
-
npx compound-workflow install [all|--all] [--root <projectDir>] [--dry-run] [--no-config]
|
|
17
|
+
npx compound-workflow install [all|--all] [--root <projectDir>] [--dry-run] [--no-config]
|
|
18
18
|
|
|
19
19
|
One action: writes opencode.json (loads from package), merges AGENTS.md, creates dirs,
|
|
20
20
|
and prompts for Repo Config Block (unless --no-config).
|
|
21
21
|
|
|
22
|
-
all, --all
|
|
22
|
+
all, --all Kept for compatibility
|
|
23
23
|
--root <dir> Project directory (default: cwd)
|
|
24
24
|
--dry-run Print planned changes only
|
|
25
25
|
--no-config Skip Repo Config Block prompt (only write opencode.json + AGENTS.md + dirs)
|
|
26
|
-
--cursor Force Cursor integration (create .cursor if missing, then wire links)
|
|
27
26
|
`;
|
|
28
27
|
(exitCode === 0 ? console.log : console.error)(msg.trimStart());
|
|
29
28
|
process.exit(exitCode);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
function parseArgs(argv) {
|
|
33
|
-
const out = { root: process.cwd(), dryRun: false, noConfig: false
|
|
32
|
+
const out = { root: process.cwd(), dryRun: false, noConfig: false };
|
|
34
33
|
for (let i = 2; i < argv.length; i++) {
|
|
35
34
|
const a = argv[i];
|
|
36
35
|
if (a === "--dry-run") out.dryRun = true;
|
|
37
36
|
else if (a === "--no-config") out.noConfig = true;
|
|
38
|
-
else if (a === "--cursor")
|
|
39
|
-
|
|
37
|
+
else if (a === "--cursor") {
|
|
38
|
+
// Deprecated compatibility alias; install now auto-detects .cursor.
|
|
39
|
+
}
|
|
40
|
+
else if (a === "--all" || a === "all") {
|
|
41
|
+
// Deprecated compatibility alias; install now auto-detects .cursor.
|
|
42
|
+
}
|
|
40
43
|
else if (a === "--root") {
|
|
41
44
|
const v = argv[i + 1];
|
|
42
45
|
if (!v) usage(1);
|
|
@@ -58,7 +61,10 @@ function realpathSafe(p) {
|
|
|
58
61
|
|
|
59
62
|
const packageRoot = realpathSafe(path.join(__dirname, ".."));
|
|
60
63
|
const packageAgents = path.join(packageRoot, "src", ".agents");
|
|
61
|
-
const
|
|
64
|
+
const LOCAL_RUNTIME_ROOT = ".agents/compound-workflow";
|
|
65
|
+
const LOCAL_COMMANDS_ROOT = `${LOCAL_RUNTIME_ROOT}/commands`;
|
|
66
|
+
const LOCAL_AGENTS_ROOT = `${LOCAL_RUNTIME_ROOT}/agents`;
|
|
67
|
+
const LOCAL_REFERENCES_ROOT = `${LOCAL_RUNTIME_ROOT}/references`;
|
|
62
68
|
|
|
63
69
|
function walkFiles(dirAbs, predicate) {
|
|
64
70
|
const out = [];
|
|
@@ -101,14 +107,14 @@ function discoverCommands(agentsRoot) {
|
|
|
101
107
|
const files = walkFiles(commandsDir, (p) => p.endsWith(".md"));
|
|
102
108
|
const map = new Map();
|
|
103
109
|
for (const fileAbs of files) {
|
|
104
|
-
const
|
|
110
|
+
const relWithinCommands = path.relative(commandsDir, fileAbs).replaceAll(path.sep, "/");
|
|
105
111
|
const md = fs.readFileSync(fileAbs, "utf8");
|
|
106
112
|
const fm = parseFrontmatter(md);
|
|
107
113
|
const id = (fm.invocation || fm.name || path.basename(fileAbs, ".md")).trim();
|
|
108
114
|
const description = (fm.description || id).trim();
|
|
109
115
|
if (!id) continue;
|
|
110
|
-
const
|
|
111
|
-
map.set(id, { id, rel:
|
|
116
|
+
const localRel = `${LOCAL_COMMANDS_ROOT}/${relWithinCommands}`;
|
|
117
|
+
map.set(id, { id, rel: localRel, description });
|
|
112
118
|
}
|
|
113
119
|
return map;
|
|
114
120
|
}
|
|
@@ -118,18 +124,65 @@ function discoverAgents(agentsRoot) {
|
|
|
118
124
|
const files = walkFiles(agentsDir, (p) => p.endsWith(".md"));
|
|
119
125
|
const map = new Map();
|
|
120
126
|
for (const fileAbs of files) {
|
|
121
|
-
const
|
|
127
|
+
const relWithinAgents = path.relative(agentsDir, fileAbs).replaceAll(path.sep, "/");
|
|
122
128
|
const md = fs.readFileSync(fileAbs, "utf8");
|
|
123
129
|
const fm = parseFrontmatter(md);
|
|
124
130
|
const id = (fm.name || path.basename(fileAbs, ".md")).trim();
|
|
125
131
|
const description = (fm.description || id).trim();
|
|
126
132
|
if (!id) continue;
|
|
127
|
-
const
|
|
128
|
-
map.set(id, { id, rel:
|
|
133
|
+
const localRel = `${LOCAL_AGENTS_ROOT}/${relWithinAgents}`;
|
|
134
|
+
map.set(id, { id, rel: localRel, description });
|
|
129
135
|
}
|
|
130
136
|
return map;
|
|
131
137
|
}
|
|
132
138
|
|
|
139
|
+
function copyDirContents(sourceDir, targetDir) {
|
|
140
|
+
if (!fs.existsSync(sourceDir)) return;
|
|
141
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
142
|
+
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const src = path.join(sourceDir, entry.name);
|
|
145
|
+
const dst = path.join(targetDir, entry.name);
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
copyDirContents(src, dst);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (entry.isFile()) {
|
|
151
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
152
|
+
fs.copyFileSync(src, dst);
|
|
153
|
+
}
|
|
154
|
+
if (entry.isSymbolicLink()) {
|
|
155
|
+
const real = realpathSafe(src);
|
|
156
|
+
const realStat = fs.statSync(real);
|
|
157
|
+
if (realStat.isDirectory()) {
|
|
158
|
+
copyDirContents(real, dst);
|
|
159
|
+
} else if (realStat.isFile()) {
|
|
160
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
161
|
+
fs.copyFileSync(real, dst);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function syncRuntimeAssets(targetRoot, dryRun) {
|
|
168
|
+
const mappings = [
|
|
169
|
+
{ label: "commands", src: path.join(packageAgents, "commands"), dst: path.join(targetRoot, LOCAL_COMMANDS_ROOT) },
|
|
170
|
+
{ label: "agents", src: path.join(packageAgents, "agents"), dst: path.join(targetRoot, LOCAL_AGENTS_ROOT) },
|
|
171
|
+
{ label: "references", src: path.join(packageAgents, "references"), dst: path.join(targetRoot, LOCAL_REFERENCES_ROOT) },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
for (const mapping of mappings) {
|
|
175
|
+
if (!fs.existsSync(mapping.src)) continue;
|
|
176
|
+
if (dryRun) {
|
|
177
|
+
console.log("[dry-run] Would sync", mapping.label, "to", path.relative(targetRoot, mapping.dst));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
fs.rmSync(mapping.dst, { recursive: true, force: true });
|
|
181
|
+
copyDirContents(mapping.src, mapping.dst);
|
|
182
|
+
console.log("Synced", mapping.label + ":", path.relative(targetRoot, mapping.dst));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
133
186
|
function ensureObject(v) {
|
|
134
187
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
135
188
|
}
|
|
@@ -237,39 +290,29 @@ function ensureSkillsSymlink(targetRoot, dryRun) {
|
|
|
237
290
|
}
|
|
238
291
|
}
|
|
239
292
|
|
|
240
|
-
function
|
|
293
|
+
function ensureCursorDirSync(targetRoot, cursorSubdir, pkgSubdir, dryRun, label, cursorReady) {
|
|
241
294
|
const cursorDir = path.join(targetRoot, ".cursor");
|
|
242
295
|
if (!cursorReady) return { status: "skipped-missing-cursor" };
|
|
243
296
|
const pkgPath = path.join(packageRoot, "src", ".agents", pkgSubdir);
|
|
244
297
|
if (!fs.existsSync(pkgPath)) return { status: "skipped-missing-package-path" };
|
|
245
298
|
|
|
246
|
-
const
|
|
247
|
-
const targetRel = path.join("..", "node_modules", "compound-workflow", "src", ".agents", pkgSubdir);
|
|
248
|
-
const targetAbs = path.resolve(path.dirname(linkPath), targetRel);
|
|
249
|
-
|
|
299
|
+
const targetPath = path.join(cursorDir, cursorSubdir);
|
|
250
300
|
if (dryRun) {
|
|
251
|
-
console.log("[dry-run] Would
|
|
301
|
+
console.log("[dry-run] Would sync .cursor/" + cursorSubdir, "from", label || pkgSubdir, "(Cursor)");
|
|
252
302
|
return { status: "dry-run" };
|
|
253
303
|
}
|
|
254
304
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
else if (!stat.isSymbolicLink()) {
|
|
260
|
-
console.warn("Skipped", ".cursor/" + cursorSubdir, "because it exists and is not a symlink");
|
|
261
|
-
return { status: "blocked-nonsymlink", path: linkPath };
|
|
305
|
+
if (fs.existsSync(targetPath)) {
|
|
306
|
+
const stat = fs.lstatSync(targetPath);
|
|
307
|
+
if (!stat.isDirectory()) {
|
|
308
|
+
return { status: "blocked-nondirectory", path: targetPath };
|
|
262
309
|
}
|
|
263
|
-
} catch (_) {}
|
|
264
|
-
|
|
265
|
-
if (needCreate) {
|
|
266
|
-
removePathIfExists(linkPath);
|
|
267
|
-
const type = process.platform === "win32" ? "dir" : "dir";
|
|
268
|
-
fs.symlinkSync(targetRel, linkPath, type);
|
|
269
|
-
console.log("Created", ".cursor/" + cursorSubdir, "->", label || pkgSubdir, "(Cursor)");
|
|
270
|
-
return { status: "created" };
|
|
271
310
|
}
|
|
272
|
-
|
|
311
|
+
|
|
312
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
313
|
+
copyDirContents(pkgPath, targetPath);
|
|
314
|
+
console.log("Synced", ".cursor/" + cursorSubdir, "from", label || pkgSubdir, "(Cursor)");
|
|
315
|
+
return { status: "synced" };
|
|
273
316
|
}
|
|
274
317
|
|
|
275
318
|
function ensureCursorSkills(targetRoot, dryRun, cursorReady) {
|
|
@@ -278,29 +321,13 @@ function ensureCursorSkills(targetRoot, dryRun, cursorReady) {
|
|
|
278
321
|
|
|
279
322
|
const packageSkillsDir = path.join(packageRoot, "src", ".agents", "skills");
|
|
280
323
|
if (!fs.existsSync(packageSkillsDir)) return { blocked: [] };
|
|
281
|
-
|
|
282
|
-
const skillNames = [];
|
|
283
|
-
try {
|
|
284
|
-
for (const name of fs.readdirSync(packageSkillsDir)) {
|
|
285
|
-
const skillPath = path.join(packageSkillsDir, name);
|
|
286
|
-
if (fs.statSync(skillPath).isDirectory() && fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
287
|
-
skillNames.push(name);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch (_) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
324
|
const skillsDir = path.join(cursorDir, "skills");
|
|
295
|
-
const type = process.platform === "win32" ? "dir" : "dir";
|
|
296
|
-
|
|
297
325
|
if (dryRun) {
|
|
298
|
-
console.log("[dry-run] Would
|
|
326
|
+
console.log("[dry-run] Would sync .cursor/skills from package skills (Cursor)");
|
|
299
327
|
return { blocked: [] };
|
|
300
328
|
}
|
|
301
329
|
|
|
302
|
-
if (
|
|
303
|
-
else {
|
|
330
|
+
if (fs.existsSync(skillsDir)) {
|
|
304
331
|
const stat = fs.lstatSync(skillsDir);
|
|
305
332
|
if (!stat.isDirectory()) {
|
|
306
333
|
console.warn("Skipped .cursor/skills because it exists and is not a directory");
|
|
@@ -308,50 +335,42 @@ function ensureCursorSkills(targetRoot, dryRun, cursorReady) {
|
|
|
308
335
|
}
|
|
309
336
|
}
|
|
310
337
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
let needCreate = true;
|
|
317
|
-
try {
|
|
318
|
-
const stat = fs.lstatSync(linkPath);
|
|
319
|
-
if (stat.isSymbolicLink() && symlinkPointsTo(linkPath, targetAbs)) needCreate = false;
|
|
320
|
-
else if (!stat.isSymbolicLink()) {
|
|
321
|
-
console.warn("Skipped", ".cursor/skills/" + name, "because it exists and is not a symlink");
|
|
322
|
-
blocked.push(linkPath);
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
} catch (_) {}
|
|
338
|
+
fs.rmSync(skillsDir, { recursive: true, force: true });
|
|
339
|
+
copyDirContents(packageSkillsDir, skillsDir);
|
|
340
|
+
console.log("Synced .cursor/skills from package skills (Cursor)");
|
|
341
|
+
return { blocked: [] };
|
|
342
|
+
}
|
|
326
343
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
344
|
+
function hasCursorPluginCommands(targetRoot) {
|
|
345
|
+
const pluginPath = path.join(targetRoot, ".cursor-plugin", "plugin.json");
|
|
346
|
+
if (!fs.existsSync(pluginPath)) return false;
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(fs.readFileSync(pluginPath, "utf8"));
|
|
349
|
+
return typeof parsed?.commands === "string" && parsed.commands.trim().length > 0;
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
332
352
|
}
|
|
333
|
-
return { blocked };
|
|
334
353
|
}
|
|
335
354
|
|
|
336
|
-
function verifyCursorIntegration(targetRoot) {
|
|
355
|
+
function verifyCursorIntegration(targetRoot, options = {}) {
|
|
337
356
|
const cursorDir = path.join(targetRoot, ".cursor");
|
|
338
357
|
if (!fs.existsSync(cursorDir)) return [];
|
|
358
|
+
const skipCommands = options.skipCommands === true;
|
|
339
359
|
|
|
340
360
|
const checks = [
|
|
341
361
|
{ name: ".cursor/agents", rel: path.join("agents") },
|
|
342
|
-
{ name: ".cursor/commands", rel: path.join("commands") },
|
|
343
362
|
{ name: ".cursor/references", rel: path.join("references") },
|
|
344
363
|
];
|
|
364
|
+
if (!skipCommands) checks.splice(1, 0, { name: ".cursor/commands", rel: path.join("commands") });
|
|
345
365
|
const issues = [];
|
|
346
366
|
|
|
347
367
|
for (const check of checks) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
if (!fs.existsSync(linkPath)) issues.push(`${check.name} is missing`);
|
|
368
|
+
const dirPath = path.join(cursorDir, check.rel);
|
|
369
|
+
if (!fs.existsSync(dirPath)) issues.push(`${check.name} is missing`);
|
|
351
370
|
else {
|
|
352
|
-
const stat = fs.lstatSync(
|
|
353
|
-
if (stat.
|
|
354
|
-
issues.push(`${check.name}
|
|
371
|
+
const stat = fs.lstatSync(dirPath);
|
|
372
|
+
if (!stat.isDirectory()) {
|
|
373
|
+
issues.push(`${check.name} exists but is not a directory`);
|
|
355
374
|
}
|
|
356
375
|
}
|
|
357
376
|
}
|
|
@@ -374,10 +393,16 @@ function verifyCursorIntegration(targetRoot) {
|
|
|
374
393
|
}
|
|
375
394
|
|
|
376
395
|
const packageRoots = [
|
|
377
|
-
{ label: "commands", pkgDir: path.join(packageRoot, "src", ".agents", "commands"), cursorSubdir: "commands" },
|
|
378
396
|
{ label: "agents", pkgDir: path.join(packageRoot, "src", ".agents", "agents"), cursorSubdir: "agents" },
|
|
379
397
|
{ label: "references", pkgDir: path.join(packageRoot, "src", ".agents", "references"), cursorSubdir: "references" },
|
|
380
398
|
];
|
|
399
|
+
if (!skipCommands) {
|
|
400
|
+
packageRoots.unshift({
|
|
401
|
+
label: "commands",
|
|
402
|
+
pkgDir: path.join(packageRoot, "src", ".agents", "commands"),
|
|
403
|
+
cursorSubdir: "commands",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
381
406
|
for (const item of packageRoots) {
|
|
382
407
|
if (!fs.existsSync(item.pkgDir)) continue;
|
|
383
408
|
const pkgFiles = walkFiles(item.pkgDir, () => true);
|
|
@@ -401,6 +426,7 @@ function verifyCursorIntegration(targetRoot) {
|
|
|
401
426
|
function ensureCursorIntegration(targetRoot, dryRun, forceCursor) {
|
|
402
427
|
const cursorDir = path.join(targetRoot, ".cursor");
|
|
403
428
|
let cursorReady = fs.existsSync(cursorDir);
|
|
429
|
+
const skipCommands = hasCursorPluginCommands(targetRoot);
|
|
404
430
|
if (!fs.existsSync(cursorDir)) {
|
|
405
431
|
if (!forceCursor) return { issues: [], status: "skipped-no-cursor" };
|
|
406
432
|
if (dryRun) console.log("[dry-run] Would create .cursor directory (Cursor)");
|
|
@@ -413,25 +439,27 @@ function ensureCursorIntegration(targetRoot, dryRun, forceCursor) {
|
|
|
413
439
|
|
|
414
440
|
const skillReport = ensureCursorSkills(targetRoot, dryRun, cursorReady);
|
|
415
441
|
const dirReports = [
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
ensureCursorDirSymlink(targetRoot, "references", "references", dryRun, "package references", cursorReady),
|
|
442
|
+
ensureCursorDirSync(targetRoot, "agents", "agents", dryRun, "package agents", cursorReady),
|
|
443
|
+
ensureCursorDirSync(targetRoot, "references", "references", dryRun, "package references", cursorReady),
|
|
419
444
|
];
|
|
445
|
+
if (!skipCommands) {
|
|
446
|
+
dirReports.splice(1, 0, ensureCursorDirSync(targetRoot, "commands", "commands", dryRun, "package commands", cursorReady));
|
|
447
|
+
}
|
|
420
448
|
|
|
421
449
|
const issues = [];
|
|
422
450
|
if (skillReport?.blocked?.length) {
|
|
423
451
|
for (const p of skillReport.blocked) issues.push(`${path.relative(targetRoot, p)} blocks symlink creation (not a symlink)`);
|
|
424
452
|
}
|
|
425
453
|
for (const report of dirReports) {
|
|
426
|
-
if (report?.status === "blocked-
|
|
427
|
-
issues.push(`${path.relative(targetRoot, report.path)} blocks
|
|
454
|
+
if (report?.status === "blocked-nondirectory") {
|
|
455
|
+
issues.push(`${path.relative(targetRoot, report.path)} blocks sync (not a directory)`);
|
|
428
456
|
}
|
|
429
457
|
}
|
|
430
458
|
|
|
431
459
|
if (!dryRun) {
|
|
432
|
-
for (const issue of verifyCursorIntegration(targetRoot)) issues.push(issue);
|
|
460
|
+
for (const issue of verifyCursorIntegration(targetRoot, { skipCommands })) issues.push(issue);
|
|
433
461
|
}
|
|
434
|
-
return { issues, status: "configured" };
|
|
462
|
+
return { issues, status: "configured", skipCommands };
|
|
435
463
|
}
|
|
436
464
|
|
|
437
465
|
function writeOpenCodeJson(targetRoot, dryRun) {
|
|
@@ -579,14 +607,20 @@ function main() {
|
|
|
579
607
|
console.log("Package root:", packageRoot);
|
|
580
608
|
console.log("OpenCode CLI detected:", hasCommand("opencode") ? "yes" : "no");
|
|
581
609
|
|
|
610
|
+
syncRuntimeAssets(targetRoot, args.dryRun);
|
|
582
611
|
writeOpenCodeJson(targetRoot, args.dryRun);
|
|
583
612
|
ensureSkillsSymlink(targetRoot, args.dryRun);
|
|
584
613
|
reportOpenCodeIntegration(targetRoot, args.dryRun);
|
|
585
|
-
const
|
|
614
|
+
const cursorExists = fs.existsSync(path.join(targetRoot, ".cursor"));
|
|
615
|
+
const cursorReport = ensureCursorIntegration(targetRoot, args.dryRun, cursorExists);
|
|
586
616
|
if (cursorReport.status === "skipped-no-cursor") {
|
|
587
|
-
console.log("Cursor integration: skipped (.cursor
|
|
617
|
+
console.log("Cursor integration: skipped (.cursor not found).");
|
|
588
618
|
} else {
|
|
589
|
-
|
|
619
|
+
if (cursorReport.skipCommands) {
|
|
620
|
+
console.log("Cursor integration: verified skills, agents, references (commands supplied by .cursor-plugin).");
|
|
621
|
+
} else {
|
|
622
|
+
console.log("Cursor integration: verified skills, agents, commands, and references.");
|
|
623
|
+
}
|
|
590
624
|
}
|
|
591
625
|
writeAgentsMd(targetRoot, args.dryRun);
|
|
592
626
|
ensureDirs(targetRoot, args.dryRun);
|
|
@@ -138,16 +138,23 @@ The input must be a plan file path.
|
|
|
138
138
|
- If you are already on a branch that clearly matches this plan, continue.
|
|
139
139
|
- Otherwise, continue anyway — the current active branch remains the reference/base for a new worktree unless the user explicitly requests a different base.
|
|
140
140
|
|
|
141
|
-
2)
|
|
141
|
+
2) Resolve the user decision (required prompt/create gate):
|
|
142
142
|
|
|
143
|
-
-
|
|
143
|
+
- If the user already gave an explicit instruction in this run:
|
|
144
|
+
- "create a worktree" / "yes use a worktree" => use worktree path
|
|
145
|
+
- "do not use a worktree" / "no worktree" => opt-out path
|
|
146
|
+
- Otherwise, you MUST ask this exact decision before proceeding:
|
|
147
|
+
- "Use a worktree for this work? (Yes/No; default recommendation: Yes)"
|
|
144
148
|
- Options:
|
|
145
149
|
- Yes (worktree)
|
|
146
150
|
- No (stay in current checkout; create/switch to a feature branch)
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
Mandatory behavior:
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
- Do not infer or assume an answer when the user has not answered.
|
|
155
|
+
- Do not run `skill: git-worktree` until the user has answered Yes (or already explicitly requested worktree creation).
|
|
156
|
+
- If Yes: ask for the new branch name when missing (e.g., `feat/<slug>`, `fix/<slug>`), then continue.
|
|
157
|
+
- If No: require explicit opt-out confirmation, then continue with the non-worktree path.
|
|
151
158
|
|
|
152
159
|
3) If worktree is chosen, run:
|
|
153
160
|
|