@curdx/flow 2.0.0-beta.7 → 2.0.0-beta.9
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 +1 -1
- package/.claude-plugin/plugin.json +1 -8
- package/CHANGELOG.md +20 -0
- package/README.zh.md +2 -2
- package/cli/doctor.js +12 -10
- package/cli/install.js +40 -25
- package/cli/protocols.js +55 -8
- package/cli/registry.js +73 -0
- package/cli/uninstall.js +6 -9
- package/cli/upgrade.js +6 -10
- package/cli/utils.js +2 -2
- package/commands/init.md +2 -2
- package/commands/spec.md +1 -1
- package/commands/start.md +13 -5
- package/hooks/hooks.json +0 -11
- package/hooks/scripts/quick-mode-guard.sh +12 -9
- package/hooks/scripts/session-start.sh +1 -1
- package/hooks/scripts/stop-watcher.sh +25 -15
- package/package.json +2 -1
- 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/hooks/scripts/fail-tracker.sh +0 -31
|
@@ -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.
|
|
9
|
+
"version": "2.0.0-beta.9"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "curdx-flow",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.9",
|
|
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",
|
|
@@ -31,13 +31,6 @@
|
|
|
31
31
|
"-y",
|
|
32
32
|
"@modelcontextprotocol/server-sequential-thinking"
|
|
33
33
|
]
|
|
34
|
-
},
|
|
35
|
-
"chrome-devtools": {
|
|
36
|
-
"command": "npx",
|
|
37
|
-
"args": [
|
|
38
|
-
"-y",
|
|
39
|
-
"chrome-devtools-mcp@latest"
|
|
40
|
-
]
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CurDX-Flow will be documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Fixed (P0)
|
|
8
|
+
|
|
9
|
+
- `cli/utils.js` — `findRuntime()` referenced `existsSync` without importing it; any user whose `bun`/`uv` was not on PATH hit `ReferenceError` during install or doctor.
|
|
10
|
+
- `hooks/scripts/stop-watcher.sh` — `export STATE_FILE` was placed after the python heredoc, so the stop-hook execution strategy never activated. Moved the export before the heredoc.
|
|
11
|
+
- `package.json` — `skills/` directory was missing from `files[]`; the 5 bundled skills were stripped from the published tarball.
|
|
12
|
+
- `cli/uninstall.js` + `cli/upgrade.js` — `chrome-devtools-mcp` (added as a recommended plugin in beta.8) was missing from uninstall/upgrade lists, making it installable but uninstallable.
|
|
13
|
+
- `cli/protocols.js` — `injectGlobalProtocols()` returned action `"created"` on both ternary branches, silently collapsing the append-to-existing-file case. Atomic write + corrupted-block detection added.
|
|
14
|
+
|
|
15
|
+
### Changed (structural)
|
|
16
|
+
|
|
17
|
+
- New `cli/registry.js` is the single source of truth for recommended plugins. `install.js`, `uninstall.js`, `upgrade.js`, and `doctor.js` all import from it.
|
|
18
|
+
- `commands/start.md` and `commands/spec.md` now produce `.state.json` files that match `schemas/spec-state.schema.json` (field names: `spec_name` / `created` / `updated` / `version`; initial phase is `research`, not the undefined `created`).
|
|
19
|
+
- All python heredocs inside hook scripts use quoted delimiters (`<<'PY'`) and read `STATE_FILE` via `os.environ`, closing a shell→python code-injection surface triggered by unusual spec names.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- `hooks/scripts/fail-tracker.sh` and its `PostToolUseFailure` registration — the counter was written but never read by any consumer (the intended pua escalation was never implemented). Can be reintroduced when the consumer exists.
|
|
24
|
+
|
|
5
25
|
## [2.0.0-beta.1] - 2026-04-20
|
|
6
26
|
|
|
7
27
|
### BREAKING — Major redesign: Discipline Layer, not meta-framework
|
package/README.zh.md
CHANGED
|
@@ -23,8 +23,8 @@ CurDX-Flow 是一个 Claude Code 插件,把 6 个验证过的 AI 工程工作
|
|
|
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
|
-
- **
|
|
27
|
-
- **
|
|
26
|
+
- **4 个 hook 事件** — SessionStart / InstructionsLoaded / Stop / PreToolUse
|
|
27
|
+
- **2 个自动安装的 MCP + 1 个推荐插件** — context7 / sequential-thinking(plugin.json 内置)+ chrome-devtools-mcp(recommended,beta.8 解耦)
|
|
28
28
|
- **优雅降级** — 依赖缺失时进入 fallback 模式并清晰告知
|
|
29
29
|
|
|
30
30
|
## 为什么用
|
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,9 +51,13 @@ 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
|
const found = mcps.find((x) => x.name === m);
|
|
58
63
|
if (found) {
|
|
@@ -67,24 +72,21 @@ export async function doctor(args = []) {
|
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
// ---------- Recommended plugins ----------
|
|
75
|
+
// ---------- Recommended plugins (single registry; see cli/registry.js) ----------
|
|
71
76
|
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
77
|
let claudeMemEnabled = false;
|
|
78
|
-
for (const r of
|
|
78
|
+
for (const r of RECOMMENDED_PLUGINS) {
|
|
79
79
|
const p = plugins.find((x) => x.name === r.name);
|
|
80
80
|
if (p && p.status === "enabled") {
|
|
81
81
|
log.ok(`${r.name.padEnd(22)} ${color.dim(`v${p.version}`)}`);
|
|
82
|
-
if (r.
|
|
82
|
+
if (r.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
|
|
83
83
|
} else if (p && p.status === "failed") {
|
|
84
84
|
log.err(`${r.name.padEnd(22)} load failed`);
|
|
85
85
|
errors++;
|
|
86
86
|
} else {
|
|
87
|
-
log.warn(
|
|
87
|
+
log.warn(
|
|
88
|
+
`${r.name.padEnd(22)} not installed ${color.dim(`(run: claude plugin install ${r.installSpec})`)}`
|
|
89
|
+
);
|
|
88
90
|
warnings++;
|
|
89
91
|
}
|
|
90
92
|
}
|
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");
|
|
@@ -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, 4, "Installing curdx-flow plugin (
|
|
93
|
+
log.step(3, 4, "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(
|
|
@@ -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") {
|
|
@@ -224,6 +237,8 @@ export async function install(args = []) {
|
|
|
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 {
|
package/cli/protocols.js
CHANGED
|
@@ -5,7 +5,14 @@
|
|
|
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
17
|
|
|
11
18
|
const HOME = process.env.HOME || "";
|
|
@@ -53,16 +60,56 @@ function readGlobalMd() {
|
|
|
53
60
|
|
|
54
61
|
/**
|
|
55
62
|
* Locate the sentinel block in the content.
|
|
56
|
-
* Returns { start, end } indices into content,
|
|
63
|
+
* Returns { start, end } indices into content, `null` if neither sentinel is
|
|
64
|
+
* present, or throws if the block is corrupted (begin without matching end).
|
|
65
|
+
* The throw is intentional — previously the corrupted case silently returned
|
|
66
|
+
* null, so the next run would append a SECOND block, producing drift.
|
|
57
67
|
*/
|
|
58
68
|
function findBlock(content) {
|
|
59
69
|
const start = content.indexOf(SENTINEL_BEGIN);
|
|
60
|
-
if (start === -1)
|
|
70
|
+
if (start === -1) {
|
|
71
|
+
// Also check for a dangling END without BEGIN — that is also corrupted.
|
|
72
|
+
if (content.indexOf(SENTINEL_END) !== -1) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: END sentinel found without BEGIN. ` +
|
|
75
|
+
`Manually inspect the file and remove the dangling END line, then re-run.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
61
80
|
const endIdx = content.indexOf(SENTINEL_END, start);
|
|
62
|
-
if (endIdx === -1)
|
|
81
|
+
if (endIdx === -1) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Corrupted protocol block in ${GLOBAL_CLAUDE_MD}: BEGIN sentinel found without END. ` +
|
|
84
|
+
`Manually remove the orphan BEGIN line (or restore the END), then re-run.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
63
87
|
return { start, end: endIdx + SENTINEL_END.length };
|
|
64
88
|
}
|
|
65
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Write `content` to `path` atomically: write to a sibling temp file first,
|
|
92
|
+
* then rename. This prevents a half-written CLAUDE.md if the process is
|
|
93
|
+
* interrupted mid-write, and avoids races between concurrent install /
|
|
94
|
+
* uninstall invocations.
|
|
95
|
+
*/
|
|
96
|
+
function atomicWrite(path, content) {
|
|
97
|
+
const tmp = `${path}.curdx-flow.tmp.${process.pid}`;
|
|
98
|
+
try {
|
|
99
|
+
writeFileSync(tmp, content, "utf-8");
|
|
100
|
+
renameSync(tmp, path);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// Best-effort cleanup of the temp file; swallow errors here since we
|
|
103
|
+
// are already re-throwing the real failure.
|
|
104
|
+
try {
|
|
105
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
106
|
+
} catch {
|
|
107
|
+
// ignore
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
66
113
|
/**
|
|
67
114
|
* Inject (or upgrade) the protocol block in ~/.claude/CLAUDE.md.
|
|
68
115
|
* @returns {{action:"created"|"upgraded"|"unchanged", path:string}}
|
|
@@ -81,8 +128,8 @@ export function injectGlobalProtocols() {
|
|
|
81
128
|
? (existing.endsWith("\n") ? "\n" : "\n\n")
|
|
82
129
|
: "";
|
|
83
130
|
const next = existing + sep + FULL_BLOCK + "\n";
|
|
84
|
-
|
|
85
|
-
return { action: existing.length === 0 ? "created" : "
|
|
131
|
+
atomicWrite(path, next);
|
|
132
|
+
return { action: existing.length === 0 ? "created" : "appended", path };
|
|
86
133
|
}
|
|
87
134
|
|
|
88
135
|
// Replace existing block (handle upgrade-in-place)
|
|
@@ -92,7 +139,7 @@ export function injectGlobalProtocols() {
|
|
|
92
139
|
}
|
|
93
140
|
const next =
|
|
94
141
|
existing.slice(0, block.start) + FULL_BLOCK + existing.slice(block.end);
|
|
95
|
-
|
|
142
|
+
atomicWrite(path, next);
|
|
96
143
|
return { action: "upgraded", path };
|
|
97
144
|
}
|
|
98
145
|
|
|
@@ -119,6 +166,6 @@ export function removeGlobalProtocols() {
|
|
|
119
166
|
}
|
|
120
167
|
|
|
121
168
|
const next = existing.slice(0, start) + existing.slice(end);
|
|
122
|
-
|
|
169
|
+
atomicWrite(path, next);
|
|
123
170
|
return { action: "removed", path };
|
|
124
171
|
}
|
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
|
+
];
|
package/cli/uninstall.js
CHANGED
|
@@ -15,18 +15,15 @@ import {
|
|
|
15
15
|
listPlugins,
|
|
16
16
|
} from "./utils.js";
|
|
17
17
|
import { removeGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
|
|
18
|
+
import { RECOMMENDED_PLUGINS } from "./registry.js";
|
|
18
19
|
|
|
19
20
|
const HOME = process.env.HOME || "";
|
|
20
21
|
|
|
21
|
-
//
|
|
22
|
-
const RECOMMENDED =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name: "frontend-design",
|
|
27
|
-
uninstallSpec: "frontend-design@claude-plugins-official",
|
|
28
|
-
},
|
|
29
|
-
];
|
|
22
|
+
// Pull uninstall-relevant subset from the single registry. See registry.js.
|
|
23
|
+
const RECOMMENDED = RECOMMENDED_PLUGINS.map(({ name, uninstallSpec }) => ({
|
|
24
|
+
name,
|
|
25
|
+
uninstallSpec,
|
|
26
|
+
}));
|
|
30
27
|
|
|
31
28
|
// Symlinks created by install.js (only cleaned with --purge)
|
|
32
29
|
const MANAGED_SYMLINKS = [
|
package/cli/upgrade.js
CHANGED
|
@@ -3,13 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { color, log, run, listPlugins, claudeVersion } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"claude-mem@thedotmack",
|
|
11
|
-
"frontend-design@claude-plugins-official",
|
|
12
|
-
];
|
|
6
|
+
import {
|
|
7
|
+
PLUGINS_TO_UPDATE,
|
|
8
|
+
MARKETPLACES_TO_REFRESH,
|
|
9
|
+
} from "./registry.js";
|
|
13
10
|
|
|
14
11
|
export async function upgrade(args = []) {
|
|
15
12
|
log.title("⬆️ CurDX-Flow upgrade");
|
|
@@ -19,10 +16,9 @@ export async function upgrade(args = []) {
|
|
|
19
16
|
process.exit(1);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
// Refresh marketplaces first
|
|
19
|
+
// Refresh marketplaces first (derived from cli/registry.js)
|
|
23
20
|
log.step(1, 2, "Refreshing marketplaces...");
|
|
24
|
-
const
|
|
25
|
-
for (const mp of marketplaces) {
|
|
21
|
+
for (const mp of MARKETPLACES_TO_REFRESH) {
|
|
26
22
|
const r = await run(
|
|
27
23
|
"claude",
|
|
28
24
|
["plugin", "marketplace", "update", mp],
|
package/cli/utils.js
CHANGED
|
@@ -247,8 +247,8 @@ export function pluginCacheDir(pluginName = "curdx-flow", marketplace = "curdx-f
|
|
|
247
247
|
// detection + self-healing: create a symlink to the user-level bun install
|
|
248
248
|
// in a PATH-visible directory.
|
|
249
249
|
|
|
250
|
-
import { mkdirSync, symlinkSync, lstatSync, unlinkSync, readlinkSync } from "node:fs";
|
|
251
|
-
// `
|
|
250
|
+
import { existsSync, mkdirSync, symlinkSync, lstatSync, unlinkSync, readlinkSync } from "node:fs";
|
|
251
|
+
// `join` already imported at the top of this file.
|
|
252
252
|
|
|
253
253
|
const HOME = process.env.HOME || "";
|
|
254
254
|
|
package/commands/init.md
CHANGED
|
@@ -72,8 +72,8 @@ Append (if not already present):
|
|
|
72
72
|
### Step 5: Health Check
|
|
73
73
|
|
|
74
74
|
Run `npx @curdx/flow doctor` (or inline its checks) to verify:
|
|
75
|
-
-
|
|
76
|
-
- Recommended plugins status (pua / claude-mem / frontend-design)
|
|
75
|
+
- 2 bundled MCPs started (context7 / sequential-thinking)
|
|
76
|
+
- Recommended plugins status (pua / claude-mem / frontend-design / chrome-devtools-mcp)
|
|
77
77
|
|
|
78
78
|
### Step 6: Prompt Next Steps
|
|
79
79
|
|
package/commands/spec.md
CHANGED
package/commands/start.md
CHANGED
|
@@ -48,7 +48,7 @@ Mode must be `fast`, `standard`, or `enterprise`. Invalid → default to `standa
|
|
|
48
48
|
## Branch logic
|
|
49
49
|
|
|
50
50
|
### Branch A: `--list`
|
|
51
|
-
Enumerate every directory under `.flow/specs/`, read each `.state.json` for `phase` and `
|
|
51
|
+
Enumerate every directory under `.flow/specs/`, read each `.state.json` for `phase` and `updated` (per `schemas/spec-state.schema.json`), print a numbered list, then `AskUserQuestion` to pick one. Picking sets `.flow/.active-spec` and exits.
|
|
52
52
|
|
|
53
53
|
### Branch B: `--resume` (no name)
|
|
54
54
|
Read `.flow/.active-spec`. If it points to a valid spec dir, report its current phase and next suggested command (`/curdx-flow:spec` if incomplete, `/curdx-flow:implement` if tasks ready). If `.active-spec` is empty or stale, fall back to Branch A.
|
|
@@ -61,17 +61,25 @@ Create a new spec:
|
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
mkdir -p ".flow/specs/$SPEC_NAME"
|
|
64
|
+
# NOTE: field names MUST match schemas/spec-state.schema.json:
|
|
65
|
+
# - spec_name (not "spec")
|
|
66
|
+
# - created (date, not "created_at")
|
|
67
|
+
# - updated (date-time, not "updated_at")
|
|
68
|
+
# - phase must be one of the enum values; the initial phase is "research"
|
|
69
|
+
# (there is no "created" phase — that was schema drift pre-beta.9)
|
|
70
|
+
# - version is required
|
|
64
71
|
cat > ".flow/specs/$SPEC_NAME/.state.json" <<JSON
|
|
65
72
|
{
|
|
66
|
-
"
|
|
73
|
+
"version": "1.0",
|
|
74
|
+
"spec_name": "$SPEC_NAME",
|
|
67
75
|
"goal": "$GOAL",
|
|
68
76
|
"mode": "$FLAG_MODE",
|
|
69
|
-
"phase": "
|
|
77
|
+
"phase": "research",
|
|
70
78
|
"phase_status": {},
|
|
71
79
|
"strategy": "auto",
|
|
72
80
|
"execute_state": {},
|
|
73
|
-
"
|
|
74
|
-
"
|
|
81
|
+
"created": "$(date -u +%Y-%m-%d)",
|
|
82
|
+
"updated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
75
83
|
}
|
|
76
84
|
JSON
|
|
77
85
|
echo "$SPEC_NAME" > .flow/.active-spec
|
package/hooks/hooks.json
CHANGED
|
@@ -20,17 +20,6 @@
|
|
|
20
20
|
]
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
|
-
"PostToolUseFailure": [
|
|
24
|
-
{
|
|
25
|
-
"matcher": "Bash|Edit|Write",
|
|
26
|
-
"hooks": [
|
|
27
|
-
{
|
|
28
|
-
"type": "command",
|
|
29
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/fail-tracker.sh"
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
],
|
|
34
23
|
"Stop": [
|
|
35
24
|
{
|
|
36
25
|
"hooks": [
|
|
@@ -40,17 +40,20 @@ ACTIVE=$(cat .flow/.active-spec 2>/dev/null)
|
|
|
40
40
|
STATE_FILE=".flow/specs/$ACTIVE/.state.json"
|
|
41
41
|
[ ! -f "$STATE_FILE" ] && exit 0
|
|
42
42
|
|
|
43
|
-
# Read quickMode + mode
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
# Read quickMode + mode. Pass STATE_FILE via env (NOT shell interpolation
|
|
44
|
+
# into the python source) so an active-spec name containing quotes/$ cannot
|
|
45
|
+
# inject python code.
|
|
46
|
+
export STATE_FILE
|
|
47
|
+
QUICK_MODE=$(python3 -c '
|
|
48
|
+
import json, os
|
|
46
49
|
try:
|
|
47
|
-
s = json.load(open(
|
|
48
|
-
qm = s.get(
|
|
49
|
-
mode = s.get(
|
|
50
|
-
print(
|
|
50
|
+
s = json.load(open(os.environ["STATE_FILE"]))
|
|
51
|
+
qm = s.get("quickMode", False)
|
|
52
|
+
mode = s.get("mode", "")
|
|
53
|
+
print("true" if (qm or mode == "autonomous") else "false")
|
|
51
54
|
except Exception:
|
|
52
|
-
print(
|
|
53
|
-
|
|
55
|
+
print("false")
|
|
56
|
+
' 2>/dev/null)
|
|
54
57
|
|
|
55
58
|
if [ "$QUICK_MODE" = "true" ]; then
|
|
56
59
|
# Block and inject guidance
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# CurDX-Flow SessionStart Hook
|
|
3
3
|
# Duties:
|
|
4
|
-
# 1. Daily dependency check — nudge user to /flow
|
|
4
|
+
# 1. Daily dependency check — nudge user to `npx @curdx/flow install --all` if recommended plugins missing
|
|
5
5
|
# 2. Load active spec progress into session context
|
|
6
6
|
#
|
|
7
7
|
# Design notes:
|
|
@@ -56,6 +56,12 @@ if ! command -v python3 >/dev/null 2>&1; then
|
|
|
56
56
|
allow_stop
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
|
+
# Export STATE_FILE BEFORE invoking python3 — the heredoc-based parser reads
|
|
60
|
+
# os.environ["STATE_FILE"]. Previously the export was placed after the
|
|
61
|
+
# heredoc, so python3 always got None, json.load(None) silently failed, and
|
|
62
|
+
# the stop-hook strategy never activated.
|
|
63
|
+
export STATE_FILE
|
|
64
|
+
|
|
59
65
|
read STRATEGY PHASE TASK_INDEX TOTAL_TASKS FAILED ROUNDS <<EOF
|
|
60
66
|
$(python3 <<'PY'
|
|
61
67
|
import json, os, sys
|
|
@@ -75,7 +81,6 @@ print(strategy, phase, ti, tt, failed, rounds)
|
|
|
75
81
|
PY
|
|
76
82
|
)
|
|
77
83
|
EOF
|
|
78
|
-
export STATE_FILE
|
|
79
84
|
|
|
80
85
|
# Only activate for stop-hook strategy + execute phase
|
|
81
86
|
[ "$STRATEGY" != "stop-hook" ] && allow_stop
|
|
@@ -95,12 +100,17 @@ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
|
95
100
|
TRANSCRIPT_TAIL=$(tail -c 51200 "$TRANSCRIPT_PATH" 2>/dev/null || echo "")
|
|
96
101
|
fi
|
|
97
102
|
|
|
103
|
+
# Python state-file updates: use quoted heredocs (<<'PY') + os.environ so
|
|
104
|
+
# the spec-name-derived STATE_FILE path is NEVER interpolated into the
|
|
105
|
+
# python source text. Previously a spec name containing single quotes or
|
|
106
|
+
# $-signs could break the script or inject arbitrary code.
|
|
107
|
+
|
|
98
108
|
# Check for explicit completion signals
|
|
99
109
|
if echo "$TRANSCRIPT_TAIL" | grep -q "ALL_TASKS_COMPLETE"; then
|
|
100
110
|
# Cleanup: mark phase completed
|
|
101
|
-
python3 <<PY 2>/dev/null
|
|
102
|
-
import json
|
|
103
|
-
p = "
|
|
111
|
+
python3 <<'PY' 2>/dev/null
|
|
112
|
+
import json, os
|
|
113
|
+
p = os.environ["STATE_FILE"]
|
|
104
114
|
s = json.load(open(p))
|
|
105
115
|
s.setdefault("phase_status", {})["execute"] = "completed"
|
|
106
116
|
s["phase"] = "verify" # move to verify phase
|
|
@@ -112,16 +122,16 @@ fi
|
|
|
112
122
|
# Check for fail signal (accumulate; actual stop decision below)
|
|
113
123
|
if echo "$TRANSCRIPT_TAIL" | grep -q "TASK_FAILED"; then
|
|
114
124
|
# Increment failed_attempts
|
|
115
|
-
python3 <<PY 2>/dev/null
|
|
116
|
-
import json
|
|
117
|
-
p = "
|
|
125
|
+
python3 <<'PY' 2>/dev/null
|
|
126
|
+
import json, os
|
|
127
|
+
p = os.environ["STATE_FILE"]
|
|
118
128
|
s = json.load(open(p))
|
|
119
129
|
s.setdefault("execute_state", {})
|
|
120
130
|
s["execute_state"]["failed_attempts"] = s["execute_state"].get("failed_attempts", 0) + 1
|
|
121
131
|
json.dump(s, open(p, "w"), indent=2, ensure_ascii=False)
|
|
122
132
|
PY
|
|
123
|
-
# Re-read
|
|
124
|
-
FAILED=$(python3 -c
|
|
133
|
+
# Re-read — again via os.environ, no shell interpolation into python.
|
|
134
|
+
FAILED=$(python3 -c 'import json, os; print(json.load(open(os.environ["STATE_FILE"]))["execute_state"]["failed_attempts"])' 2>/dev/null || echo 0)
|
|
125
135
|
fi
|
|
126
136
|
|
|
127
137
|
# ---------- 6. Safety brakes ----------
|
|
@@ -138,9 +148,9 @@ fi
|
|
|
138
148
|
# Check if all tasks done
|
|
139
149
|
if [ "$TASK_INDEX" -ge "$TOTAL_TASKS" ] && [ "$TOTAL_TASKS" -gt 0 ]; then
|
|
140
150
|
# Mark complete
|
|
141
|
-
python3 <<PY 2>/dev/null
|
|
142
|
-
import json
|
|
143
|
-
p = "
|
|
151
|
+
python3 <<'PY' 2>/dev/null
|
|
152
|
+
import json, os
|
|
153
|
+
p = os.environ["STATE_FILE"]
|
|
144
154
|
s = json.load(open(p))
|
|
145
155
|
s.setdefault("phase_status", {})["execute"] = "completed"
|
|
146
156
|
s["phase"] = "verify"
|
|
@@ -151,9 +161,9 @@ fi
|
|
|
151
161
|
|
|
152
162
|
# ---------- 7. Block and continue ----------
|
|
153
163
|
# Increment round counter
|
|
154
|
-
python3 <<PY 2>/dev/null
|
|
155
|
-
import json
|
|
156
|
-
p = "
|
|
164
|
+
python3 <<'PY' 2>/dev/null
|
|
165
|
+
import json, os
|
|
166
|
+
p = os.environ["STATE_FILE"]
|
|
157
167
|
s = json.load(open(p))
|
|
158
168
|
s.setdefault("execute_state", {})
|
|
159
169
|
s["execute_state"]["global_iteration"] = s["execute_state"].get("global_iteration", 0) + 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curdx/flow",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.9",
|
|
4
4
|
"description": "CLI installer for CurDX-Flow — AI engineering workflow meta-framework for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"agent-preamble/",
|
|
23
23
|
"templates/",
|
|
24
24
|
"schemas/",
|
|
25
|
+
"skills/",
|
|
25
26
|
"README.md",
|
|
26
27
|
"CHANGELOG.md",
|
|
27
28
|
"LICENSE"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brownfield-index
|
|
3
|
+
description: Invoke when the user is new to an unfamiliar / legacy / brownfield codebase and wants a structural understanding — module map, component inventory, API surface, data flow. Triggers on "legacy code", "brownfield", "unfamiliar", "new to this code", "new to this project", "just joined", "inherited codebase", "explore codebase", "understand structure", "index code", "map modules", "tour", "onboard", "what is this project", "老代码", "棕地", "不熟悉", "新接手", "新项目", "代码探索", "结构理解", "索引", "模块地图", "先了解下", "先熟悉一下".
|
|
4
|
+
allowed-tools: [Read, Grep, Glob, Bash]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Brownfield Index
|
|
8
|
+
|
|
9
|
+
You are invoked when the user needs a structural map of an existing codebase they are not yet familiar with.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. The repository root is the current working directory (or a path the user specifies).
|
|
14
|
+
2. The project is not a new `/curdx-flow:init`-ed greenfield project (if it is, direct the user to `/curdx-flow:start` instead).
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Detect project type
|
|
19
|
+
|
|
20
|
+
Read `package.json` / `Cargo.toml` / `pyproject.toml` / `go.mod` / `pom.xml` to classify the ecosystem and build tool. This determines which directory conventions to apply.
|
|
21
|
+
|
|
22
|
+
### Step 2: Scan directory structure
|
|
23
|
+
|
|
24
|
+
Produce a top-level inventory:
|
|
25
|
+
- **Entry points** (main / index / bin scripts)
|
|
26
|
+
- **Module directories** (src/, lib/, internal/, pkg/ …)
|
|
27
|
+
- **Test directories**
|
|
28
|
+
- **Config files**
|
|
29
|
+
- **Tooling** (CI, lint, format configs)
|
|
30
|
+
|
|
31
|
+
### Step 3: Component inventory
|
|
32
|
+
|
|
33
|
+
For each module directory, list:
|
|
34
|
+
- Files and their apparent role (inferred from names + top-of-file comments)
|
|
35
|
+
- Public exports / exported symbols
|
|
36
|
+
- Third-party dependencies imported
|
|
37
|
+
|
|
38
|
+
### Step 4: API surface
|
|
39
|
+
|
|
40
|
+
If HTTP / RPC endpoints exist, index them: route → handler → middleware. For CLI tools, index commands → handlers.
|
|
41
|
+
|
|
42
|
+
### Step 5: Write index document
|
|
43
|
+
|
|
44
|
+
Output `.flow/codebase-index.md` containing:
|
|
45
|
+
- **Overview** (project purpose, build tool, runtime)
|
|
46
|
+
- **Directory tree** (with per-directory one-liner descriptions)
|
|
47
|
+
- **Entry points** (where execution starts)
|
|
48
|
+
- **Key abstractions** (core types, interfaces, classes that everything else hangs off)
|
|
49
|
+
- **External dependencies** (grouped: prod runtime / dev tooling / transitive)
|
|
50
|
+
- **Known gaps / red flags** (missing tests, TODOs, suspicious patterns)
|
|
51
|
+
|
|
52
|
+
### Step 6: Hand off
|
|
53
|
+
|
|
54
|
+
Point the user at the next useful action:
|
|
55
|
+
- "Looking to add a feature here? Run `/curdx-flow:start <name>` to begin a spec."
|
|
56
|
+
- "Debugging something specific? Run `/curdx-flow:debug '<symptom>'`."
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
|
|
60
|
+
This skill uses Read + Grep + Glob + Bash with no specialized agent — general tools are enough for structural discovery. The index is meant to be quick (5–10 minutes), not exhaustive.
|
|
61
|
+
|
|
62
|
+
For deep research into a specific library or framework, use `context7` MCP directly.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-qa
|
|
3
|
+
description: Invoke when the user wants to test a UI/frontend in a real browser — accessibility, performance, console errors, network traffic, visual regression. Triggers on "browser test", "test in browser", "UI test", "e2e test", "frontend test", "accessibility", "a11y", "WCAG", "lighthouse", "performance audit", "console error", "network request", "cross-browser", "responsive", "mobile test", "visual regression", "screenshot", "浏览器测试", "UI 测试", "可访问性", "性能", "控制台错误", "截图", "移动端", "兼容性", "在浏览器里跑", "看看 console".
|
|
4
|
+
allowed-tools: [Read, Write, Bash, Grep, Glob, WebFetch]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Browser QA
|
|
8
|
+
|
|
9
|
+
You are invoked when the user wants real-browser QA of a UI flow.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. `chrome-devtools` MCP is available (`mcp__chrome-devtools__*`). If missing, fall back to a manual checklist.
|
|
14
|
+
2. A URL (dev server or deployed) is available. Prompt for it if not provided.
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Clarify scope
|
|
19
|
+
|
|
20
|
+
Confirm with the user:
|
|
21
|
+
- **URL under test** (local `http://localhost:3000` or remote)
|
|
22
|
+
- **Flow to test** (e.g., "sign up → dashboard → logout")
|
|
23
|
+
- **What success looks like** (accessibility / performance / zero console errors / visual match)
|
|
24
|
+
|
|
25
|
+
### Step 2: Dispatch `flow-qa-engineer`
|
|
26
|
+
|
|
27
|
+
Delegate to the `flow-qa-engineer` agent. It will:
|
|
28
|
+
1. Open the target URL via `mcp__chrome-devtools__new_page`
|
|
29
|
+
2. Drive the flow with `mcp__chrome-devtools__click` / `fill` / `navigate`
|
|
30
|
+
3. Capture `list_console_messages`, `list_network_requests`, `take_screenshot`, optionally `lighthouse_audit`
|
|
31
|
+
4. Compare against expected behavior
|
|
32
|
+
|
|
33
|
+
### Step 3: Report findings
|
|
34
|
+
|
|
35
|
+
Produce `.flow/specs/<active>/qa-report.md` with:
|
|
36
|
+
- **Bugs** (reproducible, severity P1/P2/P3)
|
|
37
|
+
- **Performance** (LCP / INP / CLS from Lighthouse)
|
|
38
|
+
- **Accessibility** (axe violations with WCAG references)
|
|
39
|
+
- **Console errors** (full stack traces)
|
|
40
|
+
- **Screenshots** (attached)
|
|
41
|
+
|
|
42
|
+
### Step 4: Hand off
|
|
43
|
+
|
|
44
|
+
If bugs found: suggest `/curdx-flow:debug "<bug title>"` for systematic root-cause analysis.
|
|
45
|
+
If accessibility violations: suggest fixes inline with WCAG refs.
|
|
46
|
+
|
|
47
|
+
## References
|
|
48
|
+
|
|
49
|
+
- `flow-qa-engineer` agent: `@${CLAUDE_PLUGIN_ROOT}/agents/flow-qa-engineer.md`
|
|
50
|
+
- chrome-devtools MCP docs: https://github.com/ChromeDevTools/chrome-devtools-mcp
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: epic
|
|
3
|
+
description: Invoke when user wants to break a large feature into multiple smaller specs with a dependency graph. Triggers on "epic", "big feature", "too big", "decompose", "break down", "break into", "split into", "multi-spec", "multiple features", "sub-features", "vertical slice", "parent feature", "large scope", "大功能", "太大", "拆分", "拆解", "分解", "多个规格", "子功能", "垂直切片", "史诗级", "分几个做", "一个 sprint 做不完", "需要拆".
|
|
4
|
+
allowed-tools: [Read, Write, Grep, Glob, Bash]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Epic Decomposition
|
|
8
|
+
|
|
9
|
+
You are invoked when the user wants to break a large feature into multiple vertical-slice specs.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. A `.flow/` project must exist (run `/curdx-flow:init` first if missing).
|
|
14
|
+
2. The user has stated a feature scope that is too large for a single spec.
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Clarify the epic scope
|
|
19
|
+
|
|
20
|
+
Ask the user (or infer from context) for:
|
|
21
|
+
- **Epic name** (short identifier, kebab-case)
|
|
22
|
+
- **One-sentence goal** of the whole epic
|
|
23
|
+
- **Hard boundary**: what is explicitly out of scope for this epic
|
|
24
|
+
|
|
25
|
+
### Step 2: Dispatch `flow-triage-analyst`
|
|
26
|
+
|
|
27
|
+
Delegate to the `flow-triage-analyst` agent with the epic name + goal + boundary. The agent returns:
|
|
28
|
+
- A vertical-slice decomposition (not horizontal by layer)
|
|
29
|
+
- Dependency graph between slices
|
|
30
|
+
- Shared interfaces that must be frozen before parallel work begins
|
|
31
|
+
- Suggested slice ordering (MVP → iteration → polish)
|
|
32
|
+
|
|
33
|
+
### Step 3: Write epic manifest
|
|
34
|
+
|
|
35
|
+
Create `.flow/_epics/<epic-name>/epic.md` with:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
# Epic: <name>
|
|
39
|
+
|
|
40
|
+
## Goal
|
|
41
|
+
<one sentence>
|
|
42
|
+
|
|
43
|
+
## Slices (vertical)
|
|
44
|
+
| ID | Slice | Depends on | Shared interface |
|
|
45
|
+
|----|-------|-----------|------------------|
|
|
46
|
+
| S1 | ... | — | — |
|
|
47
|
+
| S2 | ... | S1 | `types/auth.ts` |
|
|
48
|
+
| S3 | ... | S1 | — |
|
|
49
|
+
|
|
50
|
+
## Frozen Interfaces
|
|
51
|
+
(contracts that must not change once slices start)
|
|
52
|
+
|
|
53
|
+
## Out of Scope
|
|
54
|
+
- ...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 4: Scaffold sub-spec skeletons
|
|
58
|
+
|
|
59
|
+
For each slice, create `.flow/specs/<epic-name>-<slice-id>/` with a minimal `.state.json` linking back to the epic manifest.
|
|
60
|
+
|
|
61
|
+
### Step 5: Report to user
|
|
62
|
+
|
|
63
|
+
Summarize: "Epic `<name>` decomposed into N vertical slices. Start any slice with `/curdx-flow:start <epic-name>-<slice-id>`. Suggested order: S1 → S2 → S3."
|
|
64
|
+
|
|
65
|
+
## References
|
|
66
|
+
|
|
67
|
+
- Vertical-slice methodology: `@${CLAUDE_PLUGIN_ROOT}/knowledge/epic-decomposition.md`
|
|
68
|
+
- Spec skeleton: `@${CLAUDE_PLUGIN_ROOT}/templates/`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-audit
|
|
3
|
+
description: Invoke when the user wants a security review — OWASP Top 10, STRIDE threat modeling, credential handling, injection, secrets, sensitive data handling. Triggers on "security", "auth", "authentication", "credential", "password", "secret", "API key", "token", "OWASP", "STRIDE", "CVE", "vulnerability", "injection", "XSS", "CSRF", "SSRF", "SQL injection", "hardcoded secret", "sensitive data", "leak", "安全", "认证", "凭证", "密码", "密钥", "令牌", "漏洞", "注入", "硬编码", "敏感数据", "泄露", "API key 会不会泄", "有没有安全问题".
|
|
4
|
+
allowed-tools: [Read, Grep, Glob, Bash, WebSearch]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Security Audit
|
|
8
|
+
|
|
9
|
+
You are invoked when the user wants a systematic security review of the current spec or codebase.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. The code or spec under review is reachable from the current working directory.
|
|
14
|
+
2. The user has identified the scope (current spec, specific module, or whole repo).
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Clarify audit scope
|
|
19
|
+
|
|
20
|
+
Confirm:
|
|
21
|
+
- **Scope** (current spec / specific paths / whole repo)
|
|
22
|
+
- **Depth** (OWASP-only / OWASP + STRIDE / + dependency CVE scan)
|
|
23
|
+
- **Risk tolerance** (block on any SR / only block on SR with POC / advisory only)
|
|
24
|
+
|
|
25
|
+
### Step 2: Dispatch `flow-security-auditor`
|
|
26
|
+
|
|
27
|
+
Delegate to the `flow-security-auditor` agent. It will:
|
|
28
|
+
1. Scan for hardcoded secrets, weak crypto, unsanitized inputs
|
|
29
|
+
2. Apply OWASP Top 10 (A01 Broken Access Control → A10 SSRF)
|
|
30
|
+
3. Apply STRIDE threat modeling (Spoofing, Tampering, Repudiation, Information disclosure, DoS, Elevation)
|
|
31
|
+
4. Run dependency CVE scan (`npm audit` / equivalent)
|
|
32
|
+
5. Produce a findings report with severity labels (SR = Blocking Red line, SW = Warning, SM = Mandatory-to-address)
|
|
33
|
+
|
|
34
|
+
### Step 3: Write security report
|
|
35
|
+
|
|
36
|
+
Output `.flow/specs/<active>/security-audit.md` containing:
|
|
37
|
+
- **SR (blocking)** — must fix before ship
|
|
38
|
+
- **SW (warning)** — should fix, won't block
|
|
39
|
+
- **SM (mandatory)** — baseline items that must be present
|
|
40
|
+
- **CVE hits** — direct / transitive dependencies with known vulns
|
|
41
|
+
- **Recommended fixes** — concrete patches, not generic advice
|
|
42
|
+
|
|
43
|
+
### Step 4: Enforce gate
|
|
44
|
+
|
|
45
|
+
Apply the `security-gate` (`@${CLAUDE_PLUGIN_ROOT}/gates/security-gate.md`) — if any SR findings exist, block completion until remediated or explicitly waived with a D-NN decision in STATE.md.
|
|
46
|
+
|
|
47
|
+
## References
|
|
48
|
+
|
|
49
|
+
- `flow-security-auditor` agent: `@${CLAUDE_PLUGIN_ROOT}/agents/flow-security-auditor.md`
|
|
50
|
+
- security-gate: `@${CLAUDE_PLUGIN_ROOT}/gates/security-gate.md`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-sketch
|
|
3
|
+
description: Invoke when the user wants UI design drafts — components, layouts, variants, mockups, CSS/theme/styling decisions. Triggers on "design UI", "UI design", "component layout", "variants", "wireframe", "mockup", "prototype", "sketch", "draft layout", "visual design", "styling", "CSS", "theming", "dark mode", "responsive design", "color scheme", "UI 设计", "界面", "组件", "布局", "变体", "原型", "草图", "样式", "主题", "暗色模式", "做个 UI", "出几个版本", "换几种颜色".
|
|
4
|
+
allowed-tools: [Read, Write, Bash, WebSearch]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# UI Sketch
|
|
8
|
+
|
|
9
|
+
You are invoked when the user wants fresh UI design drafts — typically 2–4 variants for comparison, or a single iterative refinement.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. The `frontend-design` skill (Anthropic official) should be installed. Without it, fall back to Tailwind + shadcn/ui defaults.
|
|
14
|
+
2. The user provides a description of the UI goal (component, page, or flow).
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Clarify design brief
|
|
19
|
+
|
|
20
|
+
Confirm with the user:
|
|
21
|
+
- **What is being designed** (component / page / full screen)
|
|
22
|
+
- **Context** (consumer product / enterprise tool / marketing site / internal dashboard)
|
|
23
|
+
- **Must-haves** (brand colors / existing design system / responsive breakpoints)
|
|
24
|
+
- **Variant count** (default: 3 variants with distinct design directions)
|
|
25
|
+
|
|
26
|
+
### Step 2: Dispatch `flow-ux-designer`
|
|
27
|
+
|
|
28
|
+
Delegate to the `flow-ux-designer` agent with the brief. It will:
|
|
29
|
+
1. Invoke the `frontend-design` skill with the brief
|
|
30
|
+
2. Generate N variant HTML/JSX files under `.flow/specs/<active>/sketches/`
|
|
31
|
+
3. For each variant, produce a rationale: typography, color, layout decisions
|
|
32
|
+
4. Open the variants for user preview if a dev server is running
|
|
33
|
+
|
|
34
|
+
### Step 3: Present variants
|
|
35
|
+
|
|
36
|
+
Show the user:
|
|
37
|
+
- **Variant preview URLs** or file paths
|
|
38
|
+
- **Design rationale** per variant (what's different, why)
|
|
39
|
+
- **Accessibility notes** (contrast ratios, focus states)
|
|
40
|
+
|
|
41
|
+
### Step 4: Iterate or finalize
|
|
42
|
+
|
|
43
|
+
- If user picks a variant: move the chosen file into the spec's `design.md` asset section.
|
|
44
|
+
- If user wants a hybrid: dispatch `flow-ux-designer` again with "merge variant A layout + variant B color scheme".
|
|
45
|
+
|
|
46
|
+
## References
|
|
47
|
+
|
|
48
|
+
- `flow-ux-designer` agent: `@${CLAUDE_PLUGIN_ROOT}/agents/flow-ux-designer.md`
|
|
49
|
+
- `flow-ui-researcher` agent (for competitive reference scraping): `@${CLAUDE_PLUGIN_ROOT}/agents/flow-ui-researcher.md`
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# CurDX-Flow PostToolUseFailure Hook
|
|
3
|
-
# Tracks consecutive tool failures to enable pua integration (Phase 4+).
|
|
4
|
-
# For now, just maintains a counter in plugin data directory.
|
|
5
|
-
#
|
|
6
|
-
# Future: when pua is installed and fail_count >= threshold, auto-invoke /pua:pua.
|
|
7
|
-
|
|
8
|
-
set -u
|
|
9
|
-
|
|
10
|
-
DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.claude/plugins/data/curdx-flow}"
|
|
11
|
-
COUNTER="$DATA_DIR/fail-count"
|
|
12
|
-
|
|
13
|
-
mkdir -p "$DATA_DIR" 2>/dev/null || true
|
|
14
|
-
|
|
15
|
-
# Read current count
|
|
16
|
-
CURRENT=0
|
|
17
|
-
[ -f "$COUNTER" ] && CURRENT="$(cat "$COUNTER" 2>/dev/null || echo 0)"
|
|
18
|
-
|
|
19
|
-
# Increment
|
|
20
|
-
NEXT=$((CURRENT + 1))
|
|
21
|
-
echo "$NEXT" > "$COUNTER" 2>/dev/null || true
|
|
22
|
-
|
|
23
|
-
# Placeholder for future pua escalation (Phase 4+):
|
|
24
|
-
# if [ "$NEXT" -ge 2 ] && command -v claude >/dev/null 2>&1; then
|
|
25
|
-
# if claude plugin list 2>/dev/null | grep -q 'pua'; then
|
|
26
|
-
# # Inject escalation suggestion via hook output
|
|
27
|
-
# ...
|
|
28
|
-
# fi
|
|
29
|
-
# fi
|
|
30
|
-
|
|
31
|
-
exit 0
|