@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
package/cli/upgrade.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* upgrade command — update curdx-flow + recommended plugins to latest.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { log } from "./utils.js";
|
|
5
|
+
import { log, readConfig } from "./utils.js";
|
|
6
|
+
import { reconcileLegacyContext7InstallState } from "./install-context7-config.js";
|
|
6
7
|
import {
|
|
7
8
|
PLUGINS_TO_UPDATE,
|
|
8
9
|
MARKETPLACES_TO_REFRESH,
|
|
@@ -21,43 +22,70 @@ import {
|
|
|
21
22
|
UPGRADE_STEP_COUNT,
|
|
22
23
|
} from "./upgrade-workflow.js";
|
|
23
24
|
|
|
24
|
-
export async function upgrade(
|
|
25
|
-
|
|
25
|
+
export async function upgrade(
|
|
26
|
+
args = [],
|
|
27
|
+
{
|
|
28
|
+
updatePluginImpl = updatePlugin,
|
|
29
|
+
updatePluginMarketplaceImpl = updatePluginMarketplace,
|
|
30
|
+
ensureClaudeCliAvailableForUpgradeImpl = ensureClaudeCliAvailableForUpgrade,
|
|
31
|
+
getInstalledPluginNamesImpl = getInstalledPluginNames,
|
|
32
|
+
readConfigImpl = readConfig,
|
|
33
|
+
reconcileLegacyContext7InstallStateImpl = reconcileLegacyContext7InstallState,
|
|
34
|
+
logImpl = log,
|
|
35
|
+
} = {}
|
|
36
|
+
) {
|
|
37
|
+
logImpl.title("⬆️ CurdX-Flow upgrade");
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
ensureClaudeCliAvailableForUpgradeImpl({ logImpl });
|
|
28
40
|
|
|
29
41
|
// Refresh marketplaces first (derived from cli/registry.js)
|
|
30
42
|
await refreshMarketplaces(MARKETPLACES_TO_REFRESH, {
|
|
31
|
-
updatePluginMarketplaceImpl
|
|
43
|
+
updatePluginMarketplaceImpl,
|
|
44
|
+
logImpl,
|
|
32
45
|
});
|
|
33
46
|
|
|
34
47
|
// Update each plugin
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const installedNames =
|
|
48
|
+
logImpl.blank();
|
|
49
|
+
logImpl.step(2, UPGRADE_STEP_COUNT, "Updating installed plugins...");
|
|
50
|
+
const installedNames = getInstalledPluginNamesImpl();
|
|
38
51
|
|
|
39
52
|
for (const spec of PLUGINS_TO_UPDATE) {
|
|
40
53
|
const pluginName = getPluginNameFromSpec(spec);
|
|
41
54
|
if (!installedNames.has(pluginName)) {
|
|
42
|
-
|
|
55
|
+
logImpl.info(` ${pluginName.padEnd(22)} not installed, skipping`);
|
|
43
56
|
continue;
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
const result = await
|
|
59
|
+
const result = await updatePluginImpl(spec);
|
|
47
60
|
const status = classifyPluginUpdateResult(result);
|
|
48
61
|
|
|
49
62
|
if (status.status === "updated") {
|
|
50
|
-
|
|
63
|
+
logImpl.ok(` ${pluginName.padEnd(22)} ${status.message}`);
|
|
51
64
|
continue;
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
if (status.status === "unchanged") {
|
|
55
|
-
|
|
68
|
+
logImpl.info(` ${pluginName.padEnd(22)} ${status.message}`);
|
|
56
69
|
continue;
|
|
57
70
|
}
|
|
58
71
|
|
|
59
|
-
|
|
72
|
+
logImpl.warn(` ${pluginName.padEnd(22)} ${status.message}`);
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
logImpl.blank();
|
|
76
|
+
logImpl.step(3, UPGRADE_STEP_COUNT, "Reconciling legacy Context7 state...");
|
|
77
|
+
if (installedNames.has("context7-plugin")) {
|
|
78
|
+
const config = readConfigImpl();
|
|
79
|
+
const language = config?.language === "zh" ? "zh" : "en";
|
|
80
|
+
await reconcileLegacyContext7InstallStateImpl(
|
|
81
|
+
{ name: "context7-plugin" },
|
|
82
|
+
language,
|
|
83
|
+
config,
|
|
84
|
+
{ logImpl }
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
logImpl.info(" context7-plugin not installed, skipping");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
printUpgradeSummary({ logImpl });
|
|
63
91
|
}
|
package/hooks/hooks.json
CHANGED
|
@@ -21,6 +21,37 @@
|
|
|
21
21
|
]
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
|
+
"UserPromptSubmit": [
|
|
25
|
+
{
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-title.sh",
|
|
30
|
+
"statusMessage": "Refreshing CurDX-Flow session title"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"CwdChanged": [
|
|
36
|
+
{
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/flow-context-watch.sh"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"FileChanged": [
|
|
46
|
+
{
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/flow-context-watch.sh"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
],
|
|
24
55
|
"Stop": [
|
|
25
56
|
{
|
|
26
57
|
"hooks": [
|
|
@@ -43,6 +74,47 @@
|
|
|
43
74
|
]
|
|
44
75
|
}
|
|
45
76
|
],
|
|
77
|
+
"TaskCreated": [
|
|
78
|
+
{
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/task-lifecycle-guard.sh"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"TaskCompleted": [
|
|
88
|
+
{
|
|
89
|
+
"hooks": [
|
|
90
|
+
{
|
|
91
|
+
"type": "command",
|
|
92
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/task-lifecycle-guard.sh"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"TeammateIdle": [
|
|
98
|
+
{
|
|
99
|
+
"hooks": [
|
|
100
|
+
{
|
|
101
|
+
"type": "command",
|
|
102
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/teammate-idle-guard.sh"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"ConfigChange": [
|
|
108
|
+
{
|
|
109
|
+
"matcher": "project_settings|local_settings",
|
|
110
|
+
"hooks": [
|
|
111
|
+
{
|
|
112
|
+
"type": "command",
|
|
113
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/config-change-guard.sh"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
],
|
|
46
118
|
"PreToolUse": [
|
|
47
119
|
{
|
|
48
120
|
"matcher": "AskUserQuestion",
|
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
|