@curdx/flow 2.3.10 → 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/cli/README.md +6 -1
- package/cli/install-companions.js +1 -1
- package/cli/install-context7-config.js +5 -3
- package/cli/install-required-plugins.js +2 -2
- package/cli/uninstall-actions.js +37 -0
- package/cli/upgrade-workflow.js +1 -1
- package/cli/upgrade.js +42 -14
- 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
|
@@ -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
|
|
|
@@ -55,10 +55,16 @@ def active_spec(cwd):
|
|
|
55
55
|
if not cwd:
|
|
56
56
|
return ""
|
|
57
57
|
try:
|
|
58
|
-
|
|
58
|
+
current = Path(cwd).resolve()
|
|
59
59
|
except Exception:
|
|
60
60
|
return ""
|
|
61
|
-
|
|
61
|
+
for candidate in [current, *current.parents]:
|
|
62
|
+
try:
|
|
63
|
+
value = (candidate / ".flow" / ".active-spec").read_text(encoding="utf-8").strip()
|
|
64
|
+
except Exception:
|
|
65
|
+
continue
|
|
66
|
+
return value[:40]
|
|
67
|
+
return ""
|
|
62
68
|
|
|
63
69
|
|
|
64
70
|
def token_label(value):
|
|
@@ -0,0 +1,106 @@
|
|
|
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_TASK_GUARD_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
|
+
try:
|
|
24
|
+
data = json.loads(os.environ.get("CURDX_TASK_GUARD_INPUT", "{}"))
|
|
25
|
+
except Exception:
|
|
26
|
+
data = {}
|
|
27
|
+
|
|
28
|
+
field = sys.argv[1]
|
|
29
|
+
value = data.get(field, "")
|
|
30
|
+
if isinstance(value, str):
|
|
31
|
+
value = value.strip()
|
|
32
|
+
elif value is None:
|
|
33
|
+
value = ""
|
|
34
|
+
print(value)
|
|
35
|
+
PY
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
HOOK_EVENT_NAME="$(read_json_field hook_event_name)"
|
|
39
|
+
TASK_SUBJECT="$(read_json_field task_subject)"
|
|
40
|
+
TASK_DESCRIPTION="$(read_json_field task_description)"
|
|
41
|
+
|
|
42
|
+
FLOW_ROOT="$(resolve_flow_root 2>/dev/null || true)"
|
|
43
|
+
[ -n "$FLOW_ROOT" ] || exit 0
|
|
44
|
+
|
|
45
|
+
ACTIVE_SPEC="$(cat "$FLOW_ROOT/.flow/.active-spec" 2>/dev/null || true)"
|
|
46
|
+
[ -n "$ACTIVE_SPEC" ] || exit 0
|
|
47
|
+
|
|
48
|
+
TASKS_FILE="$FLOW_ROOT/.flow/specs/$ACTIVE_SPEC/tasks.md"
|
|
49
|
+
[ -f "$TASKS_FILE" ] || exit 0
|
|
50
|
+
|
|
51
|
+
extract_curdx_task_id() {
|
|
52
|
+
python3 <<'PY' 2>/dev/null
|
|
53
|
+
import os
|
|
54
|
+
import re
|
|
55
|
+
|
|
56
|
+
subject = os.environ.get("CURDX_TASK_GUARD_SUBJECT", "").strip()
|
|
57
|
+
patterns = [
|
|
58
|
+
r'^\[P\]\s+([0-9]+(?:\.([0-9]+|VF|X(\+[0-9]+)?))*)\b',
|
|
59
|
+
r'^\[VERIFY\]\s+([0-9]+(?:\.([0-9]+|VF|X(\+[0-9]+)?))*)\b',
|
|
60
|
+
r'^([0-9]+(?:\.([0-9]+|VF|X(\+[0-9]+)?))*)\b',
|
|
61
|
+
]
|
|
62
|
+
for pattern in patterns:
|
|
63
|
+
match = re.match(pattern, subject)
|
|
64
|
+
if match:
|
|
65
|
+
print(match.group(1))
|
|
66
|
+
break
|
|
67
|
+
PY
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export CURDX_TASK_GUARD_SUBJECT="$TASK_SUBJECT"
|
|
71
|
+
CURDX_TASK_ID="$(extract_curdx_task_id)"
|
|
72
|
+
[ -n "$CURDX_TASK_ID" ] || exit 0
|
|
73
|
+
|
|
74
|
+
task_exists() {
|
|
75
|
+
local escaped_id
|
|
76
|
+
escaped_id="$(printf '%s' "$1" | sed 's/[.[\*^$()+?{|]/\\&/g')"
|
|
77
|
+
grep -Eq "^- \\[[ xX]\\] \\*\\*${escaped_id}\\*\\*" "$TASKS_FILE" 2>/dev/null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
task_completed() {
|
|
81
|
+
local escaped_id
|
|
82
|
+
escaped_id="$(printf '%s' "$1" | sed 's/[.[\*^$()+?{|]/\\&/g')"
|
|
83
|
+
grep -Eq "^- \\[[xX]\\] \\*\\*${escaped_id}\\*\\*" "$TASKS_FILE" 2>/dev/null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ! task_exists "$CURDX_TASK_ID"; then
|
|
87
|
+
printf '%s\n' "[CurDX-Flow task-lifecycle-guard] Native task '${TASK_SUBJECT}' references CurDX task ${CURDX_TASK_ID}, but ${TASKS_FILE} does not contain that task id. Rebuild native tasks from tasks.md before proceeding." >&2
|
|
88
|
+
exit 2
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
case "$HOOK_EVENT_NAME" in
|
|
92
|
+
TaskCreated)
|
|
93
|
+
if [ -z "$TASK_DESCRIPTION" ]; then
|
|
94
|
+
printf '%s\n' "[CurDX-Flow task-lifecycle-guard] Native task '${TASK_SUBJECT}' is missing a description. Include a compact description derived from 'Done when' and 'Verify' so the task list mirrors tasks.md faithfully." >&2
|
|
95
|
+
exit 2
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
98
|
+
TaskCompleted)
|
|
99
|
+
if ! task_completed "$CURDX_TASK_ID"; then
|
|
100
|
+
printf '%s\n' "[CurDX-Flow task-lifecycle-guard] Native task '${TASK_SUBJECT}' cannot be marked completed before ${TASKS_FILE} marks ${CURDX_TASK_ID} as [x]. Update tasks.md and disk-backed state first, then complete the native task." >&2
|
|
101
|
+
exit 2
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
exit 0
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# CurDX-Flow TeammateIdle Hook
|
|
3
|
+
# Keeps artifact-producing CurDX teammates from idling before their disk artifact lands.
|
|
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_TEAMMATE_IDLE_INPUT="$INPUT"
|
|
17
|
+
|
|
18
|
+
read_json_field() {
|
|
19
|
+
local field="$1"
|
|
20
|
+
python3 - "$field" <<'PY' 2>/dev/null
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(os.environ.get("CURDX_TEAMMATE_IDLE_INPUT", "{}"))
|
|
27
|
+
except Exception:
|
|
28
|
+
data = {}
|
|
29
|
+
|
|
30
|
+
value = data.get(sys.argv[1], "")
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
value = value.strip()
|
|
33
|
+
elif value is None:
|
|
34
|
+
value = ""
|
|
35
|
+
print(value)
|
|
36
|
+
PY
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
TEAM_NAME="$(read_json_field team_name)"
|
|
40
|
+
TEAMMATE_NAME="$(read_json_field teammate_name)"
|
|
41
|
+
|
|
42
|
+
[ -n "$TEAM_NAME" ] || exit 0
|
|
43
|
+
[ -n "$TEAMMATE_NAME" ] || exit 0
|
|
44
|
+
|
|
45
|
+
TEAM_CONFIG="${HOME}/.claude/teams/${TEAM_NAME}/config.json"
|
|
46
|
+
[ -f "$TEAM_CONFIG" ] || exit 0
|
|
47
|
+
|
|
48
|
+
export CURDX_TEAM_CONFIG="$TEAM_CONFIG"
|
|
49
|
+
export CURDX_TEAMMATE_NAME="$TEAMMATE_NAME"
|
|
50
|
+
|
|
51
|
+
AGENT_TYPE="$(python3 <<'PY' 2>/dev/null
|
|
52
|
+
import json
|
|
53
|
+
import os
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
data = json.load(open(os.environ["CURDX_TEAM_CONFIG"]))
|
|
57
|
+
except Exception:
|
|
58
|
+
raise SystemExit(0)
|
|
59
|
+
|
|
60
|
+
name = os.environ["CURDX_TEAMMATE_NAME"]
|
|
61
|
+
for member in data.get("members", []) or []:
|
|
62
|
+
if isinstance(member, dict) and (member.get("name") or "").strip() == name:
|
|
63
|
+
print((member.get("agent_type") or "").strip())
|
|
64
|
+
break
|
|
65
|
+
PY
|
|
66
|
+
)"
|
|
67
|
+
|
|
68
|
+
[ -n "$AGENT_TYPE" ] || exit 0
|
|
69
|
+
|
|
70
|
+
curdx_resolve_artifact_contract "$AGENT_TYPE" "" "$FLOW_ROOT" || exit 0
|
|
71
|
+
|
|
72
|
+
artifact_target="$CURDX_ARTIFACT_TARGET"
|
|
73
|
+
minimum_size="$CURDX_ARTIFACT_MIN_SIZE"
|
|
74
|
+
|
|
75
|
+
if [ -f "$artifact_target" ]; then
|
|
76
|
+
size="$(wc -c < "$artifact_target" 2>/dev/null | tr -d ' ')"
|
|
77
|
+
if [ "${size:-0}" -ge "$minimum_size" ]; then
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
printf '%s\n' "[CurDX-Flow teammate-idle-guard] Teammate '${TEAMMATE_NAME}' (${AGENT_TYPE}) is about to go idle, but ${artifact_target} is missing or too small. Write the required CurDX artifact to disk before idling." >&2
|
|
83
|
+
exit 2
|
|
@@ -32,6 +32,11 @@ When a behavior is unclear, prefer the official docs and `claude plugin validate
|
|
|
32
32
|
- `PreToolUse` also supports `hookSpecificOutput.permissionDecision = "defer"` for deferred tool handling in `-p` / SDK-style flows; do not assume deny/allow are the only valid permission outcomes.
|
|
33
33
|
- `PermissionDenied` can return `{ "retry": true }` to let Claude try a different approach after an auto-mode classifier denial.
|
|
34
34
|
- Hooks must fail open when runtime prerequisites are missing (`python3`, malformed stdin JSON, absent `.flow/` state). The exception is an explicit, success-looking subagent completion with a missing required artifact.
|
|
35
|
+
- Hook and monitor scripts must not assume the current working directory is the repo root. Official `CwdChanged` exists, and users often work from nested package/app directories, so CurDX-Flow runtime scripts should prefer `CLAUDE_PROJECT_DIR` and otherwise walk upward until they find the project `.flow/` root.
|
|
36
|
+
- CurDX-Flow may use `CwdChanged` + `FileChanged` to maintain dynamic watch paths for `.flow/.active-spec`, the active spec `.state.json`, and `tasks.md`. Treat that watch layer as reactive context plumbing, not as a replacement for the monitor or disk-backed truth.
|
|
37
|
+
- `TaskCreated` / `TaskCompleted` can be used as native-task-sync guardrails, but only for CurDX-shaped task subjects. Never let those hooks break unrelated user task-list workflows that happen outside an active CurDX spec.
|
|
38
|
+
- `ConfigChange` can block project/local settings updates from taking effect in the running session. CurDX-Flow may use that to reject mid-execute changes that would disable hooks or reroute the main thread away from `flow-orchestrator`.
|
|
39
|
+
- `TeammateIdle` has less context than `SubagentStop`, so CurDX-Flow should resolve `teammate_name -> agent_type` through `~/.claude/teams/<team-name>/config.json` before enforcing artifact gates for team-mode workers.
|
|
35
40
|
|
|
36
41
|
## Subagent Artifact Discipline
|
|
37
42
|
|
|
@@ -52,10 +57,22 @@ Guarded artifact targets:
|
|
|
52
57
|
| `flow-edge-hunter` | `.flow/specs/<active>/edge-cases.md` |
|
|
53
58
|
| `flow-adversary` | `.flow/specs/<active>/adversarial-review.md` |
|
|
54
59
|
| `flow-ui-researcher` | `.flow/specs/<active>/ui-research.md` |
|
|
60
|
+
| `flow-ux-designer` | `.flow/specs/<active>/ui-sketch/index.html` |
|
|
61
|
+
| `flow-triage-analyst` | `.flow/_epics/<epic-name>/epic.md` |
|
|
55
62
|
| `flow-brownfield-analyst` | `.flow/codebase-index.md` |
|
|
56
63
|
|
|
57
64
|
`flow-executor` is marker-driven rather than report-driven: it must update task state and end with `TASK_COMPLETE: <task_id>` or `TASK_FAILED: <task_id>`.
|
|
58
65
|
|
|
66
|
+
## Background Subagent Policy
|
|
67
|
+
|
|
68
|
+
- Official background subagents keep the main conversation free while the worker runs, but any `AskUserQuestion` call inside that worker auto-denies instead of surfacing an interactive clarification prompt.
|
|
69
|
+
- CurDX-Flow should therefore reserve `background: true` for agents that are:
|
|
70
|
+
- artifact-producing or evidence-gathering
|
|
71
|
+
- long-running enough to justify concurrency
|
|
72
|
+
- not dependent on `AskUserQuestion` for normal operation
|
|
73
|
+
- Do not set `background: true` by default on `flow-executor`, `flow-debugger`, `flow-qa-engineer`, `flow-product-designer`, `flow-security-auditor`, `flow-triage-analyst`, or `flow-ux-designer` without a tighter clarification/permission contract.
|
|
74
|
+
- If those same agent definitions are reused as teammates, `TeammateIdle` quality gates should reuse the same disk-artifact contract as subagent completion whenever the agent is artifact-bearing.
|
|
75
|
+
|
|
59
76
|
## Agent Teams Compatibility
|
|
60
77
|
|
|
61
78
|
- Official `agent-teams` behavior differs from regular subagent invocation in one critical way: when a subagent definition runs as a teammate, its `skills` and `mcpServers` frontmatter fields are not applied.
|
|
@@ -69,6 +86,9 @@ Guarded artifact targets:
|
|
|
69
86
|
- Use forked context and a named agent only when the skill's work benefits from isolation or a specialized role.
|
|
70
87
|
- Avoid preloading broad tool access. Prefer the smallest useful tool set per skill/agent.
|
|
71
88
|
- Do not make bundled skills or agents implicitly depend on runtime-gated tools such as `SendMessage`, `TeamCreate`, `TeamDelete`, or `ToolSearch` unless CurDX-Flow also ships the matching feature-flag/setup contract.
|
|
89
|
+
- Interactive Claude sessions expose the Task tool family (`TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate`) while headless / SDK flows use `TodoWrite`. Any CurDX native task-list sync must therefore be optional UX, not a correctness dependency.
|
|
90
|
+
- Official interactive-mode docs also support `CLAUDE_CODE_TASK_LIST_ID` for sharing a native task list across sessions. CurDX-Flow may use that later as an optimization, but current execution must still recover correctly when the native task list changes or disappears.
|
|
91
|
+
- If CurDX uses task lifecycle hooks, `TaskCreated` should reject orphan CurDX-native tasks that do not map to `tasks.md`, and `TaskCompleted` should reject UI completion that happens before `tasks.md` is updated.
|
|
72
92
|
|
|
73
93
|
## Plugin Settings
|
|
74
94
|
|
|
@@ -85,6 +105,7 @@ Guarded artifact targets:
|
|
|
85
105
|
|
|
86
106
|
- CurDX-Flow ships a plugin monitor at `${CLAUDE_PLUGIN_ROOT}/monitors/monitors.json` to surface `.flow` state changes back into the active Claude session.
|
|
87
107
|
- Monitors run only when the Claude `Monitor` tool is available, and only in interactive CLI sessions.
|
|
108
|
+
- The monitor must keep working even when Claude's cwd moves below the repo root; `.flow` discovery should be project-root aware rather than cwd-fragile.
|
|
88
109
|
- CurDX-Flow `userConfig` values are exported to plugin subprocesses as `CLAUDE_PLUGIN_OPTION_<KEY>`.
|
|
89
110
|
- Current runtime knobs:
|
|
90
111
|
- `autonomous_blocking`: lets users disable stop-hook continuation without editing plugin files.
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
set -u
|
|
4
4
|
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
. "$SCRIPT_DIR/../../hooks/scripts/common.sh"
|
|
7
|
+
|
|
5
8
|
interval="${CLAUDE_PLUGIN_OPTION_MONITOR_INTERVAL_SECONDS:-8}"
|
|
6
9
|
case "$interval" in
|
|
7
10
|
''|*[!0-9]*)
|
|
@@ -13,15 +16,15 @@ if [ "$interval" -lt 3 ] 2>/dev/null; then
|
|
|
13
16
|
fi
|
|
14
17
|
|
|
15
18
|
build_snapshot() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
local flow_root=""
|
|
20
|
+
flow_root="$(resolve_flow_root 2>/dev/null || true)"
|
|
21
|
+
[ -n "$flow_root" ] || return 0
|
|
19
22
|
|
|
20
23
|
local active=""
|
|
21
|
-
active="$(cat
|
|
24
|
+
active="$(cat "$flow_root/.flow/.active-spec" 2>/dev/null || true)"
|
|
22
25
|
[ -z "$active" ] && return 0
|
|
23
26
|
|
|
24
|
-
local spec_dir="
|
|
27
|
+
local spec_dir="$flow_root/.flow/specs/$active"
|
|
25
28
|
[ ! -d "$spec_dir" ] && return 0
|
|
26
29
|
|
|
27
30
|
local state_file="$spec_dir/.state.json"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: CurdX Fast Mode
|
|
3
|
+
description: Low-ceremony style for /curdx-flow:fast and small surgical work. Skip preamble, ship the change, validate, stop.
|
|
4
|
+
keep-coding-instructions: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CurdX Fast Mode
|
|
8
|
+
|
|
9
|
+
You are still Claude Code. Keep the default coding workflow, safety rules,
|
|
10
|
+
tool usage behavior, and verification discipline. This style strips ceremony
|
|
11
|
+
for small, well-bounded tasks.
|
|
12
|
+
|
|
13
|
+
## When this style fits
|
|
14
|
+
|
|
15
|
+
Use during `/curdx-flow:fast` runs or any task that is obviously small,
|
|
16
|
+
surgical, low-ambiguity, and not worth a full spec workflow.
|
|
17
|
+
|
|
18
|
+
## Response priorities
|
|
19
|
+
|
|
20
|
+
1. State the change in one sentence before the first edit.
|
|
21
|
+
2. Make the smallest correct edit — do not refactor surrounding code, do
|
|
22
|
+
not introduce abstractions, do not "improve" unrelated lines.
|
|
23
|
+
3. Run the smallest verification that proves the change works (one test,
|
|
24
|
+
one command, one curl). State it explicitly.
|
|
25
|
+
4. End with a one-line status: `Validated` (with evidence), `Unvalidated`
|
|
26
|
+
(with reason), or `Blocked` (with the blocker).
|
|
27
|
+
5. If you find scope creep — a second bug, a desired refactor, an unclear
|
|
28
|
+
interface — stop and surface it instead of expanding silently.
|
|
29
|
+
|
|
30
|
+
## Hard rules
|
|
31
|
+
|
|
32
|
+
- No spec scaffolding, no phase narration, no "let me think step by step".
|
|
33
|
+
- No defensive validation, no fallback paths, no try/except for impossible
|
|
34
|
+
cases.
|
|
35
|
+
- No new files unless the change inherently requires one.
|
|
36
|
+
- No follow-on TODOs in the diff. Surface them in the reply, not the code.
|
|
37
|
+
|
|
38
|
+
## Format
|
|
39
|
+
|
|
40
|
+
- Plain prose with one diff or one command per paragraph.
|
|
41
|
+
- Bullets only when listing more than two parallel items.
|
|
42
|
+
- No section headers under three lines of content.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: CurdX Spec Mode
|
|
3
|
+
description: Spec-driven verbose style for multi-phase work. Lead with the active phase, cite artifact paths, and never claim done without an evidence block.
|
|
4
|
+
keep-coding-instructions: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CurdX Spec Mode
|
|
8
|
+
|
|
9
|
+
You are still Claude Code. Keep the default coding workflow, safety rules,
|
|
10
|
+
tool usage behavior, and verification discipline. This style adds spec-driven
|
|
11
|
+
discipline on top of the defaults.
|
|
12
|
+
|
|
13
|
+
## When this style fits
|
|
14
|
+
|
|
15
|
+
Use this style during multi-phase CurDX-Flow work — research / requirements /
|
|
16
|
+
design / tasks / execute / verify — when the user wants a clear paper trail
|
|
17
|
+
across phases.
|
|
18
|
+
|
|
19
|
+
## Response priorities
|
|
20
|
+
|
|
21
|
+
1. State the active spec and phase explicitly. If no spec is active, say so.
|
|
22
|
+
2. Lead with the artifact you produced or modified, including its full path
|
|
23
|
+
under `.flow/specs/<active>/`.
|
|
24
|
+
3. Quote the smallest concrete decision that drove the change (one or two
|
|
25
|
+
lines). Do not repeat the full artifact body.
|
|
26
|
+
4. End every reply with one of: `Validated`, `Unvalidated`, `Blocked`. State
|
|
27
|
+
what evidence backs the label.
|
|
28
|
+
5. When proposing a next step, name the exact CurDX-Flow surface that should
|
|
29
|
+
run it (`/curdx-flow:spec --phase=design`, `flow-verifier`, etc.).
|
|
30
|
+
|
|
31
|
+
## Hard rules
|
|
32
|
+
|
|
33
|
+
- Never claim a phase is complete without referencing the produced artifact
|
|
34
|
+
on disk and the gate it passed.
|
|
35
|
+
- Never produce a long architecture essay in chat — that belongs in the
|
|
36
|
+
spec artifact. The reply should point to the file and call out the
|
|
37
|
+
decision taken.
|
|
38
|
+
- Never advance phase state from chat narrative. The phase counter in
|
|
39
|
+
`.state.json` is the source of truth; only the corresponding skill or
|
|
40
|
+
agent is allowed to advance it.
|
|
41
|
+
|
|
42
|
+
## Format
|
|
43
|
+
|
|
44
|
+
- Short headers only when they aid scanning.
|
|
45
|
+
- Bullets for status, prose only for the decision rationale.
|
|
46
|
+
- Match the user's language.
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curdx/flow",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Skill-first discipline layer and CLI installer for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"curdx-flow": "bin/curdx-flow.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"test": "node --test test/*.test.mjs",
|
|
10
11
|
"validate:contracts": "node scripts/validate-plugin-contracts.mjs",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
12
|
+
"validate:contracts:strict": "node scripts/validate-plugin-contracts.mjs --strict",
|
|
13
|
+
"lint": "echo 'lint placeholder; wired in P5'",
|
|
14
|
+
"prepublishOnly": "npm run validate:contracts && npm test"
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
15
17
|
"bin/",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://curdx-flow.dev/schemas/agent-frontmatter.schema.json",
|
|
4
4
|
"title": "CurdX-Flow Agent Frontmatter",
|
|
5
|
-
"description": "Supported YAML frontmatter fields for agents/*.md plugin subagent definitions.",
|
|
5
|
+
"description": "Supported YAML frontmatter fields for agents/*.md plugin subagent definitions. Tracks the canonical field list documented at https://code.claude.com/docs/en/sub-agents.md#supported-frontmatter-fields. The fields `hooks`, `mcpServers`, and `permissionMode` are canonical for non-plugin agents but are silently ignored when an agent is loaded from a plugin (Claude Code security boundary), so this schema intentionally omits them — `additionalProperties: false` rejects them and surfaces the mistake at validation time instead of at runtime.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["name", "description"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"type": "string",
|
|
56
56
|
"enum": ["worktree"]
|
|
57
57
|
},
|
|
58
|
+
"initialPrompt": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
},
|
|
58
61
|
"color": {
|
|
59
62
|
"type": "string",
|
|
60
63
|
"enum": ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan"]
|
|
@@ -98,6 +98,24 @@
|
|
|
98
98
|
"last_error": { "type": "string" }
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
},
|
|
102
|
+
"native_task_map": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"description": "Best-effort mapping from CurDX task ids (for example 1.2 or 4.VF) to Claude native task ids for interactive task-list sync.",
|
|
105
|
+
"additionalProperties": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"native_sync_enabled": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"default": true,
|
|
112
|
+
"description": "When false, execution skips Claude native task-list sync and relies only on tasks.md plus state.json."
|
|
113
|
+
},
|
|
114
|
+
"native_sync_failure_count": {
|
|
115
|
+
"type": "integer",
|
|
116
|
+
"minimum": 0,
|
|
117
|
+
"default": 0,
|
|
118
|
+
"description": "Consecutive best-effort native task sync failures during the current execute run."
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
121
|
},
|