@curdx/flow 1.1.1

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/cli/doctor.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * doctor command — external health check (no need to enter Claude Code).
3
+ */
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import {
8
+ color,
9
+ log,
10
+ runSync,
11
+ claudeVersion,
12
+ listPlugins,
13
+ listMcps,
14
+ ensureClaudeMemRuntimes,
15
+ } from "./utils.js";
16
+
17
+ export async function doctor(args = []) {
18
+ const verbose = args.includes("--verbose") || args.includes("-v");
19
+
20
+ log.title("🏥 CurDX-Flow Health Check");
21
+
22
+ let errors = 0;
23
+ let warnings = 0;
24
+
25
+ // ---------- claude CLI ----------
26
+ const cv = claudeVersion();
27
+ if (cv) {
28
+ log.ok(`claude CLI ${color.dim(cv)}`);
29
+ } else {
30
+ log.err("claude CLI not found (install Claude Code)");
31
+ errors++;
32
+ }
33
+
34
+ // ---------- Node ----------
35
+ log.ok(`Node ${color.dim(process.version)}`);
36
+
37
+ // ---------- curdx-flow plugin ----------
38
+ const plugins = cv ? listPlugins() : [];
39
+ const curdx = plugins.find((p) => p.name === "curdx-flow");
40
+ if (curdx) {
41
+ if (curdx.status === "enabled") {
42
+ log.ok(`curdx-flow ${color.dim(`v${curdx.version} (enabled)`)}`);
43
+ } else {
44
+ log.err(`curdx-flow v${curdx.version} (${curdx.status})`);
45
+ errors++;
46
+ }
47
+ } else {
48
+ log.warn("curdx-flow not installed → run curdx-flow install");
49
+ warnings++;
50
+ }
51
+
52
+ // ---------- MCPs ----------
53
+ console.log(`\n${color.bold("MCP Servers:")}`);
54
+ const mcps = cv ? listMcps() : [];
55
+ const expectedMcps = ["context7", "sequential-thinking", "chrome-devtools"];
56
+ for (const m of expectedMcps) {
57
+ const found = mcps.find((x) => x.name === m);
58
+ if (found) {
59
+ log.ok(`${m.padEnd(22)} ${color.dim("auto-loaded")}`);
60
+ } else {
61
+ if (curdx) {
62
+ log.warn(`${m.padEnd(22)} not shown in claude mcp list (restart Claude Code may fix)`);
63
+ warnings++;
64
+ } else {
65
+ log.info(`${m.padEnd(22)} waiting for curdx-flow install`);
66
+ }
67
+ }
68
+ }
69
+
70
+ // ---------- Recommended plugins ----------
71
+ console.log(`\n${color.bold("Recommended plugins:")}`);
72
+ const recommended = [
73
+ { name: "pua", installCmd: "claude plugin install pua@pua-skills" },
74
+ { name: "claude-mem", installCmd: "claude plugin install claude-mem@thedotmack" },
75
+ { name: "frontend-design", installCmd: "claude plugin install frontend-design@claude-plugins-official" },
76
+ ];
77
+ let claudeMemEnabled = false;
78
+ for (const r of recommended) {
79
+ const p = plugins.find((x) => x.name === r.name);
80
+ if (p && p.status === "enabled") {
81
+ log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version}`)}`);
82
+ if (r.name === "claude-mem") claudeMemEnabled = true;
83
+ } else if (p && p.status === "failed") {
84
+ log.err(`${r.name.padEnd(22)} load failed`);
85
+ errors++;
86
+ } else {
87
+ log.warn(`${r.name.padEnd(22)} not installed ${color.dim("(run: curdx-flow install --all)")}`);
88
+ warnings++;
89
+ }
90
+ }
91
+
92
+ // ---------- Runtime PATH guards (only if claude-mem is installed) ----------
93
+ if (claudeMemEnabled) {
94
+ console.log(`\n${color.bold("Runtime (claude-mem dependencies):")}`);
95
+ const rt = ensureClaudeMemRuntimes();
96
+ for (const [name, res] of Object.entries(rt)) {
97
+ if (res.status === "ok") {
98
+ log.ok(`${name.padEnd(22)} ${color.dim("visible on PATH")}`);
99
+ } else if (res.status === "linked") {
100
+ log.ok(
101
+ `${name.padEnd(22)} ${color.dim(`auto-linked ${res.link} → ${res.path}`)}`
102
+ );
103
+ } else if (res.status === "missing") {
104
+ log.warn(
105
+ `${name.padEnd(22)} not installed ${color.dim("(claude-mem will auto-install on next Claude Code session)")}`
106
+ );
107
+ warnings++;
108
+ } else if (res.status === "path-unwritable") {
109
+ const dir = res.path.split("/").slice(0, -1).join("/");
110
+ log.err(
111
+ `${name.padEnd(22)} installed but not on PATH ${color.dim(`(add export PATH="${dir}:$PATH" to your shell rc)`)}`
112
+ );
113
+ errors++;
114
+ }
115
+ }
116
+ }
117
+
118
+ // ---------- Project state ----------
119
+ console.log(`\n${color.bold("Local project:")}`);
120
+ const cwd = process.cwd();
121
+ const flowDir = path.join(cwd, ".flow");
122
+ try {
123
+ const stat = await fs.stat(flowDir);
124
+ if (stat.isDirectory()) {
125
+ log.ok(`.flow/ ${color.dim(cwd)}`);
126
+ // Check active spec
127
+ try {
128
+ const active = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
129
+ log.info(`Active spec ${color.cyan(active.trim())}`);
130
+ } catch {
131
+ log.info("Active spec (none)");
132
+ }
133
+ }
134
+ } catch {
135
+ log.info(`.flow/ not a curdx-flow project (run: curdx-flow init)`);
136
+ }
137
+
138
+ // ---------- Summary ----------
139
+ console.log();
140
+ if (errors > 0) {
141
+ console.log(color.red(`Summary: ${errors} error(s), ${warnings} warning(s)`));
142
+ console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
143
+ process.exit(1);
144
+ } else if (warnings > 0) {
145
+ console.log(color.yellow(`Summary: ${warnings} warning(s). Usable, but worth addressing.`));
146
+ } else {
147
+ console.log(color.green("Summary: all healthy ✓"));
148
+ }
149
+
150
+ if (verbose) {
151
+ console.log(`\n${color.bold("Details:")}`);
152
+ console.log(color.dim(` Plugins raw:`));
153
+ console.log(runSync("claude", ["plugin", "list"]).stdout);
154
+ }
155
+ }
package/cli/init.js ADDED
@@ -0,0 +1,296 @@
1
+ /**
2
+ * init command — external version of /curdx-flow:init.
3
+ * Creates .flow/ structure in target directory.
4
+ *
5
+ * Uses templates from plugin cache if available, falls back to embedded templates.
6
+ */
7
+
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { existsSync } from "node:fs";
11
+ import {
12
+ color,
13
+ log,
14
+ confirm,
15
+ pluginCacheDir,
16
+ has,
17
+ runSync,
18
+ } from "./utils.js";
19
+
20
+ export async function init(args = []) {
21
+ const force = args.includes("--force");
22
+ const target = args.find((a) => !a.startsWith("--")) || process.cwd();
23
+ const absTarget = path.resolve(target);
24
+
25
+ log.title(`📦 Initialize CurDX-Flow project`);
26
+ console.log(` Target: ${color.cyan(absTarget)}`);
27
+
28
+ // ---------- Check target exists ----------
29
+ try {
30
+ const stat = await fs.stat(absTarget);
31
+ if (!stat.isDirectory()) {
32
+ log.err(`Target is not a directory: ${absTarget}`);
33
+ process.exit(1);
34
+ }
35
+ } catch {
36
+ log.err(`Directory does not exist: ${absTarget}`);
37
+ process.exit(1);
38
+ }
39
+
40
+ // ---------- Check existing .flow/ ----------
41
+ const flowDir = path.join(absTarget, ".flow");
42
+ if (existsSync(flowDir)) {
43
+ if (!force) {
44
+ log.warn(`.flow/ already exists`);
45
+ const ok = await confirm("Overwrite existing .flow/?", false);
46
+ if (!ok) {
47
+ log.info("Cancelled");
48
+ return;
49
+ }
50
+ }
51
+ }
52
+
53
+ // ---------- Find templates ----------
54
+ const tmplDir = await findLatestTemplatesDir();
55
+
56
+ // ---------- Create directory structure ----------
57
+ log.step(1, 4, "Creating .flow/ directory structure...");
58
+ const dirs = [
59
+ ".flow",
60
+ ".flow/specs",
61
+ ".flow/_epics",
62
+ ".flow/checkpoints",
63
+ ".flow/threads",
64
+ ".flow/seeds",
65
+ ];
66
+ for (const d of dirs) {
67
+ await fs.mkdir(path.join(absTarget, d), { recursive: true });
68
+ }
69
+ log.ok("Directories created");
70
+
71
+ // ---------- Generate files from templates ----------
72
+ log.step(2, 4, "Generating core files...");
73
+ const today = new Date().toISOString().slice(0, 10);
74
+ const userName = gitConfig("user.name") || "you";
75
+ const projectName = path.basename(absTarget);
76
+
77
+ const files = [
78
+ { tmpl: "PROJECT.md.tmpl", out: "PROJECT.md" },
79
+ { tmpl: "CONTEXT.md.tmpl", out: "CONTEXT.md" },
80
+ { tmpl: "STATE.md.tmpl", out: "STATE.md" },
81
+ { tmpl: "ROADMAP.md.tmpl", out: "ROADMAP.md" },
82
+ { tmpl: "config.json.tmpl", out: "config.json" },
83
+ ];
84
+
85
+ for (const f of files) {
86
+ const outPath = path.join(flowDir, f.out);
87
+ if (existsSync(outPath) && !force) {
88
+ log.info(` ${f.out} already exists, skipping`);
89
+ continue;
90
+ }
91
+
92
+ let content;
93
+ if (tmplDir) {
94
+ try {
95
+ content = await fs.readFile(path.join(tmplDir, f.tmpl), "utf-8");
96
+ } catch {
97
+ content = embeddedTemplate(f.tmpl);
98
+ }
99
+ } else {
100
+ content = embeddedTemplate(f.tmpl);
101
+ }
102
+
103
+ // Placeholder replacement
104
+ content = content
105
+ .replace(/\{\{PROJECT_NAME\}\}/g, projectName)
106
+ .replace(/\{\{CREATED_DATE\}\}/g, today)
107
+ .replace(/\{\{USER_NAME\}\}/g, userName);
108
+
109
+ await fs.writeFile(outPath, content, "utf-8");
110
+ log.ok(` .flow/${f.out}`);
111
+ }
112
+
113
+ // ---------- Update .gitignore ----------
114
+ log.step(3, 4, "Updating .gitignore...");
115
+ const giPath = path.join(absTarget, ".gitignore");
116
+ const giAdd = [
117
+ "",
118
+ "# CurDX-Flow runtime (auto-generated, do not commit)",
119
+ ".flow/checkpoints/",
120
+ ".flow/threads/",
121
+ ".flow/seeds/",
122
+ ".flow/.active-spec",
123
+ ".flow/specs/*/.state.json",
124
+ ".flow/specs/*/.progress.md",
125
+ ".flow/_epics/*/.epic-state.json",
126
+ ].join("\n");
127
+
128
+ let existingGi = "";
129
+ try {
130
+ existingGi = await fs.readFile(giPath, "utf-8");
131
+ } catch {
132
+ // no .gitignore yet
133
+ }
134
+
135
+ if (!existingGi.includes("CurDX-Flow")) {
136
+ await fs.writeFile(giPath, existingGi + giAdd + "\n", "utf-8");
137
+ log.ok(".gitignore appended with CurDX-Flow ignores");
138
+ } else {
139
+ log.info(".gitignore already has CurDX-Flow rules, skipping");
140
+ }
141
+
142
+ // ---------- Done ----------
143
+ log.blank();
144
+ log.step(4, 4, "Done");
145
+ log.ok(`CurDX-Flow project initialized: ${color.cyan(absTarget)}`);
146
+
147
+ console.log(`\n${color.bold("Next steps:")}\n`);
148
+ console.log(` 1. Edit ${color.cyan(".flow/PROJECT.md")} — fill in the project vision (~5 min)`);
149
+ console.log(` 2. Edit ${color.cyan(".flow/CONTEXT.md")} — your preferences`);
150
+ console.log(` 3. Enter Claude Code:`);
151
+ console.log(` ${color.cyan("cd " + absTarget)}`);
152
+ console.log(` ${color.cyan("claude")}`);
153
+ console.log(` ${color.cyan("/curdx-flow:start my-feature \"<description>\"")}`);
154
+ console.log();
155
+ }
156
+
157
+ // ---------- Helpers ----------
158
+
159
+ /**
160
+ * Find the latest installed templates directory in plugin cache.
161
+ * Returns null if plugin not installed.
162
+ */
163
+ async function findLatestTemplatesDir() {
164
+ const pluginBase = pluginCacheDir();
165
+ try {
166
+ const versions = await fs.readdir(pluginBase);
167
+ // Pick last (lexicographically — semver works for simple cases)
168
+ const sorted = versions.sort();
169
+ for (let i = sorted.length - 1; i >= 0; i--) {
170
+ const tmplDir = path.join(pluginBase, sorted[i], "templates");
171
+ if (existsSync(tmplDir)) return tmplDir;
172
+ }
173
+ } catch {
174
+ return null;
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function gitConfig(key) {
180
+ if (!has("git")) return null;
181
+ const r = runSync("git", ["config", "--get", key]);
182
+ return r.code === 0 ? r.stdout.trim() : null;
183
+ }
184
+
185
+ /**
186
+ * Embedded fallback templates — used when plugin cache not available.
187
+ * Keep these minimal; rich templates live in templates/*.tmpl.
188
+ *
189
+ * Note: these embedded templates are English. Users who want Chinese
190
+ * project state can edit the resulting files (or set lang preference
191
+ * in CONTEXT.md and use the rich templates from the plugin cache).
192
+ */
193
+ function embeddedTemplate(name) {
194
+ const today = "{{CREATED_DATE}}";
195
+ const project = "{{PROJECT_NAME}}";
196
+ const user = "{{USER_NAME}}";
197
+
198
+ switch (name) {
199
+ case "PROJECT.md.tmpl":
200
+ return `# ${project}
201
+
202
+ > CurDX-Flow project vision
203
+
204
+ ## One-line description
205
+ TODO: in one sentence — what is the project, who is it for, what problem does it solve
206
+
207
+ ## Why this exists
208
+ TODO:
209
+
210
+ ## Core users
211
+ TODO:
212
+
213
+ ## Success criteria (verifiable metrics)
214
+ 1. TODO:
215
+ 2. TODO:
216
+
217
+ ## Tech stack
218
+ TODO:
219
+
220
+ ## Out of scope (Scope Guard)
221
+ - ✗ TODO:
222
+
223
+ ---
224
+
225
+ _Generated by curdx-flow init on ${today}. Maintainer: ${user}_
226
+ `;
227
+
228
+ case "CONTEXT.md.tmpl":
229
+ return `# ${project} — User preferences
230
+
231
+ ## Code style
232
+ - Indentation: TODO
233
+ - Quotes: TODO
234
+ - Naming: TODO
235
+
236
+ ## Communication preferences
237
+ - Language: en
238
+ - Verbosity: balanced
239
+
240
+ ## Tooling preferences
241
+ - Package manager: TODO
242
+
243
+ ---
244
+
245
+ _Generated by curdx-flow init on ${today}_
246
+ `;
247
+
248
+ case "STATE.md.tmpl":
249
+ return `# ${project} — Cross-session state
250
+
251
+ ## Key decisions (Decisions)
252
+ None yet.
253
+
254
+ ## Blockers
255
+ None yet.
256
+
257
+ ---
258
+
259
+ _Initialized on ${today}_
260
+ `;
261
+
262
+ case "ROADMAP.md.tmpl":
263
+ return `# ${project} — Roadmap
264
+
265
+ ## Current version: v0.1 (MVP)
266
+
267
+ **Goal**: TODO
268
+
269
+ ### Success criteria
270
+ - [ ] TODO
271
+
272
+ ---
273
+
274
+ _Initialized on ${today}_
275
+ `;
276
+
277
+ case "config.json.tmpl":
278
+ return `{
279
+ "version": "1.0",
280
+ "mode": "standard",
281
+ "execution": {
282
+ "strategy": "auto",
283
+ "max_parallel": 5
284
+ },
285
+ "gates": {
286
+ "always_on": ["karpathy-gate", "verification-gate"],
287
+ "standard_mode": ["tdd-gate", "coverage-audit-gate"]
288
+ },
289
+ "created": "${today}"
290
+ }
291
+ `;
292
+
293
+ default:
294
+ return `# ${name}\n\n(template not found)\n`;
295
+ }
296
+ }
package/cli/install.js ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * install command — install curdx-flow plugin + optional recommended plugins.
3
+ */
4
+
5
+ import {
6
+ color,
7
+ log,
8
+ run,
9
+ has,
10
+ claudeVersion,
11
+ listPlugins,
12
+ multiSelect,
13
+ ensureClaudeMemRuntimes,
14
+ } from "./utils.js";
15
+ import { injectGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
16
+
17
+ // Recommended plugins with their marketplace source + install identifier
18
+ const RECOMMENDED = [
19
+ {
20
+ name: "pua",
21
+ marketplace: "tanweai/pua",
22
+ installSpec: "pua@pua-skills",
23
+ hint: "no-give-up + three red lines",
24
+ },
25
+ {
26
+ name: "claude-mem",
27
+ marketplace: "thedotmack/claude-mem",
28
+ installSpec: "claude-mem@thedotmack",
29
+ hint: "automatic cross-session memory",
30
+ },
31
+ {
32
+ name: "frontend-design",
33
+ marketplace: null, // already in default marketplace claude-plugins-official
34
+ installSpec: "frontend-design@claude-plugins-official",
35
+ hint: "Anthropic official UI skill",
36
+ },
37
+ ];
38
+
39
+ export async function install(args = []) {
40
+ const all = args.includes("--all");
41
+ const noDeps = args.includes("--no-deps");
42
+
43
+ log.title("🚀 CurDX-Flow Installer");
44
+
45
+ // ---------- Step 1: Check claude CLI ----------
46
+ log.step(1, 4, "Checking claude CLI...");
47
+ const ver = claudeVersion();
48
+ if (!ver) {
49
+ log.err("claude CLI not found. Install Claude Code from https://code.claude.com first.");
50
+ process.exit(1);
51
+ }
52
+ log.ok(`claude CLI found (${ver})`);
53
+
54
+ // ---------- Step 2: Add marketplace ----------
55
+ log.blank();
56
+ log.step(2, 4, "Adding curdx-flow marketplace...");
57
+ const addRes = await run("claude", ["plugin", "marketplace", "add", "curdx/curdx-flow"], {
58
+ silent: true,
59
+ });
60
+ if (addRes.code !== 0 && !addRes.stderr.includes("already")) {
61
+ // Not a fatal error if already added
62
+ log.warn(`marketplace add output: ${addRes.stderr.trim() || addRes.stdout.trim()}`);
63
+ } else {
64
+ log.ok("curdx-flow-marketplace added");
65
+ }
66
+
67
+ // ---------- Step 3: Install curdx-flow plugin ----------
68
+ log.blank();
69
+ log.step(3, 4, "Installing curdx-flow plugin (3 MCPs will auto-start)...");
70
+ const installed = listPlugins();
71
+ const already = installed.find((p) => p.name === "curdx-flow");
72
+ if (already) {
73
+ log.ok(`curdx-flow already installed (v${already.version}, ${already.status})`);
74
+ } else {
75
+ const r = await run(
76
+ "claude",
77
+ ["plugin", "install", "curdx-flow@curdx-flow-marketplace"],
78
+ { silent: true }
79
+ );
80
+ if (r.code !== 0) {
81
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
82
+ process.exit(1);
83
+ }
84
+ log.ok("curdx-flow installed");
85
+ }
86
+
87
+ // ---------- Step 4: Recommended plugins ----------
88
+ log.blank();
89
+ log.step(4, 4, "Recommended plugins");
90
+
91
+ if (noDeps) {
92
+ log.info("Skipping recommended plugins (--no-deps)");
93
+ printNextSteps();
94
+ return;
95
+ }
96
+
97
+ let toInstall;
98
+ if (all) {
99
+ toInstall = RECOMMENDED.map((r) => r.name);
100
+ log.info("--all mode: installing all recommended");
101
+ } else {
102
+ const currentlyInstalled = new Set(listPlugins().map((p) => p.name));
103
+ const choices = RECOMMENDED.map((r) => ({
104
+ label: `${color.bold(r.name)}${currentlyInstalled.has(r.name) ? color.green(" (installed)") : ""}`,
105
+ value: r.name,
106
+ hint: r.hint,
107
+ }));
108
+ const defaults = RECOMMENDED
109
+ .map((r, i) => (currentlyInstalled.has(r.name) ? -1 : i))
110
+ .filter((i) => i >= 0);
111
+
112
+ toInstall = await multiSelect("Which recommended plugins to install?", choices, defaults);
113
+ }
114
+
115
+ if (!toInstall || toInstall.length === 0) {
116
+ log.info("No recommended plugins selected, skipping");
117
+ printNextSteps();
118
+ return;
119
+ }
120
+
121
+ // Install each
122
+ for (const pluginName of toInstall) {
123
+ const rec = RECOMMENDED.find((r) => r.name === pluginName);
124
+ log.blank();
125
+ console.log(` ${color.cyan("⏳")} Installing ${color.bold(rec.name)}...`);
126
+
127
+ // 1. Add marketplace (if needed)
128
+ if (rec.marketplace) {
129
+ const ma = await run(
130
+ "claude",
131
+ ["plugin", "marketplace", "add", rec.marketplace],
132
+ { silent: true }
133
+ );
134
+ if (ma.code !== 0 && !ma.stderr.includes("already")) {
135
+ log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
136
+ // Don't abort — may already exist
137
+ }
138
+ }
139
+
140
+ // 2. Install
141
+ const ir = await run("claude", ["plugin", "install", rec.installSpec], {
142
+ silent: true,
143
+ });
144
+ if (ir.code === 0) {
145
+ console.log(` ${color.green("✓")} ${rec.name} installed`);
146
+
147
+ // 3. Post-install hook for claude-mem: its .mcp.json hard-codes `bun`,
148
+ // but ~/.bun/bin is not on PATH when Claude Code spawns the MCP server.
149
+ // Auto-create a PATH-visible symlink to fix it.
150
+ if (rec.name === "claude-mem") {
151
+ const r = ensureClaudeMemRuntimes();
152
+ for (const [name, res] of Object.entries(r)) {
153
+ if (res.status === "linked") {
154
+ console.log(
155
+ ` ${color.green("✓")} ${name} → PATH symlink created ${color.dim(`(${res.link} → ${res.path})`)}`
156
+ );
157
+ } else if (res.status === "missing") {
158
+ console.log(
159
+ ` ${color.yellow("⚠")} ${name} not installed ${color.dim("(claude-mem will auto-install on first run; or run: curdx-flow doctor)")}`
160
+ );
161
+ } else if (res.status === "path-unwritable") {
162
+ console.log(
163
+ ` ${color.yellow("⚠")} ${name} installed ${color.dim(`(${res.path}) but no writable PATH location — add export PATH=\"${res.path.split("/").slice(0,-1).join("/")}:$PATH\" to your shell rc`)}`
164
+ );
165
+ }
166
+ // status === "ok" → already on PATH, stay silent
167
+ }
168
+ }
169
+ } else {
170
+ console.log(
171
+ ` ${color.red("✗")} ${rec.name} install failed: ${ir.stderr.trim().split("\n").pop()}`
172
+ );
173
+ console.log(
174
+ color.dim(
175
+ ` Run manually: claude plugin install ${rec.installSpec}`
176
+ )
177
+ );
178
+ }
179
+ }
180
+
181
+ // ---------- Step 5: inject global protocols ----------
182
+ log.blank();
183
+ console.log(color.dim("Injecting global protocols into ~/.claude/CLAUDE.md..."));
184
+ try {
185
+ const r = injectGlobalProtocols();
186
+ if (r.action === "created") {
187
+ log.ok(`Global protocols injected ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
188
+ } else if (r.action === "upgraded") {
189
+ log.ok(`Global protocols upgraded ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
190
+ } else {
191
+ log.info(`Global protocols up to date ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
192
+ }
193
+ } catch (err) {
194
+ log.warn(`Protocol injection failed: ${err.message} ${color.dim("(non-blocking)")}`);
195
+ }
196
+
197
+ printNextSteps();
198
+ }
199
+
200
+ function printNextSteps() {
201
+ console.log(`\n${color.bold("✅ Install complete")}\n`);
202
+ console.log(`${color.bold("Next steps")}:\n`);
203
+ console.log(` ${color.dim("# Verify health")}`);
204
+ console.log(` curdx-flow doctor\n`);
205
+ console.log(` ${color.dim("# Initialize .flow/ in your project")}`);
206
+ console.log(` cd ~/your-project && curdx-flow init\n`);
207
+ console.log(` ${color.dim("# Start using it (inside Claude Code)")}`);
208
+ console.log(` ${color.cyan("claude")}`);
209
+ console.log(` ${color.cyan("/curdx-flow:start my-feature \"<describe what to build>\"")}\n`);
210
+ console.log(
211
+ `${color.bold("Learn more")}: https://github.com/curdx/curdx-flow/blob/main/docs/getting-started.md\n`
212
+ );
213
+ }