@curdx/flow 2.0.21 → 2.2.0

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.
Files changed (57) hide show
  1. package/.claude-plugin/marketplace.json +25 -2
  2. package/.claude-plugin/plugin.json +10 -1
  3. package/CHANGELOG.md +106 -3
  4. package/README.md +3 -0
  5. package/README.zh.md +14 -5
  6. package/agent-preamble/preamble.md +3 -6
  7. package/agents/flow-qa-engineer.md +16 -15
  8. package/agents/flow-ui-researcher.md +2 -2
  9. package/agents/flow-verifier.md +3 -3
  10. package/bin/curdx-flow +5 -0
  11. package/cli/README.md +10 -9
  12. package/cli/install-bundled-mcps.js +37 -0
  13. package/cli/install-companions.js +19 -252
  14. package/cli/install-context7-config.js +97 -0
  15. package/cli/install-recommended-plugins.js +104 -0
  16. package/cli/install-required-plugins.js +57 -0
  17. package/cli/install-self-update.js +2 -91
  18. package/cli/install.js +12 -1
  19. package/cli/lib/claude.js +42 -11
  20. package/cli/lib/doctor-report.js +47 -8
  21. package/cli/lib/semver.js +95 -0
  22. package/cli/protocols-body.md +3 -2
  23. package/cli/utils.js +1 -0
  24. package/hooks/scripts/quick-mode-guard.sh +6 -7
  25. package/hooks/scripts/session-start.sh +6 -3
  26. package/knowledge/execution-strategies.md +5 -5
  27. package/knowledge/planning-reviews.md +2 -2
  28. package/knowledge/wave-execution.md +17 -17
  29. package/package.json +3 -3
  30. package/schemas/agent-frontmatter.schema.json +66 -0
  31. package/schemas/config.schema.json +24 -4
  32. package/schemas/gate-frontmatter.schema.json +30 -0
  33. package/schemas/hooks.schema.json +83 -0
  34. package/schemas/plugin-manifest.schema.json +66 -0
  35. package/schemas/skill-frontmatter.schema.json +72 -0
  36. package/schemas/spec-state.schema.json +7 -2
  37. package/skills/brownfield-index/SKILL.md +2 -1
  38. package/skills/browser-qa/SKILL.md +5 -4
  39. package/skills/debug/SKILL.md +105 -0
  40. package/skills/epic/SKILL.md +2 -1
  41. package/{commands/fast.md → skills/fast/SKILL.md} +2 -1
  42. package/{commands/help.md → skills/help/SKILL.md} +15 -5
  43. package/{commands/implement.md → skills/implement/SKILL.md} +14 -154
  44. package/skills/implement/references/wave-execution.md +162 -0
  45. package/{commands/init.md → skills/init/SKILL.md} +1 -0
  46. package/{commands/review.md → skills/review/SKILL.md} +38 -3
  47. package/skills/security-audit/SKILL.md +2 -1
  48. package/{commands/spec.md → skills/spec/SKILL.md} +4 -3
  49. package/{commands/start.md → skills/start/SKILL.md} +2 -1
  50. package/skills/ui-sketch/SKILL.md +2 -1
  51. package/skills/verify/SKILL.md +99 -0
  52. package/templates/CONTEXT.md.tmpl +1 -1
  53. package/templates/PROJECT.md.tmpl +1 -1
  54. package/templates/config.json.tmpl +6 -6
  55. package/templates/progress.md.tmpl +2 -2
  56. package/commands/debug.md +0 -199
  57. package/commands/verify.md +0 -142
package/cli/lib/claude.js CHANGED
@@ -14,20 +14,51 @@ export function claudeVersion() {
14
14
  return m ? m[1] : res.stdout.trim().split("\n")[0];
15
15
  }
16
16
 
17
+ function normalizePluginError(error) {
18
+ if (typeof error === "string") {
19
+ return error;
20
+ }
21
+ if (error && typeof error === "object") {
22
+ return error.message || error.code || JSON.stringify(error);
23
+ }
24
+ return String(error);
25
+ }
26
+
27
+ export function parsePluginListJson(output) {
28
+ const trimmed = String(output || "").trim();
29
+ if (!trimmed.startsWith("[")) {
30
+ return null;
31
+ }
32
+
33
+ const arr = JSON.parse(trimmed);
34
+ return arr.map((p) => {
35
+ const id = String(p.id || p.name || "");
36
+ const errors = Array.isArray(p.errors)
37
+ ? p.errors.map(normalizePluginError).filter(Boolean)
38
+ : [];
39
+ const enabled = p.enabled !== false;
40
+
41
+ return {
42
+ id,
43
+ name: id.split("@")[0],
44
+ marketplaceId: id.split("@")[1] || undefined,
45
+ version: p.version,
46
+ status: errors.length > 0 ? "failed" : enabled ? "enabled" : "disabled",
47
+ scope: p.scope,
48
+ errors,
49
+ raw: JSON.stringify(p),
50
+ };
51
+ });
52
+ }
53
+
17
54
  export function listPlugins() {
18
55
  const j = runSync("claude", ["plugin", "list", "--json"]);
19
- if (j.code === 0 && j.stdout.trim().startsWith("[")) {
56
+ if (j.code === 0) {
20
57
  try {
21
- const arr = JSON.parse(j.stdout);
22
- return arr.map((p) => ({
23
- id: String(p.id || ""),
24
- name: String(p.id || "").split("@")[0],
25
- marketplaceId: String(p.id || "").split("@")[1] || undefined,
26
- version: p.version,
27
- status: p.enabled === false ? "disabled" : "enabled",
28
- scope: p.scope,
29
- raw: JSON.stringify(p),
30
- }));
58
+ const plugins = parsePluginListJson(j.stdout);
59
+ if (plugins) {
60
+ return plugins;
61
+ }
31
62
  } catch {
32
63
  // JSON parse failed; fall through to legacy text parser.
33
64
  }
@@ -4,6 +4,13 @@ import {
4
4
  findPluginByRegistryEntry,
5
5
  hasMarketplace,
6
6
  } from "./claude.js";
7
+ import { isVersionAtLeast } from "./semver.js";
8
+
9
+ export const MIN_CLAUDE_VERSION = "2.1.110";
10
+
11
+ function pluginErrorDetails(plugin) {
12
+ return Array.isArray(plugin?.errors) ? plugin.errors : [];
13
+ }
7
14
 
8
15
  export function buildDoctorReport({
9
16
  claudeVersionValue,
@@ -37,6 +44,17 @@ export function buildDoctorReport({
37
44
 
38
45
  if (claudeVersionValue) {
39
46
  pushLine(lines, "ok", `claude CLI ${claudeVersionValue}`);
47
+ if (!isVersionAtLeast(claudeVersionValue, MIN_CLAUDE_VERSION)) {
48
+ pushLine(
49
+ lines,
50
+ "warn",
51
+ `claude CLI ${claudeVersionValue} below recommended ${MIN_CLAUDE_VERSION}`,
52
+ [
53
+ "curdx-flow uses modern Claude Code plugin dependency resolution and plugin bin/ PATH support",
54
+ "run: claude update",
55
+ ]
56
+ );
57
+ }
40
58
  } else {
41
59
  pushLine(lines, "err", "claude CLI not found (install Claude Code)");
42
60
  }
@@ -47,7 +65,12 @@ export function buildDoctorReport({
47
65
  if (curdx.status === "enabled") {
48
66
  pushLine(lines, "ok", `curdx-flow v${curdx.version} (enabled)`);
49
67
  } else {
50
- pushLine(lines, "err", `curdx-flow v${curdx.version} (${curdx.status})`);
68
+ pushLine(
69
+ lines,
70
+ "err",
71
+ `curdx-flow v${curdx.version} (${curdx.status})`,
72
+ pluginErrorDetails(curdx)
73
+ );
51
74
  }
52
75
  } else {
53
76
  pushLine(lines, "warn", "curdx-flow not installed → run curdx-flow install");
@@ -67,7 +90,12 @@ export function buildDoctorReport({
67
90
  if (plugin && plugin.status === "enabled") {
68
91
  pushSectionLine(requiredSection, "ok", `${entry.name.padEnd(22)} v${plugin.version || "unknown"}`);
69
92
  } else if (plugin && plugin.status === "failed") {
70
- pushSectionLine(requiredSection, "err", `${entry.name.padEnd(22)} load failed`);
93
+ pushSectionLine(
94
+ requiredSection,
95
+ "err",
96
+ `${entry.name.padEnd(22)} load failed`,
97
+ pluginErrorDetails(plugin)
98
+ );
71
99
  } else {
72
100
  pushSectionLine(
73
101
  requiredSection,
@@ -120,7 +148,12 @@ export function buildDoctorReport({
120
148
  pushSectionLine(recommendedSection, "ok", `${entry.name.padEnd(22)} v${plugin.version}`);
121
149
  if (entry.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
122
150
  } else if (plugin && plugin.status === "failed") {
123
- pushSectionLine(recommendedSection, "err", `${entry.name.padEnd(22)} load failed`);
151
+ pushSectionLine(
152
+ recommendedSection,
153
+ "err",
154
+ `${entry.name.padEnd(22)} load failed`,
155
+ pluginErrorDetails(plugin)
156
+ );
124
157
  } else {
125
158
  pushSectionLine(
126
159
  recommendedSection,
@@ -133,16 +166,22 @@ export function buildDoctorReport({
133
166
 
134
167
  const duplicates = findDuplicateMcps(mcps, userMcpConfig);
135
168
  if (duplicates.length > 0) {
136
- const duplicateSection = createSection("Legacy plugin-bundled MCPs still present:");
169
+ const duplicateSection = createSection("Duplicate MCP registrations:");
137
170
  for (const duplicate of duplicates) {
171
+ const details = duplicate.pluginEntry.plugin === "curdx-flow"
172
+ ? [
173
+ "migration: claude plugin update curdx-flow@curdx-flow-marketplace",
174
+ "then restart Claude Code",
175
+ ]
176
+ : [
177
+ `remove the duplicate user-level server if plugin:${duplicate.pluginEntry.plugin} should own it`,
178
+ `run: claude mcp remove --scope user ${duplicate.name}`,
179
+ ];
138
180
  pushSectionLine(
139
181
  duplicateSection,
140
182
  "warn",
141
183
  `${duplicate.name.padEnd(22)} both user-level AND plugin:${duplicate.pluginEntry.plugin} active`,
142
- [
143
- "migration: claude plugin update curdx-flow@curdx-flow-marketplace",
144
- "then restart Claude Code",
145
- ]
184
+ details
146
185
  );
147
186
  }
148
187
  }
@@ -0,0 +1,95 @@
1
+ function normalizeVersionToken(token) {
2
+ return /^\d+$/.test(token) ? Number(token) : token;
3
+ }
4
+
5
+ function parseVersion(version) {
6
+ const normalized = String(version || "").trim().replace(/^v/i, "");
7
+ const [coreRaw = "0", prereleaseRaw] = normalized.split("-", 2);
8
+ const core = coreRaw.split(".").map((part) => Number.parseInt(part, 10) || 0);
9
+ const prerelease = prereleaseRaw
10
+ ? prereleaseRaw.split(/[.-]/).filter(Boolean).map(normalizeVersionToken)
11
+ : [];
12
+
13
+ return { core, prerelease };
14
+ }
15
+
16
+ function compareIdentifier(left, right) {
17
+ if (left === right) {
18
+ return 0;
19
+ }
20
+
21
+ const leftIsNumber = typeof left === "number";
22
+ const rightIsNumber = typeof right === "number";
23
+
24
+ if (leftIsNumber && rightIsNumber) {
25
+ return left > right ? 1 : -1;
26
+ }
27
+
28
+ if (leftIsNumber) {
29
+ return -1;
30
+ }
31
+
32
+ if (rightIsNumber) {
33
+ return 1;
34
+ }
35
+
36
+ return left > right ? 1 : -1;
37
+ }
38
+
39
+ export function compareVersions(leftVersion, rightVersion) {
40
+ const left = parseVersion(leftVersion);
41
+ const right = parseVersion(rightVersion);
42
+ const coreLength = Math.max(left.core.length, right.core.length);
43
+
44
+ for (let index = 0; index < coreLength; index += 1) {
45
+ const leftPart = left.core[index] ?? 0;
46
+ const rightPart = right.core[index] ?? 0;
47
+ if (leftPart !== rightPart) {
48
+ return leftPart > rightPart ? 1 : -1;
49
+ }
50
+ }
51
+
52
+ const leftHasPrerelease = left.prerelease.length > 0;
53
+ const rightHasPrerelease = right.prerelease.length > 0;
54
+
55
+ if (!leftHasPrerelease && !rightHasPrerelease) {
56
+ return 0;
57
+ }
58
+
59
+ if (!leftHasPrerelease) {
60
+ return 1;
61
+ }
62
+
63
+ if (!rightHasPrerelease) {
64
+ return -1;
65
+ }
66
+
67
+ const prereleaseLength = Math.max(left.prerelease.length, right.prerelease.length);
68
+ for (let index = 0; index < prereleaseLength; index += 1) {
69
+ const leftPart = left.prerelease[index];
70
+ const rightPart = right.prerelease[index];
71
+
72
+ if (leftPart === undefined) {
73
+ return -1;
74
+ }
75
+
76
+ if (rightPart === undefined) {
77
+ return 1;
78
+ }
79
+
80
+ const comparison = compareIdentifier(leftPart, rightPart);
81
+ if (comparison !== 0) {
82
+ return comparison;
83
+ }
84
+ }
85
+
86
+ return 0;
87
+ }
88
+
89
+ export function isVersionNewer(latestVersion, currentVersion) {
90
+ return compareVersions(latestVersion, currentVersion) > 0;
91
+ }
92
+
93
+ export function isVersionAtLeast(version, minimumVersion) {
94
+ return compareVersions(version, minimumVersion) >= 0;
95
+ }
@@ -3,10 +3,11 @@
3
3
  All operations MUST strictly follow these system constraints:
4
4
 
5
5
  ### Language separation
6
- - **Tool / persistence layer = English**: commit messages, code, comments, file names, function names, PR descriptions, CLI log output, error messages thrown by code, and any artifact persisted to the repository or shown in a developer terminal.
6
+ - **Tool / persistence layer = English**: commit messages, code, comments, file names, function names, PR descriptions, error messages thrown by code, and any artifact persisted to the repository or consumed by agents / skills / hooks at runtime.
7
7
  - **Conversational layer = Simplified Chinese**: chat replies, explanations and reasoning shown directly to the human in a conversation interface (e.g. Claude Code chat).
8
+ - **Installer UX (`cli/` only, narrow carve-out)**: interactive menus printed by the npm-published installer under `cli/**` may present localized Chinese options because they serve a human operator at install time. This carve-out does NOT apply to `agents/`, `skills/`, `commands/`, `gates/`, `hooks/`, `knowledge/`, documentation, or any prose consumed by agents at runtime — those stay English.
8
9
 
9
- Rationale: English in the persistence/tool layer aligns with developer-tool industry norms (npm/git/cargo are all English) and keeps the codebase internationally collaborable. Chinese in the conversational layer matches the user's language preference. Mixing the two (e.g. Chinese commit messages, Chinese CLI log output) is a violation.
10
+ Rationale: English in the persistence / tool / agent-input layer aligns with developer-tool industry norms (npm/git/cargo are all English) and keeps AI / agent adaptation reliable. Chinese in the conversational layer matches the user's language preference. The installer carve-out reflects the fact that installer menus are a human-to-human interaction surface, not agent input. Mixing the two (e.g. Chinese commit messages, Chinese strings inside agent prompts) is a violation.
10
11
 
11
12
  ### Discovery & reasoning
12
13
  - **Library / framework / API questions**: query `context7` MCP first. Do not rely on training memory.
package/cli/utils.js CHANGED
@@ -29,6 +29,7 @@ export {
29
29
  listPluginMarketplaces,
30
30
  listPlugins,
31
31
  parseMcpList,
32
+ parsePluginListJson,
32
33
  readUserMcpConfig,
33
34
  } from "./lib/claude.js";
34
35
  export { ensureClaudeMemRuntimes, ensureRuntimeInPath } from "./lib/runtime.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # CurDX-Flow PreToolUse Hook for AskUserQuestion
3
- # Blocks AskUserQuestion when the active spec has quickMode=true or mode=autonomous.
4
- # This prevents the autonomous loop from stalling waiting for user input.
3
+ # Blocks AskUserQuestion when the active spec has quickMode=true.
4
+ # This prevents quick execution loops from stalling waiting for user input.
5
5
  #
6
6
  # The hook reads Claude's PreToolUse input JSON from stdin. We only act when
7
7
  # the tool being invoked is AskUserQuestion.
@@ -34,7 +34,7 @@ if [ "$TOOL_NAME" != "AskUserQuestion" ]; then
34
34
  exit 0
35
35
  fi
36
36
 
37
- # Check if we're in a flow project with quick mode enabled
37
+ # Check if we're in a flow project with quick mode enabled.
38
38
  [ ! -d ".flow" ] && exit 0
39
39
 
40
40
  ACTIVE=$(cat .flow/.active-spec 2>/dev/null)
@@ -43,7 +43,7 @@ ACTIVE=$(cat .flow/.active-spec 2>/dev/null)
43
43
  STATE_FILE=".flow/specs/$ACTIVE/.state.json"
44
44
  [ ! -f "$STATE_FILE" ] && exit 0
45
45
 
46
- # Read quickMode + mode. Pass STATE_FILE via env (NOT shell interpolation
46
+ # Read quickMode. Pass STATE_FILE via env (NOT shell interpolation
47
47
  # into the python source) so an active-spec name containing quotes/$ cannot
48
48
  # inject python code.
49
49
  export STATE_FILE
@@ -52,15 +52,14 @@ import json, os
52
52
  try:
53
53
  s = json.load(open(os.environ["STATE_FILE"]))
54
54
  qm = s.get("quickMode", False)
55
- mode = s.get("mode", "")
56
- print("true" if (qm or mode == "autonomous") else "false")
55
+ print("true" if qm else "false")
57
56
  except Exception:
58
57
  print("false")
59
58
  ' 2>/dev/null)
60
59
 
61
60
  if [ "$QUICK_MODE" = "true" ]; then
62
61
  # Block and inject guidance
63
- MSG="[CurDX-Flow quick-mode-guard] Active spec '$ACTIVE' is in quick mode or autonomous mode — AskUserQuestion is forbidden. Decide autonomously based on user preferences in .flow/CONTEXT.md plus the most reasonable assumption, and record your assumption in .progress.md."
62
+ MSG="[CurDX-Flow quick-mode-guard] Active spec '$ACTIVE' is in quick mode — AskUserQuestion is forbidden. Decide based on user preferences in .flow/CONTEXT.md plus the most reasonable assumption, and record your assumption in .progress.md."
64
63
  emit_pretooluse_deny "$MSG"
65
64
  exit 0
66
65
  fi
@@ -32,9 +32,12 @@ if [ "$LAST_CHECK" != "$TODAY" ]; then
32
32
  if command -v claude >/dev/null 2>&1; then
33
33
  INSTALLED="$(claude plugin list 2>/dev/null || true)"
34
34
 
35
- echo "$INSTALLED" | grep -q 'pua' || MISSING+=("pua")
36
- echo "$INSTALLED" | grep -q 'claude-mem' || MISSING+=("claude-mem")
37
- echo "$INSTALLED" | grep -q 'frontend-design' || MISSING+=("frontend-design")
35
+ # Names must stay in lockstep with cli/registry.js RECOMMENDED_PLUGINS.
36
+ # Drift is guarded by test/registry-session-start-parity.test.js.
37
+ echo "$INSTALLED" | grep -q 'pua' || MISSING+=("pua")
38
+ echo "$INSTALLED" | grep -q 'claude-mem' || MISSING+=("claude-mem")
39
+ echo "$INSTALLED" | grep -q 'frontend-design' || MISSING+=("frontend-design")
40
+ echo "$INSTALLED" | grep -q 'chrome-devtools-mcp' || MISSING+=("chrome-devtools-mcp")
38
41
  fi
39
42
 
40
43
  if [ "${#MISSING[@]}" -gt 0 ]; then
@@ -65,8 +65,8 @@ The main agent reads tasks.md and **dispatches a fresh flow-executor subagent pe
65
65
 
66
66
  ```
67
67
  main agent
68
- ├─ Task() → subagent(task 1) → TASK_COMPLETE
69
- ├─ Task() → subagent(task 2) → TASK_COMPLETE
68
+ ├─ Agent() → subagent(task 1) → TASK_COMPLETE
69
+ ├─ Agent() → subagent(task 2) → TASK_COMPLETE
70
70
  └─ ...
71
71
  (each subagent has isolated context)
72
72
  ```
@@ -86,7 +86,7 @@ main agent
86
86
  ### Disadvantages
87
87
  - Dispatch overhead (each subagent reloads context)
88
88
  - Cross-task info sharing goes through files (.progress.md)
89
- - Not parallel (a single Task call is serial)
89
+ - Not parallel (a single Agent call is serial)
90
90
 
91
91
  ### Enable
92
92
  ```bash
@@ -149,7 +149,7 @@ main agent → task N → Stop → stop-watcher.sh
149
149
  ### How it works
150
150
  - Tasks in tasks.md marked `[P]` are "parallel-safe"
151
151
  - The main agent identifies consecutive `[P]` tasks as one wave
152
- - **In a single message**, it dispatches multiple Task() tool calls simultaneously
152
+ - **In a single message**, it dispatches multiple Agent() tool calls simultaneously
153
153
  - All tasks within a wave run in parallel
154
154
  - Waves run serially relative to each other
155
155
 
@@ -175,7 +175,7 @@ main agent
175
175
  ### Disadvantages
176
176
  - Parallel conflict risk (e.g., two tasks editing the same file)
177
177
  - Requires flow-planner to have tagged `[P]` (Phase 1-generated tasks.md should)
178
- - Main agent manages many Task calls, high context pressure
178
+ - Main agent manages many Agent calls, high context pressure
179
179
 
180
180
  ### Enable
181
181
  ```bash
@@ -149,7 +149,7 @@ Essentially runs `flow-architect` again — but this time not to generate the de
149
149
 
150
150
  A new agent `flow-devex-reviewer`, or reuse `flow-reviewer` by passing in the devex-gate.
151
151
 
152
- Phase 5 implementation: reuse `flow-reviewer` + `@gates/devex-gate.md`.
152
+ Phase 5 implementation: reuse `flow-reviewer` + `@${CLAUDE_PLUGIN_ROOT}/gates/devex-gate.md`.
153
153
 
154
154
  ---
155
155
 
@@ -160,7 +160,7 @@ Phase 5 implementation: reuse `flow-reviewer` + `@gates/devex-gate.md`.
160
160
  ```
161
161
 
162
162
  Workflow:
163
- 1. In parallel (single-message multi-Task), dispatch 4 reviews
163
+ 1. In parallel (single-message multi-Agent), dispatch 4 reviews
164
164
  2. Wait for all to return
165
165
  3. Merge findings, sort by priority
166
166
  4. Present to the user for decision
@@ -1,6 +1,6 @@
1
1
  # Wave Execution — DAG Parallel Execution Strategy
2
2
 
3
- > One of Phase 2's 4 execution strategies. Identify parallel-safe task groups via `[P]` markers, dispatch multiple Task tool calls **in a single message**, run in parallel within a wave and serially across waves.
3
+ > One of Phase 2's 4 execution strategies. Identify parallel-safe task groups via `[P]` markers, dispatch multiple Agent tool calls **in a single message**, run in parallel within a wave and serially across waves.
4
4
  >
5
5
  > Agents reference this via `@${CLAUDE_PLUGIN_ROOT}/knowledge/wave-execution.md`.
6
6
 
@@ -23,10 +23,10 @@ tasks.md:
23
23
  1.7 [P] add README to user
24
24
 
25
25
  Analysis:
26
- Wave 1: { 1.1, 1.2, 1.3 } — parallel (3 Tasks)
26
+ Wave 1: { 1.1, 1.2, 1.3 } — parallel (3 Agent calls)
27
27
  Wave 2: { 1.4 } — serial (VERIFY breaks)
28
28
  Wave 3: { 1.5 } — serial (no [P])
29
- Wave 4: { 1.6, 1.7 } — parallel (2 Tasks)
29
+ Wave 4: { 1.6, 1.7 } — parallel (2 Agent calls)
30
30
  ```
31
31
 
32
32
  ---
@@ -95,11 +95,11 @@ Rules:
95
95
 
96
96
  ---
97
97
 
98
- ## How Parallel Task Dispatch Actually Works
98
+ ## How Parallel Agent Dispatch Actually Works
99
99
 
100
- ### Key: multiple Task tool calls in a single message
100
+ ### Key: multiple Agent tool calls in a single message
101
101
 
102
- Claude Code's Task tool runs in parallel **when multiple calls appear in the same message**.
102
+ Claude Code's Agent tool runs in parallel **when multiple calls appear in the same message**.
103
103
  Across **separate messages** → runs sequentially.
104
104
 
105
105
  ### Correct form (Wave strategy)
@@ -107,9 +107,9 @@ Across **separate messages** → runs sequentially.
107
107
  ```
108
108
  # In a single main-agent response:
109
109
 
110
- Task(description="Task 1.1", prompt="...execute 1.1...")
111
- Task(description="Task 1.2", prompt="...execute 1.2...")
112
- Task(description="Task 1.3", prompt="...execute 1.3...")
110
+ Agent(description="Task 1.1", prompt="...execute 1.1...")
111
+ Agent(description="Task 1.2", prompt="...execute 1.2...")
112
+ Agent(description="Task 1.3", prompt="...execute 1.3...")
113
113
 
114
114
  # Wait for all to return before continuing to the next wave
115
115
  ```
@@ -118,13 +118,13 @@ Task(description="Task 1.3", prompt="...execute 1.3...")
118
118
 
119
119
  ```
120
120
  # One response:
121
- Task(description="Task 1.1", ...)
121
+ Agent(description="Task 1.1", ...)
122
122
  # wait for return
123
123
  # Next response:
124
- Task(description="Task 1.2", ...)
124
+ Agent(description="Task 1.2", ...)
125
125
  # wait for return
126
126
  # Next response:
127
- Task(description="Task 1.3", ...)
127
+ Agent(description="Task 1.3", ...)
128
128
  ```
129
129
 
130
130
  This is not parallel — it's serial subagent. The Wave strategy loses its meaning.
@@ -142,9 +142,9 @@ for wave_index, wave in enumerate(waves):
142
142
  echo " • $task.id $task.title"
143
143
 
144
144
  # === Step 2: dispatch (key: within a single message) ===
145
- # This is the main agent's response body. Call N Task tools at once.
145
+ # This is the main agent's response body. Call N Agent tools at once.
146
146
  results = await asyncio.gather([
147
- Task(
147
+ Agent(
148
148
  description=f"execute {task.id}",
149
149
  prompt=f"""
150
150
  You are the flow-executor agent.
@@ -193,7 +193,7 @@ for wave_index, wave in enumerate(waves):
193
193
  return
194
194
 
195
195
  # === Step 6: inter-wave synchronization point ===
196
- # All Tasks complete = wave ends
196
+ # All Agent calls complete = wave ends
197
197
  # Before next wave starts, confirm git state is consistent
198
198
 
199
199
  # All waves done
@@ -320,7 +320,7 @@ Progress: Wave 2/5 (60%)
320
320
 
321
321
  ### Ctrl+C interruption
322
322
 
323
- - Running Task calls in the current wave keep going (Claude Code's Task is an independent process)
323
+ - Running Agent calls in the current wave keep going (Claude Code's Agent tool starts independent work)
324
324
  - Next `/curdx-flow:start --resume` shows some tasks already committed
325
325
  - Resume from the failing task
326
326
 
@@ -372,7 +372,7 @@ If the planner missed a dependency, `[P]` may be wrong. Solutions:
372
372
 
373
373
  ### 2. A wave too large
374
374
 
375
- 10+ parallel Tasks may trigger API rate limits or context pressure. Solutions:
375
+ 10+ parallel Agent calls may trigger API rate limits or context pressure. Solutions:
376
376
  - `max_parallel: 5` splits a big wave into several
377
377
  - flow-planner avoids making waves too large when generating
378
378
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.0.21",
3
+ "version": "2.2.0",
4
4
  "description": "CLI installer for CurdX-Flow — AI engineering workflow meta-framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "curdx-flow": "bin/curdx-flow.js"
8
8
  },
9
9
  "scripts": {
10
+ "validate:contracts": "node scripts/validate-plugin-contracts.mjs",
10
11
  "test": "node --test test/*.test.js",
11
- "prepublishOnly": "node --test test/*.test.js && node bin/curdx-flow.js --version"
12
+ "prepublishOnly": "npm run validate:contracts && node --test test/*.test.js && node bin/curdx-flow.js --version"
12
13
  },
13
14
  "files": [
14
15
  "bin/",
@@ -16,7 +17,6 @@
16
17
  ".claude-plugin/",
17
18
  "agents/",
18
19
  "gates/",
19
- "commands/",
20
20
  "hooks/",
21
21
  "knowledge/",
22
22
  "agent-preamble/",
@@ -0,0 +1,66 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://curdx-flow.dev/schemas/agent-frontmatter.schema.json",
4
+ "title": "CurdX-Flow Agent Frontmatter",
5
+ "description": "Supported YAML frontmatter fields for agents/*.md plugin subagent definitions.",
6
+ "type": "object",
7
+ "required": ["name", "description"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "name": {
11
+ "type": "string",
12
+ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
13
+ },
14
+ "description": {
15
+ "type": "string",
16
+ "minLength": 1
17
+ },
18
+ "tools": {
19
+ "oneOf": [
20
+ { "type": "string" },
21
+ { "type": "array", "items": { "type": "string" } }
22
+ ]
23
+ },
24
+ "disallowedTools": {
25
+ "oneOf": [
26
+ { "type": "string" },
27
+ { "type": "array", "items": { "type": "string" } }
28
+ ]
29
+ },
30
+ "model": {
31
+ "type": "string"
32
+ },
33
+ "effort": {
34
+ "type": "string",
35
+ "enum": ["low", "medium", "high", "xhigh", "max"]
36
+ },
37
+ "maxTurns": {
38
+ "type": "integer",
39
+ "minimum": 1
40
+ },
41
+ "skills": {
42
+ "oneOf": [
43
+ { "type": "string" },
44
+ { "type": "array", "items": { "type": "string" } }
45
+ ]
46
+ },
47
+ "memory": {
48
+ "type": "string",
49
+ "enum": ["user", "project", "local"]
50
+ },
51
+ "background": {
52
+ "type": "boolean"
53
+ },
54
+ "isolation": {
55
+ "type": "string",
56
+ "enum": ["worktree"]
57
+ },
58
+ "color": {
59
+ "type": "string",
60
+ "enum": ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan"]
61
+ },
62
+ "initialPrompt": {
63
+ "type": "string"
64
+ }
65
+ }
66
+ }
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "mode": {
15
15
  "type": "string",
16
- "enum": ["sketch", "fast", "standard", "enterprise", "autonomous"],
16
+ "enum": ["fast", "standard", "enterprise"],
17
17
  "description": "Default workflow depth for this project"
18
18
  },
19
19
  "execution": {
@@ -35,6 +35,11 @@
35
35
  "minimum": 1,
36
36
  "default": 8,
37
37
  "description": "Task count above which subagent strategy is preferred"
38
+ },
39
+ "wave_fail_policy": {
40
+ "type": "string",
41
+ "enum": ["continue-on-single", "stop-on-any"],
42
+ "default": "continue-on-single"
38
43
  }
39
44
  }
40
45
  },
@@ -44,16 +49,16 @@
44
49
  "properties": {
45
50
  "always_on": {
46
51
  "type": "array",
47
- "items": { "type": "string" },
52
+ "items": { "$ref": "#/definitions/gateName" },
48
53
  "default": ["karpathy-gate", "verification-gate"]
49
54
  },
50
55
  "standard_mode": {
51
56
  "type": "array",
52
- "items": { "type": "string" }
57
+ "items": { "$ref": "#/definitions/gateName" }
53
58
  },
54
59
  "enterprise_mode": {
55
60
  "type": "array",
56
- "items": { "type": "string" }
61
+ "items": { "$ref": "#/definitions/gateName" }
57
62
  }
58
63
  }
59
64
  },
@@ -96,5 +101,20 @@
96
101
  "type": "string",
97
102
  "format": "date"
98
103
  }
104
+ },
105
+ "definitions": {
106
+ "gateName": {
107
+ "type": "string",
108
+ "enum": [
109
+ "karpathy-gate",
110
+ "verification-gate",
111
+ "tdd-gate",
112
+ "coverage-audit-gate",
113
+ "adversarial-review-gate",
114
+ "edge-case-gate",
115
+ "security-gate",
116
+ "devex-gate"
117
+ ]
118
+ }
99
119
  }
100
120
  }