@curdx/flow 2.0.0-beta.15 → 2.0.0-beta.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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
9
- "version": "2.0.0-beta.15"
9
+ "version": "2.0.0-beta.17"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.0.0-beta.15",
3
+ "version": "2.0.0-beta.17",
4
4
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
5
5
  "author": {
6
6
  "name": "wdx",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to CurDX-Flow will be documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Fixed
8
+
9
+ - `hooks/hooks.json` + `hooks/scripts/inject-karpathy.sh` — migrated the L1 baseline injector from the `InstructionsLoaded` event to `SessionStart` with `matcher: "startup|clear|compact"`. Per the Claude Code hooks docs (code.claude.com/docs/en/hooks), `InstructionsLoaded` is an observability-only event: its hook schema has no `hookSpecificOutput` field, so the injector's `{"hookSpecificOutput":{"hookEventName":"InstructionsLoaded","additionalContext":…}}` payload was rejected at session boot with `Hook JSON output validation failed — (root): Invalid input`, producing the `SessionStart:startup hook error` banner on every Claude Code launch. The `startup|clear|compact` matcher preserves the original "baseline survives compaction" intent. Also updated `docs/architecture.md` and `README.zh.md` hook-event inventories.
10
+
7
11
  ### BREAKING
8
12
 
9
13
  - **`context7` and `sequential-thinking` moved from plugin-bundled MCPs to user-level MCPs.** Previously `.claude-plugin/plugin.json` declared both in `mcpServers`, so Claude Code auto-registered them as `plugin:curdx-flow:context7` and `plugin:curdx-flow:sequential-thinking` when the plugin installed. This is no longer the case — the `mcpServers` block is gone. Instead, `curdx-flow install` now runs `claude mcp add context7 …` + `claude mcp add sequential-thinking …` against the user's `~/.claude.json`, which keeps them as standard user-level registrations named `context7` and `sequential-thinking`.
package/README.md CHANGED
@@ -28,7 +28,7 @@ Not a framework. Not a methodology library. A discipline layer.
28
28
  npx @curdx/flow install --all
29
29
  ```
30
30
 
31
- This installs the Claude Code plugin (fully offline from the npm package — no GitHub fetch), the 3 auto-registered MCP servers, and three recommended companion plugins (pua, claude-mem, frontend-design).
31
+ This installs the Claude Code plugin (fully offline from the npm package — no GitHub fetch), the official Context7 plugin, the required sequential-thinking MCP server, and four recommended companion plugins (pua, claude-mem, frontend-design, chrome-devtools-mcp).
32
32
 
33
33
  ## 9 slash commands, that's it
34
34
 
@@ -97,7 +97,8 @@ npm i -g @curdx/flow@^1.1
97
97
  - **8 composable gates** — Karpathy / Verification / TDD / Coverage / Adversarial / Edge-Case / Security / DevEx
98
98
  - **4 execution strategies** for `/curdx-flow:implement` (linear / subagent / stop-hook / wave, auto-routed)
99
99
  - **5 hook events** that enforce the discipline without user action
100
- - **3 auto-installed MCPs** — context7 (latest docs), sequential-thinking (structured reasoning), chrome-devtools (browser automation)
100
+ - **Required docs/reasoning tools** — Context7 official plugin (MCP + skill + docs agent) and sequential-thinking MCP
101
+ - **4 recommended companion plugins** — pua, claude-mem, frontend-design, chrome-devtools-mcp
101
102
  - **Offline-capable install** — the npm package ships the full plugin body; zero GitHub round-trips during install
102
103
 
103
104
  ## Documentation
@@ -123,7 +124,7 @@ CurDX-Flow v2 is a distillation, not an invention. Ideas we depend on:
123
124
  - [**superpowers**](https://github.com/obra/superpowers) — subagent discipline + TDD + two-stage review
124
125
  - [**pua**](https://github.com/tanweai/pua) — the three red lines
125
126
  - [**claude-mem**](https://github.com/thedotmack/claude-mem) — cross-session memory
126
- - **Anthropic official** — context7 (Upstash), sequential-thinking, chrome-devtools-mcp, frontend-design skill
127
+ - **MCP / plugin ecosystem** — context7 (Upstash), sequential-thinking, chrome-devtools-mcp (Chrome DevTools team), frontend-design skill
127
128
 
128
129
  ## License
129
130
 
package/README.zh.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **Claude Code 的 AI 工程工作流元框架**
4
4
  > 把 Claude Code 变成有工程纪律的 AI 团队 — 编排 MCP 和插件,强制 Karpathy 4 原则,用规格驱动工作流交付高质量软件。
5
5
 
6
- [![Version](https://img.shields.io/badge/version-1.1.0-blue)](./CHANGELOG.md)
6
+ [![Version](https://img.shields.io/npm/v/@curdx/flow.svg)](https://www.npmjs.com/package/@curdx/flow)
7
7
  [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
8
8
  [![Plugin](https://img.shields.io/badge/claude--code-plugin-purple)](https://code.claude.com)
9
9
  [![English](https://img.shields.io/badge/doc-English-red)](./README.md)
@@ -16,15 +16,15 @@ CurDX-Flow 是一个 Claude Code 插件,把 6 个验证过的 AI 工程工作
16
16
 
17
17
  **不重造轮子。编排好轮子。**
18
18
 
19
- ## 一览(v1.1.0
19
+ ## 一览(v2
20
20
 
21
- - **31 个命令** — 初始化 / 研究 / 设计 / 执行 / 验证 / 审查 / 调试 / QA / 安全 / ...
22
- - **24 个代理**15 职能 + 9 人格(Mary / John / Winston / Amelia / Rachel / David / Oliver / Serena / Emma)
21
+ - **9 个命令** — 初始化 / 启动规格 / 规格 / 执行 / 验证 / 审查 / 调试 / fast / 帮助
22
+ - **15 个内部代理**由命令调度,不再暴露 v1 的人格角色
23
23
  - **8 个可组合 Gate** — Karpathy / Verification / TDD / Coverage / Adversarial / Edge-Case / Security / DevEx
24
24
  - **4 种执行策略** — linear / subagent / stop-hook / wave(自动路由)
25
25
  - **10 个知识文档** — 规格驱动 / POC-First / 原子提交 / 执行策略 / ...
26
- - **4 个 hook 事件** — SessionStart / InstructionsLoaded / Stop / PreToolUse
27
- - **2 个自动安装的 MCP + 1 个推荐插件** — context7 / sequential-thinking(plugin.json 内置)+ chrome-devtools-mcp(recommended,beta.8 解耦)
26
+ - **5 个 hook 事件** — SessionStart / InstructionsLoaded / PostToolUseFailure / Stop / PreToolUse
27
+ - **必需文档/推理工具 + 4 个推荐插件** — Context7 官方插件 / sequential-thinking MCP + pua / claude-mem / frontend-design / chrome-devtools-mcp
28
28
  - **优雅降级** — 依赖缺失时进入 fallback 模式并清晰告知
29
29
 
30
30
  ## 为什么用
@@ -41,23 +41,21 @@ CurDX-Flow 是一个 Claude Code 插件,把 6 个验证过的 AI 工程工作
41
41
 
42
42
  ## 安装
43
43
 
44
- ### 从 marketplace(发布后)
45
- ```
46
- /plugin marketplace add wdx/curdx-flow
47
- /plugin install curdx-flow
44
+ ```bash
45
+ npx @curdx/flow install --all
48
46
  ```
49
47
 
48
+ 这会用 `claude plugin ...` 非交互 CLI 安装 CurDX-Flow 插件、Context7 官方插件、必需 MCP,并安装推荐插件。
49
+
50
50
  ### 本地 dev
51
51
  ```bash
52
- git clone https://github.com/wdx/curdx-flow
52
+ git clone https://github.com/curdx/curdx-flow
53
53
  claude --plugin-dir ./curdx-flow
54
54
  ```
55
55
 
56
- 3 MCP 自动装。然后运行:
56
+ 安装后在 Claude Code 内运行:
57
57
 
58
58
  ```
59
- /curdx-flow:install-deps # 交互式安装 pua / claude-mem / frontend-design
60
- /curdx-flow:doctor # 验证健康
61
59
  /curdx-flow:init # 初始化你的项目
62
60
  ```
63
61
 
@@ -102,8 +100,8 @@ claude --plugin-dir ./curdx-flow
102
100
  | 文档 | 何时读 |
103
101
  |-----|------|
104
102
  | [`docs/getting-started.md`](./docs/getting-started.md) | 首次使用,5 分钟上手 |
105
- | [`docs/command-reference.md`](./docs/command-reference.md) | 31 个命令完整参考 |
106
- | [`docs/agent-reference.md`](./docs/agent-reference.md) | 24 个代理 + 人格 |
103
+ | [`docs/command-reference.md`](./docs/command-reference.md) | 9 个命令完整参考 |
104
+ | [`docs/agent-reference.md`](./docs/agent-reference.md) | 15 个内部代理 |
107
105
  | [`docs/workflows.md`](./docs/workflows.md) | 5 种典型场景(greenfield/brownfield/epic/fast/UI) |
108
106
  | [`docs/architecture.md`](./docs/architecture.md) | 内部设计(给扩展者) |
109
107
  | [`docs/ethos.md`](./docs/ethos.md) | 设计决策的理由 |
@@ -132,7 +130,7 @@ CurDX-Flow 是蒸馏,不是原创。深深致谢:
132
130
  - [**pua**](https://github.com/tanweai/pua) — 持续性 + 三条红线
133
131
  - [**claude-mem**](https://github.com/thedotmack/claude-mem) — 自动跨会话记忆
134
132
  - [**frontend-design skill**](https://claude.ai/skills/frontend-design) — distinctive UI(Anthropic 官方)
135
- - **context7**(Upstash)+ **sequential-thinking**(Anthropic)+ **chrome-devtools-mcp**(Chrome 团队)
133
+ - **Context7**(Upstash)+ **sequential-thinking**(Anthropic)+ **chrome-devtools-mcp**(Chrome 团队)
136
134
 
137
135
  ## 许可
138
136
 
@@ -140,7 +138,7 @@ MIT。见 [`LICENSE`](./LICENSE)。
140
138
 
141
139
  ## 贡献
142
140
 
143
- - Issues: https://github.com/wdx/curdx-flow/issues
141
+ - Issues: https://github.com/curdx/curdx-flow/issues
144
142
  - 欢迎 PR,但请先用 `/curdx-flow:spec` 自己走一遍(吃自己的狗粮)
145
143
  - 社区准则:友善 + 具体 + 不对着 LLM 发火
146
144
 
package/cli/README.md CHANGED
@@ -24,9 +24,11 @@ npx @curdx/flow upgrade
24
24
 
25
25
  Steps:
26
26
  1. Verify the `claude` CLI is installed
27
- 2. `claude plugin marketplace add curdx/curdx-flow`
28
- 3. `claude plugin install curdx-flow@curdx-flow-marketplace` (auto-installs 3 MCPs)
29
- 4. Interactively (or automatically) install recommended plugins: **pua**, **claude-mem**, **frontend-design**
27
+ 2. `claude plugin marketplace add --scope user curdx/curdx-flow`
28
+ 3. `claude plugin install --scope user curdx-flow@curdx-flow-marketplace`
29
+ 4. Install required companion plugin: **context7-plugin@context7-marketplace**
30
+ 5. Register required user-level MCP: **sequential-thinking**
31
+ 6. Interactively (or automatically) install recommended plugins: **pua**, **claude-mem**, **frontend-design**, **chrome-devtools-mcp**
30
32
 
31
33
  | Flag | Purpose |
32
34
  |------|---------|
@@ -35,7 +37,7 @@ Steps:
35
37
 
36
38
  ### `doctor [--verbose]`
37
39
 
38
- External diagnostics: claude CLI / curdx-flow / 3 MCPs / 3 recommended plugins / current directory `.flow/` state.
40
+ External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plugins / current directory `.flow/` state.
39
41
 
40
42
  ### Project initialization (not a CLI command)
41
43
 
@@ -50,11 +52,11 @@ This keeps the CLI scoped to install-time and lifecycle operations only — anyt
50
52
 
51
53
  ### `upgrade`
52
54
 
53
- `claude plugin marketplace update` + `claude plugin update` for every installed curdx-flow-related plugin.
55
+ `claude plugin marketplace update` + `claude plugin update --scope user` for every installed curdx-flow-related plugin.
54
56
 
55
57
  ### `uninstall [-y] [--keep-recommended] [--purge]`
56
58
 
57
- Inverse of `install`. By default removes only the curdx-flow plugin. With `--purge`, also removes the `~/.local/bin/bun` and `~/.local/bin/uv` symlinks created by install.
59
+ Inverse of `install`. By default removes only the curdx-flow plugin. Recommended plugins are kept unless selected interactively. With `--purge`, also removes third-party marketplaces and the `~/.local/bin/bun` / `~/.local/bin/uv` symlinks created by install.
58
60
 
59
61
  ## Why a CLI?
60
62
 
package/cli/doctor.js CHANGED
@@ -10,12 +10,13 @@ import {
10
10
  runSync,
11
11
  claudeVersion,
12
12
  listPlugins,
13
+ listPluginMarketplaces,
13
14
  listMcps,
14
15
  ensureClaudeMemRuntimes,
15
16
  readUserMcpConfig,
16
17
  findDuplicateMcps,
17
18
  } from "./utils.js";
18
- import { RECOMMENDED_PLUGINS } from "./registry.js";
19
+ import { BUNDLED_MCPS, REQUIRED_PLUGINS, RECOMMENDED_PLUGINS } from "./registry.js";
19
20
 
20
21
  export async function doctor(args = []) {
21
22
  const verbose = args.includes("--verbose") || args.includes("-v");
@@ -52,15 +53,37 @@ export async function doctor(args = []) {
52
53
  warnings++;
53
54
  }
54
55
 
56
+ const marketplaces = cv ? listPluginMarketplaces() : [];
57
+ const marketplaceNames = new Set(marketplaces.map((m) => m.name));
58
+
59
+ // ---------- Required plugins ----------
60
+ console.log(`\n${color.bold("Required plugins:")}`);
61
+ for (const r of REQUIRED_PLUGINS) {
62
+ const p = plugins.find((x) => x.id === r.id || x.name === r.name);
63
+ if (r.marketplaceSource && !marketplaceNames.has(r.marketplaceId)) {
64
+ log.warn(
65
+ `${r.marketplaceId.padEnd(22)} marketplace missing ${color.dim(`(run: claude plugin marketplace add --scope ${r.scope} ${r.marketplaceSource})`)}`
66
+ );
67
+ warnings++;
68
+ }
69
+ if (p && p.status === "enabled") {
70
+ log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version || "unknown"}`)}`);
71
+ } else if (p && p.status === "failed") {
72
+ log.err(`${r.name.padEnd(22)} load failed`);
73
+ errors++;
74
+ } else {
75
+ log.warn(
76
+ `${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install --scope ${r.scope} ${r.installSpec})`)}`
77
+ );
78
+ warnings++;
79
+ }
80
+ }
81
+
55
82
  // ---------- MCPs ----------
56
- // Bundled by curdx-flow plugin via .claude-plugin/plugin.json mcpServers.
57
- // chrome-devtools is NOT here anymore — it was extracted into its own
58
- // recommended plugin (see below) to align with the "each MCP owned by one
59
- // plugin" model and avoid double-spawning the chrome-devtools-mcp process.
60
83
  console.log(`\n${color.bold("MCP Servers (required by L2 mandatory tools):")}`);
61
84
  const mcps = cv ? listMcps() : [];
62
- const expectedMcps = ["context7", "sequential-thinking"];
63
- for (const m of expectedMcps) {
85
+ for (const expected of BUNDLED_MCPS) {
86
+ const m = expected.name;
64
87
  // Beta.12 onward: the required MCPs are registered at user-level
65
88
  // (via `claude mcp add`), NOT plugin-bundled. Both still show up in
66
89
  // `claude mcp list`. Accept a standalone user-level registration
@@ -80,7 +103,7 @@ export async function doctor(args = []) {
80
103
  } else {
81
104
  if (curdx) {
82
105
  log.warn(
83
- `${m.padEnd(22)} missing. Run: ${color.cyan(`claude mcp add ${m} -- npx -y @upstash/context7-mcp@latest`).replace("context7-mcp", m === "context7" ? "context7-mcp" : "server-sequential-thinking")}`
106
+ `${m.padEnd(22)} missing. Run: ${color.cyan(`claude mcp add --scope user ${m} -- ${expected.command} ${expected.args.join(" ")}`)}`
84
107
  );
85
108
  warnings++;
86
109
  } else {
@@ -93,7 +116,13 @@ export async function doctor(args = []) {
93
116
  console.log(`\n${color.bold("Recommended plugins:")}`);
94
117
  let claudeMemEnabled = false;
95
118
  for (const r of RECOMMENDED_PLUGINS) {
96
- const p = plugins.find((x) => x.name === r.name);
119
+ const p = plugins.find((x) => x.id === r.id || x.name === r.name);
120
+ if (r.marketplaceSource && !marketplaceNames.has(r.marketplaceId)) {
121
+ log.warn(
122
+ `${r.marketplaceId.padEnd(22)} marketplace missing ${color.dim(`(run: claude plugin marketplace add --scope ${r.scope} ${r.marketplaceSource})`)}`
123
+ );
124
+ warnings++;
125
+ }
97
126
  if (p && p.status === "enabled") {
98
127
  log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version}`)}`);
99
128
  if (r.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
@@ -102,7 +131,7 @@ export async function doctor(args = []) {
102
131
  errors++;
103
132
  } else {
104
133
  log.warn(
105
- `${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install ${r.installSpec})`)}`
134
+ `${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install --scope ${r.scope} ${r.installSpec})`)}`
106
135
  );
107
136
  warnings++;
108
137
  }
package/cli/install.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  ensureClaudeMemRuntimes,
18
18
  } from "./utils.js";
19
19
  import { injectGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
20
- import { RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
20
+ import { REQUIRED_PLUGINS, RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
21
21
  import { readUserMcpConfig } from "./utils.js";
22
22
 
23
23
  // When installed via npm, this CLI file lives at <pkg-root>/cli/install.js.
@@ -86,7 +86,7 @@ export async function install(args = []) {
86
86
 
87
87
  const addRes = await run(
88
88
  "claude",
89
- ["plugin", "marketplace", "add", marketplaceSource],
89
+ ["plugin", "marketplace", "add", "--scope", "user", marketplaceSource],
90
90
  { silent: true }
91
91
  );
92
92
  if (addRes.code !== 0 && !addRes.stderr.includes("already")) {
@@ -121,12 +121,23 @@ export async function install(args = []) {
121
121
  const already = prevCurdxFlow;
122
122
 
123
123
  if (already && shippedVersion && already.version === shippedVersion) {
124
- // Already on the exact version the tarball ships no re-install needed.
125
- // `claude plugin install` would succeed too but the fresh-install output
126
- // is misleading here.
127
- log.ok(
128
- `curdx-flow already at v${already.version} ${color.dim("(no change)")}`
124
+ // Step 2 removes and re-adds the marketplace to rebind it to the current
125
+ // source. Claude Code removes plugins installed from that marketplace as
126
+ // part of `marketplace remove`, so even a same-version install must be
127
+ // re-registered here.
128
+ log.info(
129
+ `curdx-flow already at v${already.version}, re-registering...`
129
130
  );
131
+ const r = await run(
132
+ "claude",
133
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
134
+ { silent: true }
135
+ );
136
+ if (r.code !== 0) {
137
+ log.err(`Install failed: ${r.stderr.trim() || r.stdout.trim()}`);
138
+ process.exit(1);
139
+ }
140
+ log.ok(`curdx-flow re-registered at v${shippedVersion}`);
130
141
  } else if (already && shippedVersion) {
131
142
  // Existing install, different version — frame as an upgrade.
132
143
  log.info(
@@ -134,7 +145,7 @@ export async function install(args = []) {
134
145
  );
135
146
  const r = await run(
136
147
  "claude",
137
- ["plugin", "install", "curdx-flow@curdx-flow-marketplace"],
148
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
138
149
  { silent: true }
139
150
  );
140
151
  if (r.code !== 0) {
@@ -151,7 +162,7 @@ export async function install(args = []) {
151
162
  );
152
163
  const r = await run(
153
164
  "claude",
154
- ["plugin", "install", "curdx-flow@curdx-flow-marketplace"],
165
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
155
166
  { silent: true }
156
167
  );
157
168
  if (r.code !== 0) {
@@ -162,7 +173,7 @@ export async function install(args = []) {
162
173
  } else {
163
174
  const r = await run(
164
175
  "claude",
165
- ["plugin", "install", "curdx-flow@curdx-flow-marketplace"],
176
+ ["plugin", "install", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
166
177
  { silent: true }
167
178
  );
168
179
  if (r.code !== 0) {
@@ -176,10 +187,46 @@ export async function install(args = []) {
176
187
  }
177
188
  }
178
189
 
179
- // ---------- Step 3.5: Register user-level MCPs (context7, sequential-thinking) ----------
180
- // Beta.12: migrated from plugin.json bundling. See cli/registry.js for
181
- // the rationale (avoids plugin:curdx-flow:context7 vs context7 duplicate
182
- // registration + keeps tool names standard for third-party agent reuse).
190
+ // ---------- Step 3.5: Install required plugins + register user-level MCPs ----------
191
+ log.blank();
192
+ log.info("Installing required Claude Code plugins...");
193
+ for (const plugin of REQUIRED_PLUGINS) {
194
+ console.log(` ${color.cyan("▸")} Installing ${color.bold(plugin.name)}...`);
195
+ const ma = await run(
196
+ "claude",
197
+ ["plugin", "marketplace", "add", "--scope", plugin.scope, plugin.marketplaceSource],
198
+ { silent: true }
199
+ );
200
+ if (ma.code !== 0 && !ma.stderr.includes("already")) {
201
+ log.warn(` marketplace add warning: ${ma.stderr.trim().split("\n")[0]}`);
202
+ }
203
+
204
+ const ir = await run(
205
+ "claude",
206
+ ["plugin", "install", "--scope", plugin.scope, plugin.installSpec],
207
+ { silent: true }
208
+ );
209
+ if (ir.code === 0) {
210
+ console.log(` ${color.green("✓")} ${plugin.name} installed`);
211
+ } else {
212
+ console.log(
213
+ ` ${color.red("✗")} ${plugin.name} install failed: ${ir.stderr.trim().split("\n").pop()}`
214
+ );
215
+ console.log(
216
+ color.dim(
217
+ ` Run manually: claude plugin marketplace add --scope ${plugin.scope} ${plugin.marketplaceSource}`
218
+ )
219
+ );
220
+ console.log(
221
+ color.dim(
222
+ ` Then: claude plugin install --scope ${plugin.scope} ${plugin.installSpec}`
223
+ )
224
+ );
225
+ }
226
+ }
227
+
228
+ // Beta.12: direct MCPs migrated from plugin.json bundling. See cli/registry.js
229
+ // for the rationale. Context7 now uses Upstash's official plugin instead.
183
230
  log.blank();
184
231
  log.info("Registering required MCP servers (user-level)...");
185
232
  const existingUserMcps = readUserMcpConfig();
@@ -193,7 +240,7 @@ export async function install(args = []) {
193
240
  }
194
241
  const r = await run(
195
242
  "claude",
196
- ["mcp", "add", mcp.name, "--", mcp.command, ...mcp.args],
243
+ ["mcp", "add", "--scope", "user", mcp.name, "--", mcp.command, ...mcp.args],
197
244
  { silent: true }
198
245
  );
199
246
  if (r.code === 0) {
@@ -205,7 +252,7 @@ export async function install(args = []) {
205
252
  ` ${mcp.name.padEnd(22)} registration failed: ${r.stderr.trim().split("\n").pop()}`
206
253
  );
207
254
  log.info(
208
- ` Run manually: claude mcp add ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
255
+ ` Run manually: claude mcp add --scope user ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
209
256
  );
210
257
  }
211
258
  }
@@ -251,10 +298,10 @@ export async function install(args = []) {
251
298
  console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
252
299
 
253
300
  // 1. Add marketplace (if needed)
254
- if (rec.marketplace) {
301
+ if (rec.marketplaceSource) {
255
302
  const ma = await run(
256
303
  "claude",
257
- ["plugin", "marketplace", "add", rec.marketplace],
304
+ ["plugin", "marketplace", "add", "--scope", rec.scope, rec.marketplaceSource],
258
305
  { silent: true }
259
306
  );
260
307
  if (ma.code !== 0 && !ma.stderr.includes("already")) {
@@ -264,7 +311,7 @@ export async function install(args = []) {
264
311
  }
265
312
 
266
313
  // 2. Install
267
- const ir = await run("claude", ["plugin", "install", rec.installSpec], {
314
+ const ir = await run("claude", ["plugin", "install", "--scope", rec.scope, rec.installSpec], {
268
315
  silent: true,
269
316
  });
270
317
  if (ir.code === 0) {
@@ -298,7 +345,7 @@ export async function install(args = []) {
298
345
  );
299
346
  console.log(
300
347
  color.dim(
301
- ` Run manually: claude plugin install ${rec.installSpec}`
348
+ ` Run manually: claude plugin install --scope ${rec.scope} ${rec.installSpec}`
302
349
  )
303
350
  );
304
351
  }
package/cli/registry.js CHANGED
@@ -9,74 +9,89 @@
9
9
  * or removing a plugin is a one-file change.
10
10
  *
11
11
  * Every consumer pulls what it needs via property access:
12
- * - install.js → marketplace + installSpec + hint (+ optional postInstall)
13
- * - uninstall.js → uninstallSpec
14
- * - upgrade.js → installSpec (for `claude plugin update`) + marketplaceId
15
- * - doctor.js → name + installSpec (for manual recovery hints)
12
+ * - install.js → marketplaceSource + installSpec + hint (+ optional postInstall)
13
+ * - uninstall.js → uninstallSpec + uninstallArgs + marketplaceId
14
+ * - upgrade.js → updateSpec + marketplaceId
15
+ * - doctor.js → id + installSpec (for health checks and recovery hints)
16
16
  */
17
17
 
18
18
  export const RECOMMENDED_PLUGINS = [
19
19
  {
20
20
  name: "pua",
21
- marketplace: "tanweai/pua",
22
- marketplaceId: "pua",
21
+ id: "pua@pua-skills",
22
+ marketplaceSource: "tanweai/pua",
23
+ marketplaceId: "pua-skills",
23
24
  installSpec: "pua@pua-skills",
24
25
  uninstallSpec: "pua@pua-skills",
26
+ updateSpec: "pua@pua-skills",
27
+ scope: "user",
25
28
  hint: "no-give-up + three red lines",
26
29
  },
27
30
  {
28
31
  name: "claude-mem",
29
- marketplace: "thedotmack/claude-mem",
32
+ id: "claude-mem@thedotmack",
33
+ marketplaceSource: "thedotmack/claude-mem",
30
34
  marketplaceId: "thedotmack",
31
35
  installSpec: "claude-mem@thedotmack",
32
36
  uninstallSpec: "claude-mem@thedotmack",
37
+ updateSpec: "claude-mem@thedotmack",
38
+ uninstallArgs: ["--keep-data"],
39
+ scope: "user",
33
40
  hint: "automatic cross-session memory",
34
41
  postInstall: "claude-mem-runtimes",
35
42
  },
36
43
  {
37
44
  name: "frontend-design",
38
45
  // Already in default marketplace claude-plugins-official, no add needed
39
- marketplace: null,
46
+ id: "frontend-design@claude-plugins-official",
47
+ marketplaceSource: null,
40
48
  marketplaceId: "claude-plugins-official",
41
49
  installSpec: "frontend-design@claude-plugins-official",
42
50
  uninstallSpec: "frontend-design@claude-plugins-official",
51
+ updateSpec: "frontend-design@claude-plugins-official",
52
+ scope: "user",
43
53
  hint: "Anthropic official UI skill",
44
54
  },
45
55
  {
46
56
  name: "chrome-devtools-mcp",
47
- marketplace: "ChromeDevTools/chrome-devtools-mcp",
57
+ id: "chrome-devtools-mcp@chrome-devtools-plugins",
58
+ marketplaceSource: "ChromeDevTools/chrome-devtools-mcp",
48
59
  marketplaceId: "chrome-devtools-plugins",
49
60
  installSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
50
61
  uninstallSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
62
+ updateSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
63
+ scope: "user",
51
64
  hint: "Chrome DevTools + Puppeteer (Google official)",
52
65
  },
53
66
  ];
54
67
 
68
+ export const REQUIRED_PLUGINS = [
69
+ {
70
+ name: "context7-plugin",
71
+ id: "context7-plugin@context7-marketplace",
72
+ marketplaceSource: "upstash/context7",
73
+ marketplaceId: "context7-marketplace",
74
+ installSpec: "context7-plugin@context7-marketplace",
75
+ uninstallSpec: "context7-plugin@context7-marketplace",
76
+ updateSpec: "context7-plugin@context7-marketplace",
77
+ scope: "user",
78
+ hint: "official Context7 plugin (MCP + skill + docs-researcher agent + /context7:docs)",
79
+ },
80
+ ];
81
+
55
82
  /**
56
- * Bundled MCP servers that curdx-flow depends on for its core discipline
57
- * rules (context7 for library docs in L2, sequential-thinking for
58
- * structured reasoning in L2). Starting beta.12 these are registered at
83
+ * MCP servers that curdx-flow depends on for its core discipline rules and
84
+ * still registers directly. Starting beta.12 these are registered at
59
85
  * USER-LEVEL via `claude mcp add` instead of plugin.json bundling, so:
60
86
  *
61
- * - Tool names stay standard (mcp__context7__*, mcp__sequential-thinking__*)
87
+ * - Tool names stay standard (mcp__sequential-thinking__*)
62
88
  * — matching every agent's and knowledge doc's hardcoded references.
63
- * - Users with a paid context7 API key can set it with --api-key in their
64
- * own user-level entry; the install command will detect and respect the
65
- * existing entry instead of overwriting it.
66
- * - No more `plugin:curdx-flow:context7` + `context7` duplicate registration
67
- * — the DX pitfall that bit every early adopter with a pre-existing
68
- * `claude mcp add context7 …` history.
89
+ *
90
+ * Context7 is installed via Upstash's official Claude Code plugin instead
91
+ * of direct `claude mcp add`: context7-plugin@context7-marketplace includes
92
+ * the MCP server, skill, docs-researcher agent, and /context7:docs command.
69
93
  */
70
94
  export const BUNDLED_MCPS = [
71
- {
72
- name: "context7",
73
- command: "npx",
74
- args: ["-y", "@upstash/context7-mcp@latest"],
75
- purpose: "library / framework docs lookup (L2 Mandatory Tool)",
76
- // Respect any user-level entry with a custom `--api-key` or differing
77
- // package spec — don't overwrite it on subsequent install runs.
78
- preserveExisting: true,
79
- },
80
95
  {
81
96
  name: "sequential-thinking",
82
97
  command: "npx",
@@ -92,9 +107,8 @@ export const BUNDLED_MCPS = [
92
107
  */
93
108
  export const MARKETPLACES_TO_REFRESH = [
94
109
  "curdx-flow-marketplace",
95
- ...RECOMMENDED_PLUGINS
96
- .filter((p) => p.marketplaceId && p.marketplaceId !== "claude-plugins-official")
97
- .map((p) => p.marketplaceId),
110
+ ...REQUIRED_PLUGINS.map((p) => p.marketplaceId),
111
+ ...RECOMMENDED_PLUGINS.map((p) => p.marketplaceId),
98
112
  ];
99
113
 
100
114
  /**
@@ -103,5 +117,6 @@ export const MARKETPLACES_TO_REFRESH = [
103
117
  */
104
118
  export const PLUGINS_TO_UPDATE = [
105
119
  "curdx-flow@curdx-flow-marketplace",
106
- ...RECOMMENDED_PLUGINS.map((p) => p.installSpec),
120
+ ...REQUIRED_PLUGINS.map((p) => p.updateSpec),
121
+ ...RECOMMENDED_PLUGINS.map((p) => p.updateSpec),
107
122
  ];
package/cli/uninstall.js CHANGED
@@ -16,14 +16,24 @@ import {
16
16
  listPlugins,
17
17
  } from "./utils.js";
18
18
  import { removeGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
19
- import { RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
19
+ import { REQUIRED_PLUGINS, RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
20
20
 
21
21
  const HOME = homedir();
22
22
 
23
23
  // Pull uninstall-relevant subset from the single registry. See registry.js.
24
- const RECOMMENDED = RECOMMENDED_PLUGINS.map(({ name, uninstallSpec }) => ({
24
+ const RECOMMENDED = RECOMMENDED_PLUGINS.map(({ name, uninstallSpec, uninstallArgs, marketplaceId, scope }) => ({
25
25
  name,
26
26
  uninstallSpec,
27
+ uninstallArgs: uninstallArgs || [],
28
+ marketplaceId,
29
+ scope,
30
+ }));
31
+ const REQUIRED = REQUIRED_PLUGINS.map(({ name, uninstallSpec, uninstallArgs, marketplaceId, scope }) => ({
32
+ name,
33
+ uninstallSpec,
34
+ uninstallArgs: uninstallArgs || [],
35
+ marketplaceId,
36
+ scope,
27
37
  }));
28
38
 
29
39
  // Symlinks created by install.js (only cleaned with --purge)
@@ -68,7 +78,7 @@ export async function uninstall(args = []) {
68
78
  } else {
69
79
  const r = await run(
70
80
  "claude",
71
- ["plugin", "uninstall", "curdx-flow@curdx-flow-marketplace"],
81
+ ["plugin", "uninstall", "--scope", "user", "curdx-flow@curdx-flow-marketplace"],
72
82
  { silent: true }
73
83
  );
74
84
  if (r.code === 0) {
@@ -117,7 +127,7 @@ export async function uninstall(args = []) {
117
127
  console.log(` ${color.cyan("▸")} Uninstalling ${color.bold(rec.name)}...`);
118
128
  const r = await run(
119
129
  "claude",
120
- ["plugin", "uninstall", rec.uninstallSpec],
130
+ ["plugin", "uninstall", "--scope", rec.scope, ...rec.uninstallArgs, rec.uninstallSpec],
121
131
  { silent: true }
122
132
  );
123
133
  if (r.code === 0) {
@@ -163,9 +173,39 @@ export async function uninstall(args = []) {
163
173
  }
164
174
  }
165
175
 
176
+ // ---------- Step 4.75: uninstall required companion plugins ----------
177
+ log.blank();
178
+ log.info("Required companion plugins");
179
+ if (yes) {
180
+ log.info(
181
+ color.dim("--yes mode: keeping required companion plugins (use --purge to remove them)")
182
+ );
183
+ } else {
184
+ const removeRequired = await confirm(
185
+ `Remove required companion plugins (${REQUIRED.map((p) => p.name).join(", ")})? ${color.dim("(keeps shared tools available if other workflows depend on them)")}`,
186
+ false
187
+ );
188
+ if (removeRequired) {
189
+ for (const plugin of REQUIRED) {
190
+ const r = await run(
191
+ "claude",
192
+ ["plugin", "uninstall", "--scope", plugin.scope, ...plugin.uninstallArgs, plugin.uninstallSpec],
193
+ { silent: true }
194
+ );
195
+ if (r.code === 0) {
196
+ log.ok(` ${plugin.name.padEnd(22)} uninstalled`);
197
+ } else {
198
+ log.info(` ${plugin.name.padEnd(22)} ${color.dim("not present or already removed")}`);
199
+ }
200
+ }
201
+ } else {
202
+ log.info("Keeping required companion plugins");
203
+ }
204
+ }
205
+
166
206
  // ---------- Step 5: cleanup symlinks (only with --purge) ----------
167
207
  log.blank();
168
- log.step(3, 4, "Runtime symlinks");
208
+ log.step(3, 4, "Runtime symlinks and marketplaces");
169
209
  if (!purge) {
170
210
  log.info(
171
211
  color.dim("Keeping ~/.local/bin/bun, ~/.local/bin/uv (use --purge to remove)")
@@ -174,6 +214,27 @@ export async function uninstall(args = []) {
174
214
  color.dim("Reason: these bun/uv binaries may be used by other tools — confirm before deleting")
175
215
  );
176
216
  } else {
217
+ const marketplaceIds = [
218
+ ...new Set(
219
+ RECOMMENDED
220
+ .concat(REQUIRED)
221
+ .map((r) => r.marketplaceId)
222
+ .filter((id) => id && id !== "claude-plugins-official")
223
+ ),
224
+ ];
225
+ for (const marketplaceId of marketplaceIds) {
226
+ const r = await run(
227
+ "claude",
228
+ ["plugin", "marketplace", "remove", marketplaceId],
229
+ { silent: true }
230
+ );
231
+ if (r.code === 0) {
232
+ log.ok(`Removed marketplace ${marketplaceId}`);
233
+ } else if (!r.stderr.includes("not found")) {
234
+ log.warn(`Failed to remove marketplace ${marketplaceId}: ${r.stderr.trim().split("\n").pop()}`);
235
+ }
236
+ }
237
+
177
238
  for (const link of MANAGED_SYMLINKS) {
178
239
  if (!existsSync(link) && !isBrokenSymlink(link)) {
179
240
  continue;
package/cli/upgrade.js CHANGED
@@ -47,7 +47,7 @@ export async function upgrade(args = []) {
47
47
  continue;
48
48
  }
49
49
 
50
- const r = await run("claude", ["plugin", "update", spec], { silent: true });
50
+ const r = await run("claude", ["plugin", "update", "--scope", "user", spec], { silent: true });
51
51
  if (r.code === 0) {
52
52
  const updated = r.stdout.includes("updated from");
53
53
  if (updated) {
package/cli/utils.js CHANGED
@@ -172,7 +172,7 @@ export function claudeVersion() {
172
172
  * 2.1.117+). Falls back to parsing the human-readable stream-text output
173
173
  * for older CLI versions, but warns that parser is brittle.
174
174
  *
175
- * Returns array of { name, version, status }.
175
+ * Returns array of { id, name, marketplaceId, version, status, scope }.
176
176
  */
177
177
  export function listPlugins() {
178
178
  // Preferred: structured JSON output.
@@ -182,9 +182,12 @@ export function listPlugins() {
182
182
  const arr = JSON.parse(j.stdout);
183
183
  return arr.map((p) => ({
184
184
  // id has form "name@marketplace" — name is stable for dedup/lookup.
185
+ id: String(p.id || ""),
185
186
  name: String(p.id || "").split("@")[0],
187
+ marketplaceId: String(p.id || "").split("@")[1] || undefined,
186
188
  version: p.version,
187
189
  status: p.enabled === false ? "disabled" : "enabled",
190
+ scope: p.scope,
188
191
  raw: JSON.stringify(p),
189
192
  }));
190
193
  } catch {
@@ -203,18 +206,35 @@ export function listPlugins() {
203
206
  const blocks = res.stdout.split(/\n\s*❯\s*/).slice(1);
204
207
  for (const block of blocks) {
205
208
  const lines = block.split("\n");
206
- const name = lines[0].trim().split("@")[0];
209
+ const id = lines[0].trim();
210
+ const name = id.split("@")[0];
207
211
  const version = (block.match(/Version:\s*(\S+)/) || [])[1];
208
212
  const status = block.includes("✔")
209
213
  ? "enabled"
210
214
  : block.includes("✘")
211
215
  ? "failed"
212
216
  : "unknown";
213
- plugins.push({ name, version, status, raw: block });
217
+ plugins.push({ id, name, marketplaceId: id.split("@")[1], version, status, raw: block });
214
218
  }
215
219
  return plugins;
216
220
  }
217
221
 
222
+ /**
223
+ * List configured Claude Code plugin marketplaces.
224
+ * Returns array of { name, source, repo, path } when `--json` is supported.
225
+ */
226
+ export function listPluginMarketplaces() {
227
+ const j = runSync("claude", ["plugin", "marketplace", "list", "--json"]);
228
+ if (j.code === 0 && j.stdout.trim().startsWith("[")) {
229
+ try {
230
+ return JSON.parse(j.stdout);
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+ return [];
236
+ }
237
+
218
238
  /**
219
239
  * Read the user-level MCP registrations from ~/.claude.json. These are the
220
240
  * MCPs the user added manually via `claude mcp add …` — distinct from
package/hooks/hooks.json CHANGED
@@ -8,10 +8,9 @@
8
8
  "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.sh"
9
9
  }
10
10
  ]
11
- }
12
- ],
13
- "InstructionsLoaded": [
11
+ },
14
12
  {
13
+ "matcher": "startup|clear|compact",
15
14
  "hooks": [
16
15
  {
17
16
  "type": "command",
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env bash
2
- # CurDX-Flow InstructionsLoaded Hook
2
+ # CurDX-Flow SessionStart baseline injection
3
3
  # Injects the L1 baseline (Karpathy 4 principles + mandatory tool rules + 3 red lines)
4
- # into every session after CLAUDE.md is loaded.
4
+ # as additionalContext at every session boot (startup, /clear, post-compact).
5
5
  #
6
- # This is what makes the baseline "always on" — it doesn't rely on CLAUDE.md being
7
- # present in every project, and it survives context compaction.
6
+ # Wired under SessionStart rather than InstructionsLoaded because Claude Code's
7
+ # InstructionsLoaded event is observability-only its hook schema rejects
8
+ # hookSpecificOutput / additionalContext. SessionStart with matcher
9
+ # "startup|clear|compact" gives the same "baseline is always on, even after
10
+ # compaction" property while staying within the supported schema.
8
11
 
9
12
  set -u
10
13
 
@@ -46,7 +49,7 @@ CONTEXT='## CurDX-Flow Mind Baseline (L1 — always on)
46
49
  # Emit JSON with safe encoding
47
50
  if command -v python3 >/dev/null 2>&1; then
48
51
  ESCAPED="$(printf '%s' "$CONTEXT" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
49
- printf '{"hookSpecificOutput":{"hookEventName":"InstructionsLoaded","additionalContext":%s}}\n' "$ESCAPED"
52
+ printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' "$ESCAPED"
50
53
  fi
51
54
 
52
55
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.0.0-beta.15",
3
+ "version": "2.0.0-beta.17",
4
4
  "description": "CLI installer for CurDX-Flow — AI engineering workflow meta-framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {