@curdx/flow 1.1.11 → 2.0.0-beta.10
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/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +4 -11
- package/CHANGELOG.md +99 -0
- package/README.md +74 -102
- package/README.zh.md +2 -2
- package/agent-preamble/preamble.md +81 -11
- package/agents/flow-adversary.md +41 -56
- package/agents/flow-architect.md +24 -11
- package/agents/flow-debugger.md +2 -2
- package/agents/flow-edge-hunter.md +20 -6
- package/agents/flow-executor.md +3 -3
- package/agents/flow-planner.md +51 -48
- package/agents/flow-product-designer.md +15 -2
- package/agents/flow-qa-engineer.md +4 -4
- package/agents/flow-researcher.md +18 -3
- package/agents/flow-reviewer.md +5 -1
- package/agents/flow-security-auditor.md +2 -2
- package/agents/flow-triage-analyst.md +4 -4
- package/agents/flow-ui-researcher.md +7 -7
- package/agents/flow-ux-designer.md +3 -3
- package/agents/flow-verifier.md +47 -14
- package/bin/curdx-flow.js +13 -1
- package/cli/doctor.js +28 -13
- package/cli/install.js +62 -36
- package/cli/protocols.js +63 -10
- package/cli/registry.js +73 -0
- package/cli/uninstall.js +9 -11
- package/cli/upgrade.js +6 -10
- package/cli/utils.js +104 -56
- package/commands/debug.md +10 -10
- package/commands/fast.md +1 -1
- package/commands/help.md +109 -87
- package/commands/implement.md +7 -7
- package/commands/init.md +18 -7
- package/commands/review.md +114 -130
- package/commands/spec.md +131 -89
- package/commands/start.md +130 -153
- package/commands/verify.md +110 -92
- package/gates/adversarial-review-gate.md +20 -20
- package/gates/coverage-audit-gate.md +1 -1
- package/gates/devex-gate.md +5 -6
- package/gates/edge-case-gate.md +2 -2
- package/gates/security-gate.md +3 -3
- package/hooks/hooks.json +0 -11
- package/hooks/scripts/quick-mode-guard.sh +12 -9
- package/hooks/scripts/session-start.sh +2 -2
- package/hooks/scripts/stop-watcher.sh +25 -15
- package/knowledge/epic-decomposition.md +2 -2
- package/knowledge/execution-strategies.md +10 -9
- package/knowledge/planning-reviews.md +6 -6
- package/knowledge/spec-driven-development.md +11 -10
- package/knowledge/two-stage-review.md +6 -5
- package/knowledge/wave-execution.md +5 -5
- package/package.json +4 -2
- package/skills/brownfield-index/SKILL.md +62 -0
- package/skills/browser-qa/SKILL.md +50 -0
- package/skills/epic/SKILL.md +68 -0
- package/skills/security-audit/SKILL.md +50 -0
- package/skills/ui-sketch/SKILL.md +49 -0
- package/templates/config.json.tmpl +1 -1
- package/templates/design.md.tmpl +32 -112
- package/templates/requirements.md.tmpl +25 -43
- package/templates/research.md.tmpl +37 -68
- package/templates/tasks.md.tmpl +27 -84
- package/agents/persona-amelia.md +0 -128
- package/agents/persona-david.md +0 -141
- package/agents/persona-emma.md +0 -179
- package/agents/persona-john.md +0 -105
- package/agents/persona-mary.md +0 -95
- package/agents/persona-oliver.md +0 -136
- package/agents/persona-rachel.md +0 -126
- package/agents/persona-serena.md +0 -175
- package/agents/persona-winston.md +0 -117
- package/commands/audit.md +0 -170
- package/commands/autoplan.md +0 -184
- package/commands/design.md +0 -155
- package/commands/discuss.md +0 -162
- package/commands/doctor.md +0 -124
- package/commands/index.md +0 -261
- package/commands/install-deps.md +0 -128
- package/commands/party.md +0 -241
- package/commands/plan-ceo.md +0 -117
- package/commands/plan-design.md +0 -107
- package/commands/plan-dx.md +0 -104
- package/commands/plan-eng.md +0 -108
- package/commands/qa.md +0 -118
- package/commands/requirements.md +0 -146
- package/commands/research.md +0 -141
- package/commands/security.md +0 -109
- package/commands/sketch.md +0 -118
- package/commands/spike.md +0 -181
- package/commands/status.md +0 -139
- package/commands/switch.md +0 -95
- package/commands/tasks.md +0 -189
- package/commands/triage.md +0 -160
- package/hooks/scripts/fail-tracker.sh +0 -31
|
@@ -133,7 +133,7 @@ For this project (per CONTEXT.md):
|
|
|
133
133
|
- Palette: follow project primary #6366F1
|
|
134
134
|
- Font: keep Inter system
|
|
135
135
|
|
|
136
|
-
## Suggested Direction for
|
|
136
|
+
## Suggested Direction for flow-ux-designer
|
|
137
137
|
|
|
138
138
|
Generate 2-3 variants:
|
|
139
139
|
1. Variant A: pure Stripe style (most conservative)
|
|
@@ -170,28 +170,28 @@ mkdir -p "$REF_DIR"
|
|
|
170
170
|
/curdx-flow:ui-research "reference patterns for login form"
|
|
171
171
|
↓ outputs ui-research.md
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
the `ui-sketch` skill
|
|
174
174
|
↓ flow-ux-designer reads ui-research.md as input
|
|
175
175
|
↓ generates variants A/B/C based on research findings
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
Division of labor:
|
|
179
179
|
- **Me (UI Researcher)**: gather + classify, no design
|
|
180
|
-
- **
|
|
180
|
+
- **flow-ux-designer**: produces actual UI based on my research
|
|
181
181
|
|
|
182
182
|
---
|
|
183
183
|
|
|
184
184
|
## Forbidden
|
|
185
185
|
|
|
186
|
-
- ✗ Doing actual UI design (that's
|
|
186
|
+
- ✗ Doing actual UI design (that's flow-ux-designer's job)
|
|
187
187
|
- ✗ Listing references from memory (must WebSearch or scan the codebase)
|
|
188
|
-
- ✗ Providing only one reference
|
|
188
|
+
- ✗ Providing only one reference — aim for enough breadth across reference categories that the user has genuine alternatives to pick from
|
|
189
189
|
- ✗ Ignoring CONTEXT.md preferences
|
|
190
190
|
|
|
191
191
|
## Quality Self-Check
|
|
192
192
|
|
|
193
193
|
- [ ] Scanned codebase for existing patterns?
|
|
194
|
-
- [ ] WebSearch covered
|
|
194
|
+
- [ ] WebSearch covered enough reference categories that the user has genuine design alternatives?
|
|
195
195
|
- [ ] sequential-thinking used to classify references?
|
|
196
196
|
- [ ] Recommendation considers CONTEXT.md?
|
|
197
197
|
- [ ] Asset files saved?
|
|
@@ -219,7 +219,7 @@ Report: .flow/specs/<name>/ui-research.md
|
|
|
219
219
|
Assets: .flow/specs/<name>/ui-research/refs/
|
|
220
220
|
|
|
221
221
|
Next step:
|
|
222
|
-
|
|
222
|
+
the `ui-sketch` skill — generate concrete UI variants based on research
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
---
|
|
@@ -28,7 +28,7 @@ Output: HTML files under `.flow/specs/<name>/ui-sketch/` (multiple variants allo
|
|
|
28
28
|
**Fallback when skill is unavailable**:
|
|
29
29
|
- Switch to Tailwind CSS + shadcn/ui default style
|
|
30
30
|
- Clearly tell the user "frontend-design skill not installed, using generic styles"
|
|
31
|
-
- Suggest
|
|
31
|
+
- Suggest `npx @curdx/flow install --all` to install frontend-design
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -201,7 +201,7 @@ View:
|
|
|
201
201
|
|
|
202
202
|
Next:
|
|
203
203
|
- Pick a variant → tell me which one → I'll turn it into production components
|
|
204
|
-
- Or
|
|
204
|
+
- Or the `browser-qa` skill to verify interactions in-browser (chrome-devtools)
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
---
|
|
@@ -237,7 +237,7 @@ The sketch stage = HTML prototype. Convert to React/Vue/Svelte components only a
|
|
|
237
237
|
## Quality Self-Check
|
|
238
238
|
|
|
239
239
|
- [ ] Invoked the frontend-design skill (if available)?
|
|
240
|
-
- [ ]
|
|
240
|
+
- [ ] Enough variants for the user to pick meaningful alternatives (omit if the brief clearly calls for one direction only)?
|
|
241
241
|
- [ ] Each variant a single HTML file, zero dependencies?
|
|
242
242
|
- [ ] decisions.md explains rationale for choices?
|
|
243
243
|
- [ ] Considered CONTEXT.md user preferences?
|
package/agents/flow-verifier.md
CHANGED
|
@@ -85,33 +85,60 @@ for comp in design.components:
|
|
|
85
85
|
assertions.append(("Comp", comp.name, f"{comp.name} must exist"))
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
### Step 3:
|
|
88
|
+
### Step 3: Classify every AC — does it describe user-visible behavior?
|
|
89
|
+
|
|
90
|
+
**BEFORE searching for evidence, classify each AC as either UI-facing or code-only.**
|
|
91
|
+
|
|
92
|
+
An AC is **UI-facing** if any of these is true:
|
|
93
|
+
- Contains words: "user sees", "displays", "renders", "shown", "visible", "click", "type into", "press", "hover", "select"
|
|
94
|
+
- Names a UI element: "button", "input", "checkbox", "link", "list", "form", "label", "modal", "banner"
|
|
95
|
+
- Describes a user flow: "the user can do X", "after X the user sees Y"
|
|
96
|
+
- References a visual state: "strikethrough", "highlighted", "disabled", "focus ring"
|
|
97
|
+
|
|
98
|
+
An AC is **code-only** if it describes internal behavior:
|
|
99
|
+
- Schema shape, API response structure, data transformations
|
|
100
|
+
- Performance ("p95 < 50ms"), reliability, security properties
|
|
101
|
+
- Error-envelope shapes, database constraints
|
|
102
|
+
|
|
103
|
+
### Step 3a: Find evidence for code-only ACs
|
|
89
104
|
|
|
90
105
|
```python
|
|
91
|
-
for source, id, text in
|
|
106
|
+
for source, id, text in code_only_assertions:
|
|
92
107
|
evidence = []
|
|
93
|
-
|
|
94
|
-
# Evidence 1: code implementation
|
|
95
108
|
relevant_files = grep_codebase(extract_keywords(text))
|
|
96
109
|
if relevant_files:
|
|
97
110
|
evidence.append(("code", relevant_files))
|
|
98
|
-
|
|
99
|
-
# Evidence 2: tests
|
|
100
111
|
test_files = find_tests_mentioning(id)
|
|
101
112
|
if test_files:
|
|
102
113
|
evidence.append(("test", test_files))
|
|
103
|
-
|
|
104
|
-
# Evidence 3: commit references
|
|
105
114
|
commits = git_log_grep(id)
|
|
106
115
|
if commits:
|
|
107
116
|
evidence.append(("commit", commits))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
status = "verified" if evidence and all_evidence_strong(evidence) else ("partial" if evidence else "missing")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 3b: UI-facing ACs REQUIRE browser verification (hard rule)
|
|
121
|
+
|
|
122
|
+
Code inspection + unit tests are **insufficient** evidence for a UI-facing AC. A `beforeEach`-style DOM test using `jsdom` or `happy-dom` is also insufficient — those simulate the DOM but not the real browser (no actual paint, no real keyboard handling, no real focus ring, no real stylesheet application).
|
|
123
|
+
|
|
124
|
+
For every UI-facing AC:
|
|
125
|
+
|
|
114
126
|
```
|
|
127
|
+
1. Check chrome-devtools MCP availability (mcp__chrome-devtools__*).
|
|
128
|
+
2. If available:
|
|
129
|
+
- Start the app (dev server or served build) in the current repo.
|
|
130
|
+
- Drive the flow described in the AC: click / type / navigate.
|
|
131
|
+
- Capture screenshot + list_console_messages + list_network_requests.
|
|
132
|
+
- Compare observed behavior against the AC text.
|
|
133
|
+
- Verdict: verified | partial | failed, with the screenshot as evidence.
|
|
134
|
+
3. If chrome-devtools MCP is NOT available:
|
|
135
|
+
- Mark the AC as "unverified — browser MCP missing".
|
|
136
|
+
- Add a CRITICAL section in verification-report.md listing the UI-facing ACs that could not be verified.
|
|
137
|
+
- Do NOT silently pass the AC based on code reading.
|
|
138
|
+
- Do NOT accept "manual smoke" as sufficient evidence unless the user explicitly logged a D-NN decision in STATE.md waiving automated browser verification.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Manual-smoke evidence (comments in tasks.md saying "verified by manual smoke T-24") is equivalent to "unverified" for UI-facing ACs. Flag it. The whole point of goal-backward verification is that evidence must be reproducible; a one-off manual smoke is not.
|
|
115
142
|
|
|
116
143
|
### Step 4: Run Actual Tests (Decisive)
|
|
117
144
|
|
|
@@ -145,6 +172,12 @@ For each match, check:
|
|
|
145
172
|
|
|
146
173
|
### Step 6: Generate verification-report.md
|
|
147
174
|
|
|
175
|
+
**CRITICAL (see L8 of the preamble):** your FIRST action in this step must be a `Write` tool call with the **complete report content**. Do NOT paste the report as assistant text before writing — doing so doubles output tokens and causes truncation inside the `Write` call. After the write succeeds, respond with a ≤ 5-line summary only (path, verdict counts, next step). Do not re-paste the report.
|
|
176
|
+
|
|
177
|
+
If a single `Write` call would approach the sub-agent output-token budget (judge by section density, not line count), split into `verification-report.md` (short index + verdict) and `verification-details.md` (full findings table) — two `Write` calls. See preamble L8.
|
|
178
|
+
|
|
179
|
+
Required structure (use this as the content passed to `Write`, not as preview text):
|
|
180
|
+
|
|
148
181
|
```markdown
|
|
149
182
|
# Verification Report: <spec-name>
|
|
150
183
|
|
package/bin/curdx-flow.js
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* for the full command/workflow reference)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { pathToFileURL } from "node:url";
|
|
24
|
+
|
|
23
25
|
import { install } from "../cli/install.js";
|
|
24
26
|
import { doctor } from "../cli/doctor.js";
|
|
25
27
|
import { upgrade } from "../cli/upgrade.js";
|
|
@@ -128,4 +130,14 @@ async function main() {
|
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
main()
|
|
133
|
+
// Only execute main() when invoked directly (`node bin/curdx-flow.js ...`
|
|
134
|
+
// or via the npm bin shim). When the file is imported by tests or tooling,
|
|
135
|
+
// we want the module graph to load without side-effects. This idiom is
|
|
136
|
+
// the ESM equivalent of Python's `if __name__ == "__main__"`.
|
|
137
|
+
const invokedDirectly =
|
|
138
|
+
process.argv[1] &&
|
|
139
|
+
import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
140
|
+
|
|
141
|
+
if (invokedDirectly) {
|
|
142
|
+
main();
|
|
143
|
+
}
|
package/cli/doctor.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
listMcps,
|
|
14
14
|
ensureClaudeMemRuntimes,
|
|
15
15
|
} from "./utils.js";
|
|
16
|
+
import { RECOMMENDED_PLUGINS } from "./registry.js";
|
|
16
17
|
|
|
17
18
|
export async function doctor(args = []) {
|
|
18
19
|
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
@@ -50,13 +51,28 @@ export async function doctor(args = []) {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
// ---------- MCPs ----------
|
|
54
|
+
// Bundled by curdx-flow plugin via .claude-plugin/plugin.json mcpServers.
|
|
55
|
+
// chrome-devtools is NOT here anymore — it was extracted into its own
|
|
56
|
+
// recommended plugin (see below) to align with the "each MCP owned by one
|
|
57
|
+
// plugin" model and avoid double-spawning the chrome-devtools-mcp process.
|
|
53
58
|
console.log(`\n${color.bold("MCP Servers:")}`);
|
|
54
59
|
const mcps = cv ? listMcps() : [];
|
|
55
|
-
const expectedMcps = ["context7", "sequential-thinking"
|
|
60
|
+
const expectedMcps = ["context7", "sequential-thinking"];
|
|
56
61
|
for (const m of expectedMcps) {
|
|
57
|
-
|
|
62
|
+
// `claude mcp list` reports plugin-bundled MCPs as
|
|
63
|
+
// "plugin:curdx-flow:<name>" and standalone MCPs as "<name>". Accept
|
|
64
|
+
// either form — previously this was a bare .name === m check, so a
|
|
65
|
+
// plugin MCP named "plugin:curdx-flow:context7" would silently report
|
|
66
|
+
// as not-installed even though it was running (the same class of bug
|
|
67
|
+
// that bit chrome-devtools in beta.7).
|
|
68
|
+
const found = mcps.find(
|
|
69
|
+
(x) =>
|
|
70
|
+
x.name === m &&
|
|
71
|
+
(x.plugin === null || x.plugin === "curdx-flow")
|
|
72
|
+
);
|
|
58
73
|
if (found) {
|
|
59
|
-
|
|
74
|
+
const via = found.plugin ? `via plugin:${found.plugin}` : "standalone";
|
|
75
|
+
log.ok(`${m.padEnd(22)} ${color.dim(`auto-loaded (${via})`)}`);
|
|
60
76
|
} else {
|
|
61
77
|
if (curdx) {
|
|
62
78
|
log.warn(`${m.padEnd(22)} not shown in claude mcp list (restart Claude Code may fix)`);
|
|
@@ -67,24 +83,21 @@ export async function doctor(args = []) {
|
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
// ---------- Recommended plugins ----------
|
|
86
|
+
// ---------- Recommended plugins (single registry; see cli/registry.js) ----------
|
|
71
87
|
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
88
|
let claudeMemEnabled = false;
|
|
78
|
-
for (const r of
|
|
89
|
+
for (const r of RECOMMENDED_PLUGINS) {
|
|
79
90
|
const p = plugins.find((x) => x.name === r.name);
|
|
80
91
|
if (p && p.status === "enabled") {
|
|
81
92
|
log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version}`)}`);
|
|
82
|
-
if (r.
|
|
93
|
+
if (r.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
|
|
83
94
|
} else if (p && p.status === "failed") {
|
|
84
95
|
log.err(`${r.name.padEnd(22)} load failed`);
|
|
85
96
|
errors++;
|
|
86
97
|
} else {
|
|
87
|
-
log.warn(
|
|
98
|
+
log.warn(
|
|
99
|
+
`${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install ${r.installSpec})`)}`
|
|
100
|
+
);
|
|
88
101
|
warnings++;
|
|
89
102
|
}
|
|
90
103
|
}
|
|
@@ -147,7 +160,9 @@ export async function doctor(args = []) {
|
|
|
147
160
|
console.log(color.green("Summary: all healthy ✓"));
|
|
148
161
|
}
|
|
149
162
|
|
|
150
|
-
if (verbose) {
|
|
163
|
+
if (verbose && cv) {
|
|
164
|
+
// Only call claude when it is actually on PATH; otherwise we spawn a
|
|
165
|
+
// child that fails silently and print a blank block.
|
|
151
166
|
console.log(`\n${color.bold("Details:")}`);
|
|
152
167
|
console.log(color.dim(` Plugins raw:`));
|
|
153
168
|
console.log(runSync("claude", ["plugin", "list"]).stdout);
|
package/cli/install.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* install command — install curdx-flow plugin + optional recommended plugins.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
@@ -17,6 +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 } from "./registry.js";
|
|
20
21
|
|
|
21
22
|
// When installed via npm, this CLI file lives at <pkg-root>/cli/install.js.
|
|
22
23
|
// The npm package bundles the full plugin body (.claude-plugin/, agents/,
|
|
@@ -28,27 +29,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
28
29
|
const PKG_ROOT = dirname(__dirname);
|
|
29
30
|
const LOCAL_MARKETPLACE_MANIFEST = join(PKG_ROOT, ".claude-plugin", "marketplace.json");
|
|
30
31
|
|
|
31
|
-
// Recommended plugins
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
installSpec: "pua@pua-skills",
|
|
37
|
-
hint: "no-give-up + three red lines",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "claude-mem",
|
|
41
|
-
marketplace: "thedotmack/claude-mem",
|
|
42
|
-
installSpec: "claude-mem@thedotmack",
|
|
43
|
-
hint: "automatic cross-session memory",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "frontend-design",
|
|
47
|
-
marketplace: null, // already in default marketplace claude-plugins-official
|
|
48
|
-
installSpec: "frontend-design@claude-plugins-official",
|
|
49
|
-
hint: "Anthropic official UI skill",
|
|
50
|
-
},
|
|
51
|
-
];
|
|
32
|
+
// Recommended plugins: single source of truth is cli/registry.js.
|
|
33
|
+
// See registry.js for the rationale — this list used to drift across
|
|
34
|
+
// install/uninstall/upgrade/doctor, producing the chrome-devtools-mcp
|
|
35
|
+
// orphan-plugin bug (installable but uninstallable).
|
|
36
|
+
const RECOMMENDED = RECOMMENDED_PLUGINS;
|
|
52
37
|
|
|
53
38
|
export async function install(args = []) {
|
|
54
39
|
const all = args.includes("--all");
|
|
@@ -64,7 +49,7 @@ export async function install(args = []) {
|
|
|
64
49
|
log.title("🚀 CurDX-Flow Installer");
|
|
65
50
|
|
|
66
51
|
// ---------- Step 1: Check claude CLI ----------
|
|
67
|
-
log.step(1,
|
|
52
|
+
log.step(1, 5, "Checking claude CLI...");
|
|
68
53
|
const ver = claudeVersion();
|
|
69
54
|
if (!ver) {
|
|
70
55
|
log.err("claude CLI not found. Install Claude Code from https://code.claude.com first.");
|
|
@@ -78,7 +63,7 @@ export async function install(args = []) {
|
|
|
78
63
|
const marketplaceLabel = useOffline
|
|
79
64
|
? `local npm package (${PKG_ROOT})`
|
|
80
65
|
: "GitHub curdx/curdx-flow";
|
|
81
|
-
log.step(2,
|
|
66
|
+
log.step(2, 5, `Adding curdx-flow marketplace from ${marketplaceLabel}...`);
|
|
82
67
|
|
|
83
68
|
// Remove any existing marketplace with the same name so we get a clean
|
|
84
69
|
// rebind to the chosen source. Errors are non-fatal (marketplace may
|
|
@@ -105,10 +90,38 @@ export async function install(args = []) {
|
|
|
105
90
|
|
|
106
91
|
// ---------- Step 3: Install curdx-flow plugin ----------
|
|
107
92
|
log.blank();
|
|
108
|
-
log.step(3,
|
|
93
|
+
log.step(3, 5, "Installing curdx-flow plugin (2 MCPs will auto-start)...");
|
|
94
|
+
// Read the version the marketplace is shipping so we can decide whether an
|
|
95
|
+
// already-installed plugin needs an update (same name but stale version
|
|
96
|
+
// previously silently skipped the upgrade — caused the beta.1 → beta.7 drift).
|
|
97
|
+
let shippedVersion = null;
|
|
98
|
+
try {
|
|
99
|
+
const mf = JSON.parse(
|
|
100
|
+
readFileSync(LOCAL_MARKETPLACE_MANIFEST, "utf-8")
|
|
101
|
+
);
|
|
102
|
+
shippedVersion = mf?.metadata?.version || null;
|
|
103
|
+
} catch {
|
|
104
|
+
// marketplace not local (online install) or unreadable — fall through
|
|
105
|
+
}
|
|
106
|
+
|
|
109
107
|
const installed = listPlugins();
|
|
110
108
|
const already = installed.find((p) => p.name === "curdx-flow");
|
|
111
|
-
if (already) {
|
|
109
|
+
if (already && shippedVersion && already.version !== shippedVersion) {
|
|
110
|
+
log.info(
|
|
111
|
+
`curdx-flow installed at v${already.version}, marketplace ships v${shippedVersion} — updating...`
|
|
112
|
+
);
|
|
113
|
+
const r = await run(
|
|
114
|
+
"claude",
|
|
115
|
+
["plugin", "update", "curdx-flow@curdx-flow-marketplace"],
|
|
116
|
+
{ silent: true }
|
|
117
|
+
);
|
|
118
|
+
if (r.code !== 0) {
|
|
119
|
+
log.warn(`Update returned non-zero: ${r.stderr.trim() || r.stdout.trim()}`);
|
|
120
|
+
log.info(`If the version stays on v${already.version}, run: claude plugin uninstall curdx-flow@curdx-flow-marketplace && retry`);
|
|
121
|
+
} else {
|
|
122
|
+
log.ok(`curdx-flow updated to v${shippedVersion}`);
|
|
123
|
+
}
|
|
124
|
+
} else if (already) {
|
|
112
125
|
log.ok(`curdx-flow already installed (v${already.version}, ${already.status})`);
|
|
113
126
|
} else {
|
|
114
127
|
const r = await run(
|
|
@@ -125,7 +138,7 @@ export async function install(args = []) {
|
|
|
125
138
|
|
|
126
139
|
// ---------- Step 4: Recommended plugins ----------
|
|
127
140
|
log.blank();
|
|
128
|
-
log.step(4,
|
|
141
|
+
log.step(4, 5, "Recommended plugins");
|
|
129
142
|
|
|
130
143
|
if (noDeps) {
|
|
131
144
|
log.info("Skipping recommended plugins (--no-deps)");
|
|
@@ -161,7 +174,7 @@ export async function install(args = []) {
|
|
|
161
174
|
for (const pluginName of toInstall) {
|
|
162
175
|
const rec = RECOMMENDED.find((r) => r.name === pluginName);
|
|
163
176
|
log.blank();
|
|
164
|
-
console.log(` ${color.cyan("
|
|
177
|
+
console.log(` ${color.cyan("▸")} Installing ${color.bold(rec.name)}...`);
|
|
165
178
|
|
|
166
179
|
// 1. Add marketplace (if needed)
|
|
167
180
|
if (rec.marketplace) {
|
|
@@ -186,7 +199,7 @@ export async function install(args = []) {
|
|
|
186
199
|
// 3. Post-install hook for claude-mem: its .mcp.json hard-codes `bun`,
|
|
187
200
|
// but ~/.bun/bin is not on PATH when Claude Code spawns the MCP server.
|
|
188
201
|
// Auto-create a PATH-visible symlink to fix it.
|
|
189
|
-
if (rec.
|
|
202
|
+
if (rec.postInstall === "claude-mem-runtimes") {
|
|
190
203
|
const r = ensureClaudeMemRuntimes();
|
|
191
204
|
for (const [name, res] of Object.entries(r)) {
|
|
192
205
|
if (res.status === "linked") {
|
|
@@ -219,11 +232,13 @@ export async function install(args = []) {
|
|
|
219
232
|
|
|
220
233
|
// ---------- Step 5: inject global protocols ----------
|
|
221
234
|
log.blank();
|
|
222
|
-
|
|
235
|
+
log.step(5, 5, "Injecting global protocols into ~/.claude/CLAUDE.md...");
|
|
223
236
|
try {
|
|
224
237
|
const r = injectGlobalProtocols();
|
|
225
238
|
if (r.action === "created") {
|
|
226
239
|
log.ok(`Global protocols injected ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
240
|
+
} else if (r.action === "appended") {
|
|
241
|
+
log.ok(`Global protocols appended ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
227
242
|
} else if (r.action === "upgraded") {
|
|
228
243
|
log.ok(`Global protocols upgraded ${color.dim(`(${GLOBAL_CLAUDE_MD})`)}`);
|
|
229
244
|
} else {
|
|
@@ -237,15 +252,26 @@ export async function install(args = []) {
|
|
|
237
252
|
}
|
|
238
253
|
|
|
239
254
|
function printNextSteps() {
|
|
240
|
-
|
|
255
|
+
// Detect whether the CLI is globally installed (curdx-flow on PATH) or
|
|
256
|
+
// the user ran us via npx. Tell them the right invocation each time.
|
|
257
|
+
const cliOnPath = has("curdx-flow");
|
|
258
|
+
const cliCmd = cliOnPath ? "curdx-flow" : "npx @curdx/flow";
|
|
259
|
+
|
|
260
|
+
console.log(`\n${color.bold(`${color.green("✓")} Install complete`)}\n`);
|
|
261
|
+
console.log(`${color.bold("Restart Claude Code")} so the plugin registers all its commands and hooks.\n`);
|
|
241
262
|
console.log(`${color.bold("Next steps")}:\n`);
|
|
242
263
|
console.log(` ${color.dim("# Verify health")}`);
|
|
243
|
-
console.log(`
|
|
244
|
-
console.log(` ${color.dim("#
|
|
245
|
-
console.log(` cd ~/your-project
|
|
246
|
-
console.log(` ${color.dim("# Start using it (inside Claude Code)")}`);
|
|
264
|
+
console.log(` ${cliCmd} doctor\n`);
|
|
265
|
+
console.log(` ${color.dim("# Inside any project, initialize and start a feature spec")}`);
|
|
266
|
+
console.log(` ${color.cyan("cd ~/your-project")}`);
|
|
247
267
|
console.log(` ${color.cyan("claude")}`);
|
|
248
|
-
console.log(` ${color.cyan("/curdx-flow:
|
|
268
|
+
console.log(` ${color.cyan("/curdx-flow:init")}`);
|
|
269
|
+
console.log(` ${color.cyan("/curdx-flow:start my-feature \"<one-line goal>\"")}\n`);
|
|
270
|
+
if (!cliOnPath) {
|
|
271
|
+
console.log(
|
|
272
|
+
`${color.dim("Tip: install the CLI globally for shorter commands —")} ${color.cyan("npm i -g @curdx/flow")}\n`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
249
275
|
console.log(
|
|
250
276
|
`${color.bold("Learn more")}: https://github.com/curdx/curdx-flow/blob/main/docs/getting-started.md\n`
|
|
251
277
|
);
|
package/cli/protocols.js
CHANGED
|
@@ -5,10 +5,23 @@
|
|
|
5
5
|
* and reversible (uninstall removes it cleanly without touching user content).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
unlinkSync,
|
|
15
|
+
} from "node:fs";
|
|
9
16
|
import { join, dirname } from "node:path";
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
|
|
19
|
+
// Use os.homedir() instead of process.env.HOME — HOME can be empty inside
|
|
20
|
+
// non-login shells (CI containers, some spawned child envs), which would
|
|
21
|
+
// resolve GLOBAL_CLAUDE_MD to "/.claude/CLAUDE.md" (filesystem root) and
|
|
22
|
+
// cause mkdir/writeFileSync to fail with EACCES. homedir() falls back to
|
|
23
|
+
// the effective user's passwd entry on POSIX and USERPROFILE on Windows.
|
|
24
|
+
const HOME = homedir();
|
|
12
25
|
export const GLOBAL_CLAUDE_MD = join(HOME, ".claude", "CLAUDE.md");
|
|
13
26
|
|
|
14
27
|
const SENTINEL_BEGIN =
|
|
@@ -53,16 +66,56 @@ function readGlobalMd() {
|
|
|
53
66
|
|
|
54
67
|
/**
|
|
55
68
|
* Locate the sentinel block in the content.
|
|
56
|
-
* Returns { start, end } indices into content,
|
|
69
|
+
* Returns { start, end } indices into content, `null` if neither sentinel is
|
|
70
|
+
* present, or throws if the block is corrupted (begin without matching end).
|
|
71
|
+
* The throw is intentional — previously the corrupted case silently returned
|
|
72
|
+
* null, so the next run would append a SECOND block, producing drift.
|
|
57
73
|
*/
|
|
58
74
|
function findBlock(content) {
|
|
59
75
|
const start = content.indexOf(SENTINEL_BEGIN);
|
|
60
|
-
if (start === -1)
|
|
76
|
+
if (start === -1) {
|
|
77
|
+
// Also check for a dangling END without BEGIN — that is also corrupted.
|
|
78
|
+
if (content.indexOf(SENTINEL_END) !== -1) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: END sentinel found without BEGIN. ` +
|
|
81
|
+
`Manually inspect the file and remove the dangling END line, then re-run.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
61
86
|
const endIdx = content.indexOf(SENTINEL_END, start);
|
|
62
|
-
if (endIdx === -1)
|
|
87
|
+
if (endIdx === -1) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: BEGIN sentinel found without END. ` +
|
|
90
|
+
`Manually remove the orphan BEGIN line (or restore the END), then re-run.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
63
93
|
return { start, end: endIdx + SENTINEL_END.length };
|
|
64
94
|
}
|
|
65
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Write `content` to `path` atomically: write to a sibling temp file first,
|
|
98
|
+
* then rename. This prevents a half-written CLAUDE.md if the process is
|
|
99
|
+
* interrupted mid-write, and avoids races between concurrent install /
|
|
100
|
+
* uninstall invocations.
|
|
101
|
+
*/
|
|
102
|
+
function atomicWrite(path, content) {
|
|
103
|
+
const tmp = `${path}.curdx-flow.tmp.${process.pid}`;
|
|
104
|
+
try {
|
|
105
|
+
writeFileSync(tmp, content, "utf-8");
|
|
106
|
+
renameSync(tmp, path);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// Best-effort cleanup of the temp file; swallow errors here since we
|
|
109
|
+
// are already re-throwing the real failure.
|
|
110
|
+
try {
|
|
111
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
112
|
+
} catch {
|
|
113
|
+
// ignore
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
66
119
|
/**
|
|
67
120
|
* Inject (or upgrade) the protocol block in ~/.claude/CLAUDE.md.
|
|
68
121
|
* @returns {{action:"created"|"upgraded"|"unchanged", path:string}}
|
|
@@ -81,8 +134,8 @@ export function injectGlobalProtocols() {
|
|
|
81
134
|
? (existing.endsWith("\n") ? "\n" : "\n\n")
|
|
82
135
|
: "";
|
|
83
136
|
const next = existing + sep + FULL_BLOCK + "\n";
|
|
84
|
-
|
|
85
|
-
return { action: existing.length === 0 ? "created" : "
|
|
137
|
+
atomicWrite(path, next);
|
|
138
|
+
return { action: existing.length === 0 ? "created" : "appended", path };
|
|
86
139
|
}
|
|
87
140
|
|
|
88
141
|
// Replace existing block (handle upgrade-in-place)
|
|
@@ -92,7 +145,7 @@ export function injectGlobalProtocols() {
|
|
|
92
145
|
}
|
|
93
146
|
const next =
|
|
94
147
|
existing.slice(0, block.start) + FULL_BLOCK + existing.slice(block.end);
|
|
95
|
-
|
|
148
|
+
atomicWrite(path, next);
|
|
96
149
|
return { action: "upgraded", path };
|
|
97
150
|
}
|
|
98
151
|
|
|
@@ -119,6 +172,6 @@ export function removeGlobalProtocols() {
|
|
|
119
172
|
}
|
|
120
173
|
|
|
121
174
|
const next = existing.slice(0, start) + existing.slice(end);
|
|
122
|
-
|
|
175
|
+
atomicWrite(path, next);
|
|
123
176
|
return { action: "removed", path };
|
|
124
177
|
}
|
package/cli/registry.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for recommended companion plugins.
|
|
3
|
+
*
|
|
4
|
+
* Background: before this file existed, the list of recommended plugins lived
|
|
5
|
+
* in FOUR independent places (install.js, uninstall.js, upgrade.js,
|
|
6
|
+
* doctor.js). They drifted — chrome-devtools-mcp was added to install.js
|
|
7
|
+
* during the beta.8 MCP decoupling but forgotten in the other three,
|
|
8
|
+
* making it installable but uninstallable. This registry exists so adding
|
|
9
|
+
* or removing a plugin is a one-file change.
|
|
10
|
+
*
|
|
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)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const RECOMMENDED_PLUGINS = [
|
|
19
|
+
{
|
|
20
|
+
name: "pua",
|
|
21
|
+
marketplace: "tanweai/pua",
|
|
22
|
+
marketplaceId: "pua",
|
|
23
|
+
installSpec: "pua@pua-skills",
|
|
24
|
+
uninstallSpec: "pua@pua-skills",
|
|
25
|
+
hint: "no-give-up + three red lines",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "claude-mem",
|
|
29
|
+
marketplace: "thedotmack/claude-mem",
|
|
30
|
+
marketplaceId: "thedotmack",
|
|
31
|
+
installSpec: "claude-mem@thedotmack",
|
|
32
|
+
uninstallSpec: "claude-mem@thedotmack",
|
|
33
|
+
hint: "automatic cross-session memory",
|
|
34
|
+
postInstall: "claude-mem-runtimes",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "frontend-design",
|
|
38
|
+
// Already in default marketplace claude-plugins-official, no add needed
|
|
39
|
+
marketplace: null,
|
|
40
|
+
marketplaceId: "claude-plugins-official",
|
|
41
|
+
installSpec: "frontend-design@claude-plugins-official",
|
|
42
|
+
uninstallSpec: "frontend-design@claude-plugins-official",
|
|
43
|
+
hint: "Anthropic official UI skill",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "chrome-devtools-mcp",
|
|
47
|
+
marketplace: "ChromeDevTools/chrome-devtools-mcp",
|
|
48
|
+
marketplaceId: "chrome-devtools-plugins",
|
|
49
|
+
installSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
50
|
+
uninstallSpec: "chrome-devtools-mcp@chrome-devtools-plugins",
|
|
51
|
+
hint: "Chrome DevTools + Puppeteer (Google official)",
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Marketplaces to refresh during `upgrade`. Derived from RECOMMENDED_PLUGINS
|
|
57
|
+
* plus the curdx-flow marketplace itself.
|
|
58
|
+
*/
|
|
59
|
+
export const MARKETPLACES_TO_REFRESH = [
|
|
60
|
+
"curdx-flow-marketplace",
|
|
61
|
+
...RECOMMENDED_PLUGINS
|
|
62
|
+
.filter((p) => p.marketplaceId && p.marketplaceId !== "claude-plugins-official")
|
|
63
|
+
.map((p) => p.marketplaceId),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Plugin install specs to update during `upgrade` — includes curdx-flow
|
|
68
|
+
* itself plus every recommended plugin.
|
|
69
|
+
*/
|
|
70
|
+
export const PLUGINS_TO_UPDATE = [
|
|
71
|
+
"curdx-flow@curdx-flow-marketplace",
|
|
72
|
+
...RECOMMENDED_PLUGINS.map((p) => p.installSpec),
|
|
73
|
+
];
|