@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +19 -2
- package/README.md +15 -8
- package/README.zh.md +5 -3
- package/agent-preamble/preamble.md +33 -0
- package/agents/flow-adversary.md +1 -1
- package/agents/flow-architect.md +2 -1
- package/agents/flow-brownfield-analyst.md +153 -0
- package/agents/flow-debugger.md +6 -11
- package/agents/flow-edge-hunter.md +1 -1
- package/agents/flow-executor.md +30 -8
- package/agents/flow-planner.md +38 -5
- package/agents/flow-product-designer.md +2 -1
- package/agents/flow-qa-engineer.md +9 -5
- package/agents/flow-researcher.md +2 -1
- package/agents/flow-reviewer.md +23 -5
- package/agents/flow-security-auditor.md +5 -3
- package/agents/flow-triage-analyst.md +5 -24
- package/agents/flow-ui-researcher.md +4 -3
- package/agents/flow-ux-designer.md +12 -39
- package/agents/flow-verifier.md +35 -3
- package/cli/README.md +3 -1
- package/cli/doctor-workflow.js +165 -2
- package/cli/doctor.js +8 -0
- package/cli/help.js +2 -0
- package/cli/lib/doctor-claude-settings.js +736 -0
- package/cli/lib/doctor-report.js +256 -1
- package/cli/lib/doctor-runtime-environment.js +196 -0
- package/cli/lib/frontmatter.js +44 -0
- package/cli/lib/json-schema.js +57 -0
- package/cli/lib/runtime.js +20 -2
- package/cli/lib/semver.js +14 -0
- package/cli/uninstall-actions.js +323 -0
- package/cli/uninstall.js +9 -253
- package/cli/utils.js +6 -1
- package/gates/adversarial-review-gate.md +1 -1
- package/gates/security-gate.md +2 -2
- package/gates/test-quality-gate.md +59 -0
- package/hooks/hooks.json +16 -2
- package/hooks/scripts/common.sh +4 -0
- package/hooks/scripts/session-start.sh +17 -2
- package/hooks/scripts/stop-watcher.sh +69 -18
- package/hooks/scripts/subagent-artifact-guard.sh +159 -0
- package/hooks/scripts/subagent-statusline.sh +105 -0
- package/knowledge/atomic-commits.md +1 -1
- package/knowledge/claude-code-runtime-contracts.md +203 -0
- package/knowledge/epic-decomposition.md +1 -1
- package/knowledge/execution-strategies.md +23 -1
- package/knowledge/planning-reviews.md +2 -2
- package/knowledge/poc-first-workflow.md +8 -8
- package/knowledge/review-feedback-intake.md +57 -0
- package/knowledge/two-stage-review.md +19 -6
- package/knowledge/wave-execution.md +16 -1
- package/output-styles/curdx-evidence-first.md +34 -0
- package/package.json +7 -1
- package/schemas/agent-frontmatter.schema.json +0 -7
- package/schemas/config.schema.json +14 -0
- package/schemas/hooks.schema.json +34 -2
- package/schemas/output-style-frontmatter.schema.json +22 -0
- package/schemas/plugin-manifest.schema.json +387 -17
- package/schemas/plugin-settings.schema.json +29 -0
- package/schemas/skill-frontmatter.schema.json +109 -4
- package/schemas/spec-state.schema.json +29 -4
- package/settings.json +6 -0
- package/skills/brownfield-index/SKILL.md +31 -35
- package/skills/browser-qa/SKILL.md +11 -3
- package/skills/cancel/SKILL.md +82 -0
- package/skills/debug/SKILL.md +6 -2
- package/skills/epic/SKILL.md +5 -3
- package/skills/fast/SKILL.md +1 -0
- package/skills/help/SKILL.md +17 -7
- package/skills/implement/SKILL.md +38 -7
- package/skills/init/SKILL.md +2 -1
- package/skills/review/SKILL.md +4 -1
- package/skills/security-audit/SKILL.md +17 -3
- package/skills/spec/SKILL.md +2 -1
- package/skills/start/SKILL.md +18 -18
- package/skills/status/SKILL.md +85 -0
- package/skills/ui-sketch/SKILL.md +11 -3
- package/skills/verify/SKILL.md +13 -1
- package/templates/config.json.tmpl +4 -1
- package/templates/progress.md.tmpl +19 -0
- 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",
|
package/hooks/scripts/common.sh
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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
|
|