@curdx/flow 2.3.11 → 3.0.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -20
- package/CHANGELOG.md +55 -2
- package/README.md +69 -19
- package/agents/flow-adversary.md +1 -0
- package/agents/flow-architect.md +1 -0
- package/agents/flow-brownfield-analyst.md +1 -0
- package/agents/flow-edge-hunter.md +1 -0
- package/agents/flow-planner.md +1 -0
- package/agents/flow-researcher.md +1 -0
- package/agents/flow-reviewer.md +1 -0
- package/agents/flow-ui-researcher.md +1 -0
- package/agents/flow-verifier.md +1 -0
- package/bin/curdx-flow-state +104 -0
- package/hooks/hooks.json +72 -0
- package/hooks/scripts/common.sh +191 -0
- package/hooks/scripts/config-change-guard.sh +94 -0
- package/hooks/scripts/flow-context-watch.sh +94 -0
- package/hooks/scripts/quick-mode-guard.sh +4 -3
- package/hooks/scripts/session-start.sh +14 -10
- package/hooks/scripts/session-title.sh +87 -0
- package/hooks/scripts/stop-watcher.sh +4 -3
- package/hooks/scripts/subagent-artifact-guard.sh +7 -74
- package/hooks/scripts/subagent-statusline.sh +8 -2
- package/hooks/scripts/task-lifecycle-guard.sh +106 -0
- package/hooks/scripts/teammate-idle-guard.sh +83 -0
- package/knowledge/claude-code-runtime-contracts.md +21 -0
- package/monitors/scripts/flow-state-monitor.sh +8 -5
- package/output-styles/curdx-fast-mode.md +42 -0
- package/output-styles/curdx-spec-mode.md +46 -0
- package/package.json +5 -3
- package/schemas/agent-frontmatter.schema.json +4 -1
- package/schemas/spec-state.schema.json +18 -0
- package/settings.json +2 -1
- package/skills/implement/SKILL.md +8 -0
- package/skills/implement/references/linear-execution.md +11 -0
- package/skills/implement/references/native-task-sync.md +107 -0
- package/skills/implement/references/progress-contract.md +4 -0
- package/skills/implement/references/state-init.md +3 -0
- package/skills/implement/references/stop-hook-execution.md +19 -5
- package/skills/implement/references/subagent-execution.md +16 -2
- package/skills/implement/references/wave-execution.md +18 -0
- package/skills/status/references/gather-contract.md +3 -0
- package/skills/status/references/output-contract.md +1 -0
package/hooks/scripts/common.sh
CHANGED
|
@@ -4,6 +4,186 @@ has_python3() {
|
|
|
4
4
|
command -v python3 >/dev/null 2>&1
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
resolve_flow_root() {
|
|
8
|
+
local candidate="${1:-${PWD:-}}"
|
|
9
|
+
|
|
10
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}/.flow" ]; then
|
|
11
|
+
printf '%s\n' "${CLAUDE_PROJECT_DIR%/}"
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
[ -n "$candidate" ] || candidate="$(pwd 2>/dev/null || printf '.')"
|
|
16
|
+
|
|
17
|
+
while [ -n "$candidate" ]; do
|
|
18
|
+
if [ -d "$candidate/.flow" ]; then
|
|
19
|
+
printf '%s\n' "$candidate"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
[ "$candidate" = "/" ] && break
|
|
24
|
+
candidate="$(dirname "$candidate")"
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
curdx_active_spec_name() {
|
|
31
|
+
local flow_root="${1:-${FLOW_ROOT:-}}"
|
|
32
|
+
[ -n "$flow_root" ] || return 1
|
|
33
|
+
[ -f "$flow_root/.flow/.active-spec" ] || return 1
|
|
34
|
+
cat "$flow_root/.flow/.active-spec" 2>/dev/null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
curdx_active_spec_path() {
|
|
38
|
+
local flow_root="${1:-${FLOW_ROOT:-}}"
|
|
39
|
+
local file_name="${2:-}"
|
|
40
|
+
local active
|
|
41
|
+
|
|
42
|
+
[ -n "$flow_root" ] || return 1
|
|
43
|
+
[ -n "$file_name" ] || return 1
|
|
44
|
+
|
|
45
|
+
active="$(curdx_active_spec_name "$flow_root" 2>/dev/null || true)"
|
|
46
|
+
[ -n "$active" ] || return 1
|
|
47
|
+
|
|
48
|
+
printf '%s/.flow/specs/%s/%s\n' "$flow_root" "$active" "$file_name"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
curdx_latest_epic_artifact_path() {
|
|
52
|
+
local flow_root="${1:-${FLOW_ROOT:-}}"
|
|
53
|
+
|
|
54
|
+
[ -n "$flow_root" ] || return 1
|
|
55
|
+
[ -d "$flow_root/.flow/_epics" ] || return 1
|
|
56
|
+
has_python3 || return 1
|
|
57
|
+
|
|
58
|
+
export CURDX_EPIC_DIR="$flow_root/.flow/_epics"
|
|
59
|
+
python3 <<'PY' 2>/dev/null
|
|
60
|
+
import os
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
base = Path(os.environ["CURDX_EPIC_DIR"])
|
|
64
|
+
candidates = [path for path in base.glob("*/epic.md") if path.is_file()]
|
|
65
|
+
if not candidates:
|
|
66
|
+
raise SystemExit(1)
|
|
67
|
+
|
|
68
|
+
latest = max(candidates, key=lambda path: path.stat().st_mtime)
|
|
69
|
+
print(str(latest))
|
|
70
|
+
PY
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
curdx_epic_artifact_path_from_message() {
|
|
74
|
+
local flow_root="${1:-${FLOW_ROOT:-}}"
|
|
75
|
+
local msg="${2:-}"
|
|
76
|
+
|
|
77
|
+
[ -n "$flow_root" ] || return 1
|
|
78
|
+
has_python3 || return 1
|
|
79
|
+
|
|
80
|
+
export CURDX_EPIC_MSG="$msg"
|
|
81
|
+
export CURDX_FLOW_ROOT="$flow_root"
|
|
82
|
+
python3 <<'PY' 2>/dev/null
|
|
83
|
+
import os
|
|
84
|
+
import re
|
|
85
|
+
|
|
86
|
+
msg = os.environ.get("CURDX_EPIC_MSG", "")
|
|
87
|
+
match = re.search(r'(\.flow/_epics/[^/\s]+/epic\.md)\b', msg)
|
|
88
|
+
if not match:
|
|
89
|
+
raise SystemExit(1)
|
|
90
|
+
|
|
91
|
+
rel = match.group(1)
|
|
92
|
+
if rel.startswith(".flow/"):
|
|
93
|
+
rel = rel[len(".flow/"):]
|
|
94
|
+
|
|
95
|
+
print(os.path.join(os.environ["CURDX_FLOW_ROOT"], ".flow", rel))
|
|
96
|
+
PY
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
curdx_resolve_artifact_contract() {
|
|
100
|
+
local agent_type="${1:-}"
|
|
101
|
+
local last_message="${2:-}"
|
|
102
|
+
local flow_root="${3:-${FLOW_ROOT:-}}"
|
|
103
|
+
|
|
104
|
+
CURDX_ARTIFACT_TARGET=""
|
|
105
|
+
CURDX_ARTIFACT_MIN_SIZE=0
|
|
106
|
+
|
|
107
|
+
[ -n "$agent_type" ] || return 1
|
|
108
|
+
|
|
109
|
+
case "$agent_type" in
|
|
110
|
+
flow-researcher)
|
|
111
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" research.md)" || return 1
|
|
112
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
113
|
+
;;
|
|
114
|
+
flow-product-designer)
|
|
115
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" requirements.md)" || return 1
|
|
116
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
117
|
+
;;
|
|
118
|
+
flow-architect)
|
|
119
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" design.md)" || return 1
|
|
120
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
121
|
+
;;
|
|
122
|
+
flow-planner)
|
|
123
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" tasks.md)" || return 1
|
|
124
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
125
|
+
;;
|
|
126
|
+
flow-executor)
|
|
127
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" tasks.md)" || return 1
|
|
128
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
129
|
+
;;
|
|
130
|
+
flow-debugger)
|
|
131
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" debug-report.md)" || return 1
|
|
132
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
133
|
+
;;
|
|
134
|
+
flow-reviewer)
|
|
135
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" review-report.md)" || return 1
|
|
136
|
+
CURDX_ARTIFACT_MIN_SIZE=300
|
|
137
|
+
;;
|
|
138
|
+
flow-verifier)
|
|
139
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" verification-report.md)" || return 1
|
|
140
|
+
CURDX_ARTIFACT_MIN_SIZE=300
|
|
141
|
+
;;
|
|
142
|
+
flow-security-auditor)
|
|
143
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" security-audit.md)" || return 1
|
|
144
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
145
|
+
;;
|
|
146
|
+
flow-qa-engineer)
|
|
147
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" qa-report.md)" || return 1
|
|
148
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
149
|
+
;;
|
|
150
|
+
flow-edge-hunter)
|
|
151
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" edge-cases.md)" || return 1
|
|
152
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
153
|
+
;;
|
|
154
|
+
flow-adversary)
|
|
155
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" adversarial-review.md)" || return 1
|
|
156
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
157
|
+
;;
|
|
158
|
+
flow-ui-researcher)
|
|
159
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" ui-research.md)" || return 1
|
|
160
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
161
|
+
;;
|
|
162
|
+
flow-ux-designer)
|
|
163
|
+
CURDX_ARTIFACT_TARGET="$(curdx_active_spec_path "$flow_root" ui-sketch/index.html)" || return 1
|
|
164
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
165
|
+
;;
|
|
166
|
+
flow-triage-analyst)
|
|
167
|
+
CURDX_ARTIFACT_TARGET="$(curdx_epic_artifact_path_from_message "$flow_root" "$last_message" 2>/dev/null || true)"
|
|
168
|
+
if [ -z "$CURDX_ARTIFACT_TARGET" ]; then
|
|
169
|
+
CURDX_ARTIFACT_TARGET="$(curdx_latest_epic_artifact_path "$flow_root" 2>/dev/null || true)"
|
|
170
|
+
fi
|
|
171
|
+
[ -n "$CURDX_ARTIFACT_TARGET" ] || return 1
|
|
172
|
+
CURDX_ARTIFACT_MIN_SIZE=400
|
|
173
|
+
;;
|
|
174
|
+
flow-brownfield-analyst)
|
|
175
|
+
[ -n "$flow_root" ] || return 1
|
|
176
|
+
CURDX_ARTIFACT_TARGET="$flow_root/.flow/codebase-index.md"
|
|
177
|
+
CURDX_ARTIFACT_MIN_SIZE=250
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
return 1
|
|
181
|
+
;;
|
|
182
|
+
esac
|
|
183
|
+
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
7
187
|
env_flag_enabled() {
|
|
8
188
|
case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
|
|
9
189
|
1|true|yes|on) return 0 ;;
|
|
@@ -30,6 +210,12 @@ emit_session_start_context() {
|
|
|
30
210
|
"$(json_escape "$context")"
|
|
31
211
|
}
|
|
32
212
|
|
|
213
|
+
emit_userprompt_submit_title() {
|
|
214
|
+
local title="${1:-}"
|
|
215
|
+
printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","sessionTitle":%s}}\n' \
|
|
216
|
+
"$(json_escape "$title")"
|
|
217
|
+
}
|
|
218
|
+
|
|
33
219
|
emit_pretooluse_deny() {
|
|
34
220
|
local reason="${1:-}"
|
|
35
221
|
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' \
|
|
@@ -44,3 +230,8 @@ emit_stop_block() {
|
|
|
44
230
|
emit_subagentstop_block() {
|
|
45
231
|
emit_stop_block "${1:-}"
|
|
46
232
|
}
|
|
233
|
+
|
|
234
|
+
emit_configchange_block() {
|
|
235
|
+
local reason="${1:-}"
|
|
236
|
+
printf '{"decision":"block","reason":%s}\n' "$(json_escape "$reason")"
|
|
237
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# CurDX-Flow ConfigChange Hook
|
|
3
|
+
# Blocks mid-execute settings changes that would disable CurDX's runtime spine.
|
|
4
|
+
|
|
5
|
+
set -u
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
. "$SCRIPT_DIR/common.sh"
|
|
9
|
+
|
|
10
|
+
INPUT="$(cat 2>/dev/null || echo "{}")"
|
|
11
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
12
|
+
|
|
13
|
+
[ -n "$FLOW_ROOT" ] || exit 0
|
|
14
|
+
has_python3 || exit 0
|
|
15
|
+
|
|
16
|
+
export CURDX_CONFIG_CHANGE_INPUT="$INPUT"
|
|
17
|
+
|
|
18
|
+
SOURCE="$(python3 -c 'import json, os
|
|
19
|
+
try:
|
|
20
|
+
data = json.loads(os.environ["CURDX_CONFIG_CHANGE_INPUT"])
|
|
21
|
+
print(data.get("source", ""))
|
|
22
|
+
except Exception:
|
|
23
|
+
print("")
|
|
24
|
+
' 2>/dev/null)"
|
|
25
|
+
|
|
26
|
+
case "$SOURCE" in
|
|
27
|
+
project_settings|local_settings) ;;
|
|
28
|
+
*) exit 0 ;;
|
|
29
|
+
esac
|
|
30
|
+
|
|
31
|
+
FILE_PATH="$(python3 -c 'import json, os
|
|
32
|
+
try:
|
|
33
|
+
data = json.loads(os.environ["CURDX_CONFIG_CHANGE_INPUT"])
|
|
34
|
+
print(data.get("file_path", ""))
|
|
35
|
+
except Exception:
|
|
36
|
+
print("")
|
|
37
|
+
' 2>/dev/null)"
|
|
38
|
+
|
|
39
|
+
[ -n "$FILE_PATH" ] || exit 0
|
|
40
|
+
[ -f "$FILE_PATH" ] || exit 0
|
|
41
|
+
[ -f "$FLOW_ROOT/.flow/.active-spec" ] || exit 0
|
|
42
|
+
|
|
43
|
+
ACTIVE="$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null)"
|
|
44
|
+
[ -n "$ACTIVE" ] || exit 0
|
|
45
|
+
|
|
46
|
+
STATE_FILE="$FLOW_ROOT/.flow/specs/$ACTIVE/.state.json"
|
|
47
|
+
[ -f "$STATE_FILE" ] || exit 0
|
|
48
|
+
|
|
49
|
+
export STATE_FILE
|
|
50
|
+
PHASE="$(python3 -c 'import json, os
|
|
51
|
+
try:
|
|
52
|
+
state = json.load(open(os.environ["STATE_FILE"]))
|
|
53
|
+
print(state.get("phase", ""))
|
|
54
|
+
except Exception:
|
|
55
|
+
print("")
|
|
56
|
+
' 2>/dev/null)"
|
|
57
|
+
|
|
58
|
+
[ "$PHASE" = "execute" ] || exit 0
|
|
59
|
+
|
|
60
|
+
export CURDX_CONFIG_CHANGE_FILE="$FILE_PATH"
|
|
61
|
+
BLOCK_REASONS="$(python3 <<'PY' 2>/dev/null
|
|
62
|
+
import json
|
|
63
|
+
import os
|
|
64
|
+
|
|
65
|
+
file_path = os.environ["CURDX_CONFIG_CHANGE_FILE"]
|
|
66
|
+
try:
|
|
67
|
+
parsed = json.load(open(file_path))
|
|
68
|
+
except Exception:
|
|
69
|
+
raise SystemExit(0)
|
|
70
|
+
|
|
71
|
+
reasons = []
|
|
72
|
+
|
|
73
|
+
if parsed.get("disableAllHooks") is True:
|
|
74
|
+
reasons.append("disableAllHooks would disable CurDX-Flow stop/recovery hooks and status lines")
|
|
75
|
+
|
|
76
|
+
agent = parsed.get("agent")
|
|
77
|
+
if isinstance(agent, str):
|
|
78
|
+
agent = agent.strip()
|
|
79
|
+
if agent and agent != "flow-orchestrator":
|
|
80
|
+
reasons.append(f'agent would reroute the main thread through subagent "{agent}"')
|
|
81
|
+
|
|
82
|
+
enabled_plugins = parsed.get("enabledPlugins")
|
|
83
|
+
if isinstance(enabled_plugins, dict) and enabled_plugins.get("curdx-flow@curdx-flow-marketplace") is False:
|
|
84
|
+
reasons.append("enabledPlugins would disable curdx-flow@curdx-flow-marketplace for this project")
|
|
85
|
+
|
|
86
|
+
if reasons:
|
|
87
|
+
print(" | ".join(reasons))
|
|
88
|
+
PY
|
|
89
|
+
)"
|
|
90
|
+
|
|
91
|
+
[ -n "$BLOCK_REASONS" ] || exit 0
|
|
92
|
+
|
|
93
|
+
emit_configchange_block "[CurDX-Flow config-change-guard] Blocking ${SOURCE} update while spec '${ACTIVE}' is in execute: ${BLOCK_REASONS}. Finish or cancel the active implementation first, then reapply the settings change after execute ends."
|
|
94
|
+
exit 0
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
. "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
INPUT="$(cat 2>/dev/null || echo "{}")"
|
|
9
|
+
|
|
10
|
+
if ! has_python3; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
export CURDX_FLOW_CONTEXT_WATCH_INPUT="$INPUT"
|
|
15
|
+
|
|
16
|
+
read_json_field() {
|
|
17
|
+
local field="$1"
|
|
18
|
+
python3 - "$field" <<'PY' 2>/dev/null
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
field = sys.argv[1]
|
|
24
|
+
try:
|
|
25
|
+
data = json.loads(os.environ.get("CURDX_FLOW_CONTEXT_WATCH_INPUT", "{}"))
|
|
26
|
+
except Exception:
|
|
27
|
+
data = {}
|
|
28
|
+
|
|
29
|
+
value = data.get(field, "")
|
|
30
|
+
if value is None:
|
|
31
|
+
value = ""
|
|
32
|
+
print(value)
|
|
33
|
+
PY
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
HOOK_EVENT_NAME="$(read_json_field hook_event_name)"
|
|
37
|
+
NEW_CWD="$(read_json_field new_cwd)"
|
|
38
|
+
CURRENT_CWD="$(read_json_field cwd)"
|
|
39
|
+
|
|
40
|
+
START_DIR="$CURRENT_CWD"
|
|
41
|
+
if [ "$HOOK_EVENT_NAME" = "CwdChanged" ] && [ -n "$NEW_CWD" ]; then
|
|
42
|
+
START_DIR="$NEW_CWD"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
FLOW_ROOT="$(resolve_flow_root "$START_DIR" 2>/dev/null || true)"
|
|
46
|
+
ACTIVE_SPEC=""
|
|
47
|
+
SPEC_DIR=""
|
|
48
|
+
|
|
49
|
+
if [ -n "$FLOW_ROOT" ] && [ -f "$FLOW_ROOT/.flow/.active-spec" ]; then
|
|
50
|
+
ACTIVE_SPEC="$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null || true)"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [ -n "$FLOW_ROOT" ] && [ -n "$ACTIVE_SPEC" ]; then
|
|
54
|
+
SPEC_DIR="$FLOW_ROOT/.flow/specs/$ACTIVE_SPEC"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
|
|
58
|
+
{
|
|
59
|
+
printf 'export CURDX_FLOW_PROJECT_ROOT=%s\n' "$(json_escape "$FLOW_ROOT")"
|
|
60
|
+
printf 'export CURDX_FLOW_ACTIVE_SPEC=%s\n' "$(json_escape "$ACTIVE_SPEC")"
|
|
61
|
+
if [ -n "$SPEC_DIR" ]; then
|
|
62
|
+
printf 'export CURDX_FLOW_SPEC_DIR=%s\n' "$(json_escape "$SPEC_DIR")"
|
|
63
|
+
else
|
|
64
|
+
printf 'export CURDX_FLOW_SPEC_DIR=%s\n' "$(json_escape "")"
|
|
65
|
+
fi
|
|
66
|
+
} >> "$CLAUDE_ENV_FILE" 2>/dev/null || true
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ -z "$FLOW_ROOT" ]; then
|
|
70
|
+
printf '{"watchPaths":[]}\n'
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
export CURDX_FLOW_CONTEXT_WATCH_ROOT="$FLOW_ROOT"
|
|
75
|
+
export CURDX_FLOW_CONTEXT_WATCH_ACTIVE="$ACTIVE_SPEC"
|
|
76
|
+
|
|
77
|
+
python3 <<'PY' 2>/dev/null
|
|
78
|
+
import json
|
|
79
|
+
import os
|
|
80
|
+
from pathlib import Path
|
|
81
|
+
|
|
82
|
+
root = Path(os.environ["CURDX_FLOW_CONTEXT_WATCH_ROOT"])
|
|
83
|
+
active = os.environ.get("CURDX_FLOW_CONTEXT_WATCH_ACTIVE", "").strip()
|
|
84
|
+
|
|
85
|
+
watch_paths = [str(root / ".flow" / ".active-spec")]
|
|
86
|
+
if active:
|
|
87
|
+
spec_dir = root / ".flow" / "specs" / active
|
|
88
|
+
watch_paths.append(str(spec_dir / ".state.json"))
|
|
89
|
+
watch_paths.append(str(spec_dir / "tasks.md"))
|
|
90
|
+
|
|
91
|
+
print(json.dumps({"watchPaths": watch_paths}, ensure_ascii=True))
|
|
92
|
+
PY
|
|
93
|
+
|
|
94
|
+
exit 0
|
|
@@ -35,12 +35,13 @@ if [ "$TOOL_NAME" != "AskUserQuestion" ]; then
|
|
|
35
35
|
fi
|
|
36
36
|
|
|
37
37
|
# Check if we're in a flow project with quick mode enabled.
|
|
38
|
-
|
|
38
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
39
|
+
[ -n "$FLOW_ROOT" ] || exit 0
|
|
39
40
|
|
|
40
|
-
ACTIVE=$(cat
|
|
41
|
+
ACTIVE=$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null)
|
|
41
42
|
[ -z "$ACTIVE" ] && exit 0
|
|
42
43
|
|
|
43
|
-
STATE_FILE="
|
|
44
|
+
STATE_FILE="$FLOW_ROOT/.flow/specs/$ACTIVE/.state.json"
|
|
44
45
|
[ ! -f "$STATE_FILE" ] && exit 0
|
|
45
46
|
|
|
46
47
|
# Read quickMode. Pass STATE_FILE via env (NOT shell interpolation
|
|
@@ -19,6 +19,7 @@ DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.claude/plugins/data/curdx-flow}"
|
|
|
19
19
|
MARKER="$DATA_DIR/.deps-checked"
|
|
20
20
|
TODAY="$(date +%Y-%m-%d)"
|
|
21
21
|
ADDITIONAL_CONTEXT=""
|
|
22
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
22
23
|
|
|
23
24
|
mkdir -p "$DATA_DIR" 2>/dev/null || true
|
|
24
25
|
|
|
@@ -50,22 +51,22 @@ if env_flag_enabled "${CLAUDE_PLUGIN_OPTION_DAILY_DEPENDENCY_CHECK:-1}" && [ "$L
|
|
|
50
51
|
fi
|
|
51
52
|
|
|
52
53
|
# ---------- 2. Load .flow/ state (if project is a flow project) ----------
|
|
53
|
-
if [ -
|
|
54
|
+
if [ -n "$FLOW_ROOT" ]; then
|
|
54
55
|
ADDITIONAL_CONTEXT+="## CurDX-Flow Project Active\n\n"
|
|
55
56
|
ADDITIONAL_CONTEXT+="- Plugin root: \`${CLAUDE_PLUGIN_ROOT:-unknown}\`\n"
|
|
56
57
|
ADDITIONAL_CONTEXT+="- Plugin data: \`${CLAUDE_PLUGIN_DATA:-$DATA_DIR}\`\n"
|
|
57
58
|
ADDITIONAL_CONTEXT+="- Best practice: write long agent artifacts to disk first; keep final assistant summaries short.\n\n"
|
|
58
59
|
|
|
59
|
-
if [ -f "
|
|
60
|
-
ADDITIONAL_CONTEXT+="### Project Vision\n$(head -80
|
|
60
|
+
if [ -f "$FLOW_ROOT/.flow/PROJECT.md" ]; then
|
|
61
|
+
ADDITIONAL_CONTEXT+="### Project Vision\n$(head -80 "$FLOW_ROOT/.flow/PROJECT.md")\n\n"
|
|
61
62
|
fi
|
|
62
63
|
|
|
63
|
-
if [ -f "
|
|
64
|
-
ACTIVE="$(cat
|
|
65
|
-
if [ -n "$ACTIVE" ] && [ -d "
|
|
64
|
+
if [ -f "$FLOW_ROOT/.flow/.active-spec" ]; then
|
|
65
|
+
ACTIVE="$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null)"
|
|
66
|
+
if [ -n "$ACTIVE" ] && [ -d "$FLOW_ROOT/.flow/specs/$ACTIVE" ]; then
|
|
66
67
|
ADDITIONAL_CONTEXT+="### Active Spec: \`$ACTIVE\`\n\n"
|
|
67
|
-
if [ -f "
|
|
68
|
-
ADDITIONAL_CONTEXT+="$(head -40 "
|
|
68
|
+
if [ -f "$FLOW_ROOT/.flow/specs/$ACTIVE/.progress.md" ]; then
|
|
69
|
+
ADDITIONAL_CONTEXT+="$(head -40 "$FLOW_ROOT/.flow/specs/$ACTIVE/.progress.md")\n\n"
|
|
69
70
|
fi
|
|
70
71
|
fi
|
|
71
72
|
fi
|
|
@@ -76,8 +77,11 @@ if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
|
|
|
76
77
|
{
|
|
77
78
|
printf 'export CURDX_FLOW_PLUGIN_ROOT=%s\n' "$(json_escape "${CLAUDE_PLUGIN_ROOT:-}")"
|
|
78
79
|
printf 'export CURDX_FLOW_PLUGIN_DATA=%s\n' "$(json_escape "${CLAUDE_PLUGIN_DATA:-$DATA_DIR}")"
|
|
79
|
-
if [ -
|
|
80
|
-
printf 'export
|
|
80
|
+
if [ -n "$FLOW_ROOT" ]; then
|
|
81
|
+
printf 'export CURDX_FLOW_PROJECT_ROOT=%s\n' "$(json_escape "$FLOW_ROOT")"
|
|
82
|
+
fi
|
|
83
|
+
if [ -n "$FLOW_ROOT" ] && [ -f "$FLOW_ROOT/.flow/.active-spec" ]; then
|
|
84
|
+
printf 'export CURDX_FLOW_ACTIVE_SPEC=%s\n' "$(json_escape "$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null)")"
|
|
81
85
|
fi
|
|
82
86
|
} >> "$CLAUDE_ENV_FILE" 2>/dev/null || true
|
|
83
87
|
fi
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
. "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
INPUT="$(cat 2>/dev/null || echo "{}")"
|
|
9
|
+
DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.claude/plugins/data/curdx-flow}"
|
|
10
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
11
|
+
|
|
12
|
+
[ -n "$FLOW_ROOT" ] || exit 0
|
|
13
|
+
|
|
14
|
+
ACTIVE="$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null || true)"
|
|
15
|
+
[ -n "$ACTIVE" ] || exit 0
|
|
16
|
+
|
|
17
|
+
if ! has_python3; then
|
|
18
|
+
emit_userprompt_submit_title "curdx-flow/${ACTIVE}"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
export CURDX_FLOW_SESSION_TITLE_INPUT="$INPUT"
|
|
23
|
+
export CURDX_FLOW_SESSION_TITLE_ACTIVE="$ACTIVE"
|
|
24
|
+
export CURDX_FLOW_ROOT="$FLOW_ROOT"
|
|
25
|
+
|
|
26
|
+
TITLE_INFO="$(python3 <<'PY' 2>/dev/null
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
active = os.environ["CURDX_FLOW_SESSION_TITLE_ACTIVE"]
|
|
32
|
+
data = json.loads(os.environ.get("CURDX_FLOW_SESSION_TITLE_INPUT", "{}"))
|
|
33
|
+
session_id = data.get("session_id", "")
|
|
34
|
+
|
|
35
|
+
phase = ""
|
|
36
|
+
task_index = 0
|
|
37
|
+
total_tasks = 0
|
|
38
|
+
|
|
39
|
+
state_path = Path(os.environ["CURDX_FLOW_ROOT"]) / ".flow" / "specs" / active / ".state.json"
|
|
40
|
+
if state_path.exists():
|
|
41
|
+
try:
|
|
42
|
+
state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
43
|
+
phase = state.get("phase") or ""
|
|
44
|
+
execute_state = state.get("execute_state") or {}
|
|
45
|
+
task_index = int(execute_state.get("task_index") or 0)
|
|
46
|
+
total_tasks = int(execute_state.get("total_tasks") or 0)
|
|
47
|
+
except Exception:
|
|
48
|
+
phase = ""
|
|
49
|
+
task_index = 0
|
|
50
|
+
total_tasks = 0
|
|
51
|
+
|
|
52
|
+
label = phase
|
|
53
|
+
if phase == "execute" and total_tasks > 0:
|
|
54
|
+
label = f"{phase} {task_index}/{total_tasks}"
|
|
55
|
+
|
|
56
|
+
title = f"curdx-flow/{active}"
|
|
57
|
+
if label:
|
|
58
|
+
title = f"{title} [{label}]"
|
|
59
|
+
|
|
60
|
+
print(session_id)
|
|
61
|
+
print(title)
|
|
62
|
+
PY
|
|
63
|
+
)"
|
|
64
|
+
|
|
65
|
+
SESSION_ID="$(printf '%s\n' "$TITLE_INFO" | sed -n '1p')"
|
|
66
|
+
TITLE="$(printf '%s\n' "$TITLE_INFO" | sed -n '2p')"
|
|
67
|
+
[ -n "$TITLE" ] || exit 0
|
|
68
|
+
|
|
69
|
+
if [ -n "$SESSION_ID" ]; then
|
|
70
|
+
MARKER_DIR="$DATA_DIR/session-titles"
|
|
71
|
+
MARKER_FILE="$MARKER_DIR/$SESSION_ID"
|
|
72
|
+
LAST_TITLE=""
|
|
73
|
+
|
|
74
|
+
if [ -f "$MARKER_FILE" ]; then
|
|
75
|
+
LAST_TITLE="$(cat "$MARKER_FILE" 2>/dev/null || true)"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$LAST_TITLE" = "$TITLE" ]; then
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
mkdir -p "$MARKER_DIR" 2>/dev/null || true
|
|
83
|
+
printf '%s\n' "$TITLE" > "$MARKER_FILE" 2>/dev/null || true
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
emit_userprompt_submit_title "$TITLE"
|
|
87
|
+
exit 0
|
|
@@ -38,12 +38,13 @@ block_continue() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
# ---------- 1. Must be a flow project ----------
|
|
41
|
-
|
|
41
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
42
|
+
[ -n "$FLOW_ROOT" ] || allow_stop
|
|
42
43
|
|
|
43
44
|
# ---------- 2. Must have active spec ----------
|
|
44
|
-
ACTIVE=$(cat
|
|
45
|
+
ACTIVE=$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null)
|
|
45
46
|
[ -z "$ACTIVE" ] && allow_stop
|
|
46
|
-
SPEC_DIR="
|
|
47
|
+
SPEC_DIR="$FLOW_ROOT/.flow/specs/$ACTIVE"
|
|
47
48
|
[ ! -d "$SPEC_DIR" ] && allow_stop
|
|
48
49
|
|
|
49
50
|
STATE_FILE="$SPEC_DIR/.state.json"
|
|
@@ -14,6 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
14
14
|
. "$SCRIPT_DIR/common.sh"
|
|
15
15
|
|
|
16
16
|
INPUT="$(cat 2>/dev/null || echo "{}")"
|
|
17
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
17
18
|
|
|
18
19
|
if ! has_python3; then
|
|
19
20
|
# Without JSON parsing, fail open rather than blocking subagents blindly.
|
|
@@ -43,18 +44,6 @@ looks_like_success() {
|
|
|
43
44
|
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
|
}
|
|
45
46
|
|
|
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
47
|
completed_task_id() {
|
|
59
48
|
local msg="${1:-}"
|
|
60
49
|
printf '%s' "$msg" | sed -nE 's/.*TASK_COMPLETE:[[:space:]]*([0-9]+(\.([0-9]+|VF|X(\+[0-9]+)?))*).*/\1/p' | head -1
|
|
@@ -64,24 +53,10 @@ artifact_target=""
|
|
|
64
53
|
minimum_size=200
|
|
65
54
|
|
|
66
55
|
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
56
|
flow-executor)
|
|
84
|
-
|
|
57
|
+
curdx_resolve_artifact_contract "$AGENT_TYPE" "$LAST_MESSAGE" "$FLOW_ROOT" || exit 0
|
|
58
|
+
artifact_target="$CURDX_ARTIFACT_TARGET"
|
|
59
|
+
minimum_size="$CURDX_ARTIFACT_MIN_SIZE"
|
|
85
60
|
if printf '%s' "$LAST_MESSAGE" | grep -q 'ALL_TASKS_COMPLETE'; then
|
|
86
61
|
exit 0
|
|
87
62
|
fi
|
|
@@ -93,52 +68,10 @@ case "$AGENT_TYPE" in
|
|
|
93
68
|
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
69
|
exit 0
|
|
95
70
|
;;
|
|
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
71
|
*)
|
|
141
|
-
exit 0
|
|
72
|
+
curdx_resolve_artifact_contract "$AGENT_TYPE" "$LAST_MESSAGE" "$FLOW_ROOT" || exit 0
|
|
73
|
+
artifact_target="$CURDX_ARTIFACT_TARGET"
|
|
74
|
+
minimum_size="$CURDX_ARTIFACT_MIN_SIZE"
|
|
142
75
|
;;
|
|
143
76
|
esac
|
|
144
77
|
|