@curdx/flow 2.2.0 → 2.2.4

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 (83) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +19 -2
  3. package/README.md +15 -8
  4. package/README.zh.md +5 -3
  5. package/agent-preamble/preamble.md +33 -0
  6. package/agents/flow-adversary.md +1 -1
  7. package/agents/flow-architect.md +2 -1
  8. package/agents/flow-brownfield-analyst.md +153 -0
  9. package/agents/flow-debugger.md +6 -11
  10. package/agents/flow-edge-hunter.md +1 -1
  11. package/agents/flow-executor.md +30 -8
  12. package/agents/flow-planner.md +38 -5
  13. package/agents/flow-product-designer.md +2 -1
  14. package/agents/flow-qa-engineer.md +9 -5
  15. package/agents/flow-researcher.md +2 -1
  16. package/agents/flow-reviewer.md +23 -5
  17. package/agents/flow-security-auditor.md +5 -3
  18. package/agents/flow-triage-analyst.md +5 -24
  19. package/agents/flow-ui-researcher.md +4 -3
  20. package/agents/flow-ux-designer.md +12 -39
  21. package/agents/flow-verifier.md +35 -3
  22. package/cli/README.md +3 -1
  23. package/cli/doctor-workflow.js +165 -2
  24. package/cli/doctor.js +8 -0
  25. package/cli/help.js +2 -0
  26. package/cli/lib/doctor-claude-settings.js +736 -0
  27. package/cli/lib/doctor-report.js +256 -1
  28. package/cli/lib/doctor-runtime-environment.js +196 -0
  29. package/cli/lib/frontmatter.js +44 -0
  30. package/cli/lib/json-schema.js +57 -0
  31. package/cli/lib/runtime.js +20 -2
  32. package/cli/lib/semver.js +14 -0
  33. package/cli/uninstall-actions.js +323 -0
  34. package/cli/uninstall.js +9 -253
  35. package/cli/utils.js +6 -1
  36. package/gates/adversarial-review-gate.md +1 -1
  37. package/gates/security-gate.md +2 -2
  38. package/gates/test-quality-gate.md +59 -0
  39. package/hooks/hooks.json +16 -2
  40. package/hooks/scripts/common.sh +4 -0
  41. package/hooks/scripts/session-start.sh +17 -2
  42. package/hooks/scripts/stop-watcher.sh +69 -18
  43. package/hooks/scripts/subagent-artifact-guard.sh +159 -0
  44. package/hooks/scripts/subagent-statusline.sh +105 -0
  45. package/knowledge/atomic-commits.md +1 -1
  46. package/knowledge/claude-code-runtime-contracts.md +203 -0
  47. package/knowledge/epic-decomposition.md +1 -1
  48. package/knowledge/execution-strategies.md +23 -1
  49. package/knowledge/planning-reviews.md +2 -2
  50. package/knowledge/poc-first-workflow.md +8 -8
  51. package/knowledge/review-feedback-intake.md +57 -0
  52. package/knowledge/two-stage-review.md +19 -6
  53. package/knowledge/wave-execution.md +16 -1
  54. package/output-styles/curdx-evidence-first.md +34 -0
  55. package/package.json +7 -1
  56. package/schemas/agent-frontmatter.schema.json +0 -7
  57. package/schemas/config.schema.json +14 -0
  58. package/schemas/hooks.schema.json +34 -2
  59. package/schemas/output-style-frontmatter.schema.json +22 -0
  60. package/schemas/plugin-manifest.schema.json +387 -17
  61. package/schemas/plugin-settings.schema.json +29 -0
  62. package/schemas/skill-frontmatter.schema.json +109 -4
  63. package/schemas/spec-state.schema.json +29 -4
  64. package/settings.json +6 -0
  65. package/skills/brownfield-index/SKILL.md +31 -35
  66. package/skills/browser-qa/SKILL.md +11 -3
  67. package/skills/cancel/SKILL.md +82 -0
  68. package/skills/debug/SKILL.md +6 -2
  69. package/skills/epic/SKILL.md +5 -3
  70. package/skills/fast/SKILL.md +1 -0
  71. package/skills/help/SKILL.md +17 -7
  72. package/skills/implement/SKILL.md +38 -7
  73. package/skills/init/SKILL.md +2 -1
  74. package/skills/review/SKILL.md +4 -1
  75. package/skills/security-audit/SKILL.md +17 -3
  76. package/skills/spec/SKILL.md +2 -1
  77. package/skills/start/SKILL.md +18 -18
  78. package/skills/status/SKILL.md +85 -0
  79. package/skills/ui-sketch/SKILL.md +11 -3
  80. package/skills/verify/SKILL.md +13 -1
  81. package/templates/config.json.tmpl +4 -1
  82. package/templates/progress.md.tmpl +19 -0
  83. package/templates/tasks.md.tmpl +26 -3
package/hooks/hooks.json CHANGED
@@ -5,7 +5,8 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.sh"
8
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.sh",
9
+ "statusMessage": "Loading CurDX-Flow project context"
9
10
  }
10
11
  ]
11
12
  },
@@ -14,7 +15,8 @@
14
15
  "hooks": [
15
16
  {
16
17
  "type": "command",
17
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/inject-karpathy.sh"
18
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/inject-karpathy.sh",
19
+ "statusMessage": "Injecting CurDX-Flow engineering baseline"
18
20
  }
19
21
  ]
20
22
  }
@@ -29,6 +31,18 @@
29
31
  ]
30
32
  }
31
33
  ],
34
+ "SubagentStop": [
35
+ {
36
+ "matcher": "flow-(architect|brownfield-analyst|debugger|edge-hunter|executor|product-designer|planner|qa-engineer|researcher|reviewer|security-auditor|triage-analyst|ui-researcher|ux-designer|verifier|adversary)",
37
+ "hooks": [
38
+ {
39
+ "type": "command",
40
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/subagent-artifact-guard.sh",
41
+ "statusMessage": "Checking curdx-flow artifact landing"
42
+ }
43
+ ]
44
+ }
45
+ ],
32
46
  "PreToolUse": [
33
47
  {
34
48
  "matcher": "AskUserQuestion",
@@ -33,3 +33,7 @@ emit_stop_block() {
33
33
  local reason="${1:-}"
34
34
  printf '{"decision":"block","reason":%s}\n' "$(json_escape "$reason")"
35
35
  }
36
+
37
+ emit_subagentstop_block() {
38
+ emit_stop_block "${1:-}"
39
+ }
@@ -3,6 +3,7 @@
3
3
  # Duties:
4
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
+ # 3. Persist stable CurDX-Flow environment hints for this session
6
7
  #
7
8
  # Design notes:
8
9
  # - Idempotent: marker file tracks last check date
@@ -45,12 +46,15 @@ if [ "$LAST_CHECK" != "$TODAY" ]; then
45
46
  ADDITIONAL_CONTEXT+="## CurDX-Flow Recommended Plugins Check\n\nThe following recommended plugins were not detected: **${JOINED}**\n\nRun \`npx @curdx/flow install --all\` for interactive one-shot install. Run \`npx @curdx/flow doctor\` for the full health report.\n\n"
46
47
  fi
47
48
 
48
- echo "$TODAY" > "$MARKER" 2>/dev/null || true
49
+ { echo "$TODAY" > "$MARKER"; } 2>/dev/null || true
49
50
  fi
50
51
 
51
52
  # ---------- 2. Load .flow/ state (if project is a flow project) ----------
52
53
  if [ -d ".flow" ]; then
53
54
  ADDITIONAL_CONTEXT+="## CurDX-Flow Project Active\n\n"
55
+ ADDITIONAL_CONTEXT+="- Plugin root: \`${CLAUDE_PLUGIN_ROOT:-unknown}\`\n"
56
+ ADDITIONAL_CONTEXT+="- Plugin data: \`${CLAUDE_PLUGIN_DATA:-$DATA_DIR}\`\n"
57
+ ADDITIONAL_CONTEXT+="- Best practice: write long agent artifacts to disk first; keep final assistant summaries short.\n\n"
54
58
 
55
59
  if [ -f ".flow/PROJECT.md" ]; then
56
60
  ADDITIONAL_CONTEXT+="### Project Vision\n$(head -80 .flow/PROJECT.md)\n\n"
@@ -67,7 +71,18 @@ if [ -d ".flow" ]; then
67
71
  fi
68
72
  fi
69
73
 
70
- # ---------- 3. Emit hook output ----------
74
+ # ---------- 3. Persist session environment hints ----------
75
+ if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
76
+ {
77
+ printf 'export CURDX_FLOW_PLUGIN_ROOT=%s\n' "$(json_escape "${CLAUDE_PLUGIN_ROOT:-}")"
78
+ printf 'export CURDX_FLOW_PLUGIN_DATA=%s\n' "$(json_escape "${CLAUDE_PLUGIN_DATA:-$DATA_DIR}")"
79
+ if [ -f ".flow/.active-spec" ]; then
80
+ printf 'export CURDX_FLOW_ACTIVE_SPEC=%s\n' "$(json_escape "$(cat .flow/.active-spec 2>/dev/null)")"
81
+ fi
82
+ } >> "$CLAUDE_ENV_FILE" 2>/dev/null || true
83
+ fi
84
+
85
+ # ---------- 4. Emit hook output ----------
71
86
  if [ -n "$ADDITIONAL_CONTEXT" ]; then
72
87
  emit_session_start_context "$ADDITIONAL_CONTEXT"
73
88
  fi
@@ -57,7 +57,7 @@ fi
57
57
  # the stop-hook strategy never activated.
58
58
  export STATE_FILE
59
59
 
60
- read STRATEGY PHASE TASK_INDEX TOTAL_TASKS FAILED ROUNDS <<EOF
60
+ read STRATEGY PHASE TASK_INDEX TOTAL_TASKS FAILED ROUNDS RECOVERY_MODE MAX_FIX_TASKS <<EOF
61
61
  $(python3 <<'PY'
62
62
  import json, os, sys
63
63
  p = os.environ.get("STATE_FILE")
@@ -72,7 +72,9 @@ ti = ex.get("task_index", 0)
72
72
  tt = ex.get("total_tasks", 0)
73
73
  failed = ex.get("failed_attempts", 0)
74
74
  rounds = ex.get("global_iteration", 0)
75
- print(strategy, phase, ti, tt, failed, rounds)
75
+ recovery_mode = ex.get("recovery_mode", "manual")
76
+ max_fix_tasks = ex.get("max_fix_tasks_per_original", 2)
77
+ print(strategy, phase, ti, tt, failed, rounds, recovery_mode, max_fix_tasks)
76
78
  PY
77
79
  )
78
80
  EOF
@@ -81,7 +83,7 @@ EOF
81
83
  [ "$STRATEGY" != "stop-hook" ] && allow_stop
82
84
  [ "$PHASE" != "execute" ] && allow_stop
83
85
 
84
- # ---------- 5. Check for completion signal in transcript ----------
86
+ # ---------- 5. Check hook input + completion signal in transcript ----------
85
87
  # Claude Code passes transcript path via stdin as JSON: {"transcript_path": "/path/..."}
86
88
  # We read stdin to detect ALL_TASKS_COMPLETE or TASK_FAILED
87
89
  INPUT=$(cat 2>/dev/null || echo "{}")
@@ -89,6 +91,19 @@ TRANSCRIPT_PATH=$(echo "$INPUT" | python3 -c 'import json,sys;
89
91
  try: print(json.load(sys.stdin).get("transcript_path",""))
90
92
  except: print("")' 2>/dev/null)
91
93
 
94
+ STOP_HOOK_ACTIVE=$(echo "$INPUT" | python3 -c 'import json,sys;
95
+ try: print("true" if json.load(sys.stdin).get("stop_hook_active", False) else "false")
96
+ except: print("false")' 2>/dev/null)
97
+
98
+ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
99
+ # Claude Code sets stop_hook_active during a stop-hook continuation.
100
+ # Treat it as context only: the final decision still comes from transcript
101
+ # signals, state-file progress, and tasks.md parity. Unconditionally allowing
102
+ # stop here can terminate an in-flight stop-hook loop after the first
103
+ # continuation, leaving remaining tasks stranded.
104
+ echo "[CurDX-Flow stop-hook] stop_hook_active=true; evaluating transcript/state before deciding" >&2
105
+ fi
106
+
92
107
  TRANSCRIPT_TAIL=""
93
108
  if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
94
109
  # Read last 50KB only (efficiency)
@@ -100,22 +115,54 @@ fi
100
115
  # python source text. Previously a spec name containing single quotes or
101
116
  # $-signs could break the script or inject arbitrary code.
102
117
 
103
- # Check for explicit completion signals
104
- if echo "$TRANSCRIPT_TAIL" | grep -q "ALL_TASKS_COMPLETE"; then
105
- # Cleanup: mark phase completed
118
+ # Helper: count unchecked tasks in tasks.md. If tasks.md is absent, return 0
119
+ # to avoid blocking recovery for partially-initialized specs.
120
+ unchecked_task_count() {
121
+ local tasks_file="$SPEC_DIR/tasks.md"
122
+ [ ! -f "$tasks_file" ] && { echo 0; return; }
123
+ grep -Ec '^- \[ \] \*\*[0-9]+(\.[0-9]+|\.VF|\.X|\.X\+1)*\*\*' "$tasks_file" 2>/dev/null || echo 0
124
+ }
125
+
126
+ last_task_signal() {
127
+ local msg="${1:-}"
128
+ printf '%s' "$msg" \
129
+ | grep -Eo 'ALL_TASKS_COMPLETE|TASK_(COMPLETE|FAILED):[[:space:]]*[0-9]+(\.([0-9]+|VF|X(\+[0-9]+)?))*' \
130
+ | tail -1
131
+ }
132
+
133
+ failed_task_id() {
134
+ local msg="${1:-}"
135
+ printf '%s' "$msg" | sed -nE 's/.*TASK_FAILED:[[:space:]]*([0-9]+(\.([0-9]+|VF|X(\+[0-9]+)?))*).*/\1/p' | tail -1
136
+ }
137
+
138
+ mark_execute_complete() {
106
139
  python3 <<'PY' 2>/dev/null
107
140
  import json, os
108
141
  p = os.environ["STATE_FILE"]
109
142
  s = json.load(open(p))
110
143
  s.setdefault("phase_status", {})["execute"] = "completed"
111
- s["phase"] = "verify" # move to verify phase
144
+ s["phase"] = "verify"
112
145
  json.dump(s, open(p, "w"), indent=2, ensure_ascii=False)
113
146
  PY
147
+ }
148
+
149
+ # Check for explicit completion signals
150
+ LAST_TASK_SIGNAL="$(last_task_signal "$TRANSCRIPT_TAIL")"
151
+
152
+ if [ "$LAST_TASK_SIGNAL" = "ALL_TASKS_COMPLETE" ]; then
153
+ UNCHECKED="$(unchecked_task_count)"
154
+ if [ "${UNCHECKED:-0}" -gt 0 ]; then
155
+ block_continue "[CurDX-Flow stop-hook] ALL_TASKS_COMPLETE was emitted, but tasks.md still has ${UNCHECKED} unchecked task(s). Read .flow/specs/${ACTIVE}/tasks.md, complete only the remaining unchecked tasks, update tasks.md, then emit ALL_TASKS_COMPLETE again."
156
+ fi
157
+ mark_execute_complete
114
158
  allow_stop
115
159
  fi
116
160
 
117
- # Check for fail signal (accumulate; actual stop decision below)
118
- if echo "$TRANSCRIPT_TAIL" | grep -q "TASK_FAILED"; then
161
+ # Check for the latest fail signal (accumulate; actual stop decision below)
162
+ if printf '%s' "$LAST_TASK_SIGNAL" | grep -q "^TASK_FAILED"; then
163
+ FAILED_TASK="$(failed_task_id "$LAST_TASK_SIGNAL")"
164
+ [ -z "$FAILED_TASK" ] && FAILED_TASK="the current task"
165
+
119
166
  # Increment failed_attempts
120
167
  python3 <<'PY' 2>/dev/null
121
168
  import json, os
@@ -127,6 +174,14 @@ json.dump(s, open(p, "w"), indent=2, ensure_ascii=False)
127
174
  PY
128
175
  # Re-read — again via os.environ, no shell interpolation into python.
129
176
  FAILED=$(python3 -c 'import json, os; print(json.load(open(os.environ["STATE_FILE"]))["execute_state"]["failed_attempts"])' 2>/dev/null || echo 0)
177
+
178
+ if [ "${FAILED:-0}" -lt 3 ]; then
179
+ if [ "${RECOVERY_MODE:-manual}" = "fix-task" ]; then
180
+ block_continue "[CurDX-Flow stop-hook] TASK_FAILED observed for ${FAILED_TASK}. Do not skip it. Recovery mode is fix-task: insert one targeted [FIX ${FAILED_TASK}] task immediately after the failed task in tasks.md (max ${MAX_FIX_TASKS:-2} fix task(s) per original), update .state.json execute_state.fix_task_map, then execute the fix task before retrying ${FAILED_TASK}. The fix task must include Do, Files, Done when, Verify, and Commit fields."
181
+ fi
182
+
183
+ block_continue "[CurDX-Flow stop-hook] TASK_FAILED observed for ${FAILED_TASK}. Do not advance past the failed task. Re-read tasks.md, perform root-cause analysis, retry the first unchecked task, and emit TASK_COMPLETE only after its Verify command passes. failed_attempts=${FAILED}/3."
184
+ fi
130
185
  fi
131
186
 
132
187
  # ---------- 6. Safety brakes ----------
@@ -142,15 +197,11 @@ fi
142
197
 
143
198
  # Check if all tasks done
144
199
  if [ "$TASK_INDEX" -ge "$TOTAL_TASKS" ] && [ "$TOTAL_TASKS" -gt 0 ]; then
145
- # Mark complete
146
- python3 <<'PY' 2>/dev/null
147
- import json, os
148
- p = os.environ["STATE_FILE"]
149
- s = json.load(open(p))
150
- s.setdefault("phase_status", {})["execute"] = "completed"
151
- s["phase"] = "verify"
152
- json.dump(s, open(p, "w"), indent=2, ensure_ascii=False)
153
- PY
200
+ UNCHECKED="$(unchecked_task_count)"
201
+ if [ "${UNCHECKED:-0}" -gt 0 ]; then
202
+ block_continue "[CurDX-Flow stop-hook] State says execute is complete (${TASK_INDEX}/${TOTAL_TASKS}), but tasks.md still has ${UNCHECKED} unchecked task(s). Continue with the first unchecked task; do not add new tasks."
203
+ fi
204
+ mark_execute_complete
154
205
  allow_stop
155
206
  fi
156
207
 
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # CurDX-Flow SubagentStop Hook
3
+ # Blocks successful subagent completion if the expected artifact never landed on disk.
4
+ #
5
+ # Why:
6
+ # - long markdown/report-writing agents can truncate near the end of a run
7
+ # - users then see a cheerful success summary but the actual file is missing or tiny
8
+ # - latest Claude Code hooks expose SubagentStop with agent_type + last_assistant_message,
9
+ # which lets us guard only "success-looking" exits while allowing genuine precondition failures
10
+
11
+ set -u
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ . "$SCRIPT_DIR/common.sh"
15
+
16
+ INPUT="$(cat 2>/dev/null || echo "{}")"
17
+
18
+ if ! has_python3; then
19
+ # Without JSON parsing, fail open rather than blocking subagents blindly.
20
+ exit 0
21
+ fi
22
+
23
+ export SUBAGENT_GUARD_INPUT="$INPUT"
24
+
25
+ AGENT_TYPE="$(python3 -c 'import json, os
26
+ try:
27
+ data = json.loads(os.environ["SUBAGENT_GUARD_INPUT"])
28
+ print(data.get("agent_type", ""))
29
+ except Exception:
30
+ print("")
31
+ ' 2>/dev/null)"
32
+
33
+ LAST_MESSAGE="$(python3 -c 'import json, os
34
+ try:
35
+ data = json.loads(os.environ["SUBAGENT_GUARD_INPUT"])
36
+ print((data.get("last_assistant_message") or "").strip())
37
+ except Exception:
38
+ print("")
39
+ ' 2>/dev/null)"
40
+
41
+ looks_like_success() {
42
+ local msg="${1:-}"
43
+ printf '%s' "$msg" | grep -Eq '(^✓| generated$| generated\n|Wrote |Review complete|Requirements done|Research complete|UI Sketch generation complete|Report:|Next:|TASK_COMPLETE|ALL_TASKS_COMPLETE)'
44
+ }
45
+
46
+ active_spec_path() {
47
+ local file_name="$1"
48
+
49
+ [ ! -d ".flow" ] && return 1
50
+
51
+ local active
52
+ active="$(cat .flow/.active-spec 2>/dev/null)"
53
+ [ -z "$active" ] && return 1
54
+
55
+ printf '.flow/specs/%s/%s\n' "$active" "$file_name"
56
+ }
57
+
58
+ completed_task_id() {
59
+ local msg="${1:-}"
60
+ printf '%s' "$msg" | sed -nE 's/.*TASK_COMPLETE:[[:space:]]*([0-9]+(\.([0-9]+|VF|X(\+[0-9]+)?))*).*/\1/p' | head -1
61
+ }
62
+
63
+ artifact_target=""
64
+ minimum_size=200
65
+
66
+ case "$AGENT_TYPE" in
67
+ flow-researcher)
68
+ artifact_target="$(active_spec_path research.md)" || exit 0
69
+ minimum_size=400
70
+ ;;
71
+ flow-product-designer)
72
+ artifact_target="$(active_spec_path requirements.md)" || exit 0
73
+ minimum_size=400
74
+ ;;
75
+ flow-architect)
76
+ artifact_target="$(active_spec_path design.md)" || exit 0
77
+ minimum_size=400
78
+ ;;
79
+ flow-planner)
80
+ artifact_target="$(active_spec_path tasks.md)" || exit 0
81
+ minimum_size=400
82
+ ;;
83
+ flow-executor)
84
+ artifact_target="$(active_spec_path tasks.md)" || exit 0
85
+ if printf '%s' "$LAST_MESSAGE" | grep -q 'ALL_TASKS_COMPLETE'; then
86
+ exit 0
87
+ fi
88
+ task_id="$(completed_task_id "$LAST_MESSAGE")"
89
+ [ -z "$task_id" ] && exit 0
90
+ if grep -Eq "^- \\[x\\] \\*\\*${task_id//./\\.}\\*\\*" "$artifact_target" 2>/dev/null; then
91
+ exit 0
92
+ fi
93
+ emit_subagentstop_block "[CurDX-Flow subagent-artifact-guard] flow-executor emitted TASK_COMPLETE: ${task_id}, but ${artifact_target} does not mark that task as [x]. Update tasks.md and the spec progress/state before stopping."
94
+ exit 0
95
+ ;;
96
+ flow-debugger)
97
+ artifact_target="$(active_spec_path debug-report.md)" || exit 0
98
+ minimum_size=250
99
+ ;;
100
+ flow-triage-analyst)
101
+ artifact_target="$(active_spec_path triage-report.md)" || exit 0
102
+ minimum_size=250
103
+ ;;
104
+ flow-ux-designer)
105
+ artifact_target="$(active_spec_path ui-sketch.md)" || exit 0
106
+ minimum_size=250
107
+ ;;
108
+ flow-reviewer)
109
+ artifact_target="$(active_spec_path review-report.md)" || exit 0
110
+ minimum_size=300
111
+ ;;
112
+ flow-verifier)
113
+ artifact_target="$(active_spec_path verification-report.md)" || exit 0
114
+ minimum_size=300
115
+ ;;
116
+ flow-security-auditor)
117
+ artifact_target="$(active_spec_path security-audit.md)" || exit 0
118
+ minimum_size=250
119
+ ;;
120
+ flow-qa-engineer)
121
+ artifact_target="$(active_spec_path qa-report.md)" || exit 0
122
+ minimum_size=250
123
+ ;;
124
+ flow-edge-hunter)
125
+ artifact_target="$(active_spec_path edge-cases.md)" || exit 0
126
+ minimum_size=250
127
+ ;;
128
+ flow-adversary)
129
+ artifact_target="$(active_spec_path adversarial-review.md)" || exit 0
130
+ minimum_size=250
131
+ ;;
132
+ flow-ui-researcher)
133
+ artifact_target="$(active_spec_path ui-research.md)" || exit 0
134
+ minimum_size=250
135
+ ;;
136
+ flow-brownfield-analyst)
137
+ artifact_target=".flow/codebase-index.md"
138
+ minimum_size=250
139
+ ;;
140
+ *)
141
+ exit 0
142
+ ;;
143
+ esac
144
+
145
+ if [ -f "$artifact_target" ]; then
146
+ size="$(wc -c < "$artifact_target" 2>/dev/null | tr -d ' ')"
147
+ if [ "${size:-0}" -ge "$minimum_size" ]; then
148
+ exit 0
149
+ fi
150
+ fi
151
+
152
+ if ! looks_like_success "$LAST_MESSAGE"; then
153
+ # The subagent appears to be stopping because of a real precondition failure,
154
+ # clarification request, or other non-success path. Let that response through.
155
+ exit 0
156
+ fi
157
+
158
+ emit_subagentstop_block "[CurDX-Flow subagent-artifact-guard] ${AGENT_TYPE} is stopping with a success summary, but ${artifact_target} is missing or too small. Write the full artifact to disk first, then respond with the minimal completion summary only."
159
+ exit 0
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # CurDX-Flow subagentStatusLine command.
3
+ # Reads the official subagent status-line JSON payload from stdin and emits
4
+ # one JSON line per CurDX-Flow subagent row that should be overridden.
5
+
6
+ set -u
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ . "$SCRIPT_DIR/common.sh"
10
+
11
+ INPUT="$(cat 2>/dev/null || echo "{}")"
12
+
13
+ if ! has_python3; then
14
+ exit 0
15
+ fi
16
+
17
+ export CURDX_SUBAGENT_STATUSLINE_INPUT="$INPUT"
18
+
19
+ python3 <<'PY'
20
+ import json
21
+ import os
22
+ from pathlib import Path
23
+
24
+ try:
25
+ data = json.loads(os.environ.get("CURDX_SUBAGENT_STATUSLINE_INPUT", "{}"))
26
+ except Exception:
27
+ raise SystemExit(0)
28
+
29
+ columns = data.get("columns")
30
+ try:
31
+ columns = int(columns)
32
+ except Exception:
33
+ columns = 100
34
+ columns = max(40, columns)
35
+
36
+
37
+ def flow_type(task):
38
+ raw = str(task.get("type") or task.get("name") or task.get("label") or "")
39
+ if raw.startswith("curdx-flow:"):
40
+ raw = raw.split(":", 1)[1]
41
+ return raw
42
+
43
+
44
+ def is_flow_task(task):
45
+ values = [
46
+ task.get("type"),
47
+ task.get("name"),
48
+ task.get("label"),
49
+ task.get("description"),
50
+ ]
51
+ return any(str(value or "").startswith(("flow-", "curdx-flow:flow-")) for value in values)
52
+
53
+
54
+ def active_spec(cwd):
55
+ if not cwd:
56
+ return ""
57
+ try:
58
+ value = (Path(cwd) / ".flow" / ".active-spec").read_text(encoding="utf-8").strip()
59
+ except Exception:
60
+ return ""
61
+ return value[:40]
62
+
63
+
64
+ def token_label(value):
65
+ try:
66
+ count = int(value)
67
+ except Exception:
68
+ return ""
69
+ if count >= 1000:
70
+ return f"{count / 1000:.1f}k tok"
71
+ return f"{count} tok"
72
+
73
+
74
+ def clamp(text):
75
+ if len(text) <= columns:
76
+ return text
77
+ if columns <= 4:
78
+ return text[:columns]
79
+ return text[: columns - 3] + "..."
80
+
81
+
82
+ for task in data.get("tasks") or []:
83
+ if not isinstance(task, dict) or not is_flow_task(task):
84
+ continue
85
+
86
+ task_id = task.get("id")
87
+ if not task_id:
88
+ continue
89
+
90
+ parts = ["[curdx-flow]", flow_type(task) or "flow-agent"]
91
+
92
+ status = str(task.get("status") or "").strip()
93
+ if status:
94
+ parts.append(status)
95
+
96
+ spec = active_spec(task.get("cwd"))
97
+ if spec:
98
+ parts.append(f"spec:{spec}")
99
+
100
+ tokens = token_label(task.get("tokenCount"))
101
+ if tokens:
102
+ parts.append(tokens)
103
+
104
+ print(json.dumps({"id": str(task_id), "content": clamp(" | ".join(parts))}, ensure_ascii=True))
105
+ PY
@@ -230,7 +230,7 @@ PR review reads each commit's message.
230
230
  - Good commit message → reviewer finishes in 5 minutes
231
231
  - Bad commit message → reviewer either rubber-stamps or blocks without reading
232
232
 
233
- CurdX-Flow's `/curdx-flow:ship` command (Phase 6) will turn atomic commits into a clean PR description. Poor commit quality yields poor PR descriptions.
233
+ CurdX-Flow's review handoff expects atomic commits plus verification/review reports. Poor commit quality yields poor PR descriptions and weak release evidence.
234
234
 
235
235
  ---
236
236