@orchestrator-claude/cli 3.10.1 → 3.11.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/base/claude/hooks/dangling-workflow-guard.sh +65 -0
- package/dist/templates/base/claude/hooks/gate-guardian.sh +80 -0
- package/dist/templates/base/claude/hooks/orch-helpers.sh +128 -0
- package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +72 -0
- package/dist/templates/base/claude/settings.json +26 -7
- package/package.json +1 -1
- package/templates/base/claude/hooks/dangling-workflow-guard.sh +65 -0
- package/templates/base/claude/hooks/gate-guardian.sh +80 -0
- package/templates/base/claude/hooks/orch-helpers.sh +128 -0
- package/templates/base/claude/hooks/ping-pong-enforcer.sh +72 -0
- package/templates/base/claude/settings.json +26 -7
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# dangling-workflow-guard.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: Stop (session end)
|
|
4
|
+
# Purpose: Before the conversation ends, check for workflows still
|
|
5
|
+
# in "in_progress" state. Warn the user and attempt to
|
|
6
|
+
# complete the workflow automatically.
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 always (non-blocking — we don't want to prevent session exit).
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
14
|
+
|
|
15
|
+
orch_log "DANGLING-GUARD: Stop hook triggered"
|
|
16
|
+
|
|
17
|
+
# Find active workflow
|
|
18
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || {
|
|
19
|
+
orch_log "DANGLING-GUARD: No active workflow, clean exit"
|
|
20
|
+
exit 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
orch_log "DANGLING-GUARD: Found active workflow=$WORKFLOW_ID"
|
|
24
|
+
|
|
25
|
+
# Get workflow status
|
|
26
|
+
TOKEN=$(orch_get_token 2>/dev/null) || exit 0
|
|
27
|
+
|
|
28
|
+
STATUS_RESP=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/status" \
|
|
29
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
30
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || exit 0
|
|
31
|
+
|
|
32
|
+
CURRENT_PHASE=$(orch_json_field "$STATUS_RESP" "currentPhase")
|
|
33
|
+
WF_STATUS=$(orch_json_field "$STATUS_RESP" "status")
|
|
34
|
+
|
|
35
|
+
orch_log "DANGLING-GUARD: status=$WF_STATUS phase=$CURRENT_PHASE"
|
|
36
|
+
|
|
37
|
+
if [ "$WF_STATUS" = "in_progress" ] || [ "$WF_STATUS" = "running" ]; then
|
|
38
|
+
echo ""
|
|
39
|
+
echo "[DANGLING-GUARD] WARNING: Workflow $WORKFLOW_ID is still active (status: $WF_STATUS, phase: $CURRENT_PHASE)."
|
|
40
|
+
|
|
41
|
+
# If in implement phase with no pending actions, auto-complete
|
|
42
|
+
if [ "$CURRENT_PHASE" = "implement" ]; then
|
|
43
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || exit 0
|
|
44
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
45
|
+
|
|
46
|
+
if [ "$HAS_ACTION" != "true" ]; then
|
|
47
|
+
echo "[DANGLING-GUARD] Auto-completing workflow (implement phase, no pending actions)..."
|
|
48
|
+
curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/complete" \
|
|
49
|
+
-H "Content-Type: application/json" \
|
|
50
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
51
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
52
|
+
-d "{}" >> "$DEBUG_LOG" 2>&1 || true
|
|
53
|
+
echo "[DANGLING-GUARD] Workflow completed."
|
|
54
|
+
orch_log "DANGLING-GUARD: Auto-completed workflow $WORKFLOW_ID"
|
|
55
|
+
else
|
|
56
|
+
echo "[DANGLING-GUARD] Workflow has pending actions — cannot auto-complete."
|
|
57
|
+
echo "Resume this session to continue the workflow."
|
|
58
|
+
fi
|
|
59
|
+
else
|
|
60
|
+
echo "[DANGLING-GUARD] Workflow is in '$CURRENT_PHASE' phase — not auto-completing."
|
|
61
|
+
echo "Resume this session to continue the workflow."
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
exit 0
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# gate-guardian.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: PreToolUse on mcp__orchestrator-tools__advancePhase
|
|
4
|
+
# Purpose: Before any phase advance, evaluate the gate.
|
|
5
|
+
# BLOCKS the advance if the gate fails (exit 1).
|
|
6
|
+
# Also enforces human approval for IMPLEMENT phase.
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 = allow advancePhase to proceed
|
|
9
|
+
# Exit 1 = BLOCK advancePhase (gate failed or approval required)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
15
|
+
|
|
16
|
+
# Read stdin (Claude Code passes tool input JSON)
|
|
17
|
+
STDIN_DATA=$(orch_read_stdin)
|
|
18
|
+
orch_log "GATE-GUARDIAN: PreToolUse advancePhase triggered"
|
|
19
|
+
|
|
20
|
+
# Extract workflowId and targetPhase from tool input
|
|
21
|
+
WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "tool_input.workflowId")
|
|
22
|
+
TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "tool_input.targetPhase")
|
|
23
|
+
|
|
24
|
+
# Fallback: try without tool_input wrapper
|
|
25
|
+
[ -z "$WORKFLOW_ID" ] && WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "workflowId")
|
|
26
|
+
[ -z "$TARGET_PHASE" ] && TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "targetPhase")
|
|
27
|
+
|
|
28
|
+
orch_log "GATE-GUARDIAN: workflow=$WORKFLOW_ID targetPhase=$TARGET_PHASE"
|
|
29
|
+
|
|
30
|
+
# If we can't parse the input, allow (don't break on edge cases)
|
|
31
|
+
if [ -z "$WORKFLOW_ID" ] || [ -z "$TARGET_PHASE" ]; then
|
|
32
|
+
orch_log "GATE-GUARDIAN: Could not parse input, allowing"
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# --- IMPLEMENT phase: check for human approval ---
|
|
37
|
+
TARGET_LOWER=$(echo "$TARGET_PHASE" | tr '[:upper:]' '[:lower:]')
|
|
38
|
+
|
|
39
|
+
if [ "$TARGET_LOWER" = "implement" ]; then
|
|
40
|
+
orch_log "GATE-GUARDIAN: Checking approval for IMPLEMENT phase"
|
|
41
|
+
|
|
42
|
+
# Check pending action status
|
|
43
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || {
|
|
44
|
+
orch_log "GATE-GUARDIAN: Could not check approval, allowing"
|
|
45
|
+
exit 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
49
|
+
|
|
50
|
+
if [ "$STATUS" = "awaiting_approval" ]; then
|
|
51
|
+
echo ""
|
|
52
|
+
echo "[GATE-GUARDIAN] BLOCKED: Cannot advance to IMPLEMENT without human approval."
|
|
53
|
+
echo "The workflow requires user approval before implementation begins."
|
|
54
|
+
echo "Call mcp__orchestrator-tools__approveAction({ workflowId: '$WORKFLOW_ID' }) after user approves."
|
|
55
|
+
orch_log "GATE-GUARDIAN: BLOCKED — awaiting_approval for IMPLEMENT"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# --- All phases: evaluate gate ---
|
|
61
|
+
GATE_RESULT=$(orch_evaluate_gate "$WORKFLOW_ID" "$TARGET_PHASE" 2>/dev/null) || {
|
|
62
|
+
orch_log "GATE-GUARDIAN: evaluateGate call failed, allowing (API may not support this gate)"
|
|
63
|
+
exit 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
PASSED=$(orch_json_field "$GATE_RESULT" "passed")
|
|
67
|
+
|
|
68
|
+
if [ "$PASSED" = "false" ]; then
|
|
69
|
+
REASONS=$(orch_json_field "$GATE_RESULT" "reasons")
|
|
70
|
+
echo ""
|
|
71
|
+
echo "[GATE-GUARDIAN] BLOCKED: Gate to '$TARGET_PHASE' did not pass."
|
|
72
|
+
echo "Reasons: $REASONS"
|
|
73
|
+
echo "Fix the issues before advancing."
|
|
74
|
+
orch_log "GATE-GUARDIAN: BLOCKED — gate failed for $TARGET_PHASE: $REASONS"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
orch_log "GATE-GUARDIAN: Gate to $TARGET_PHASE PASSED"
|
|
79
|
+
echo "[GATE-GUARDIAN] Gate to '$TARGET_PHASE' PASSED."
|
|
80
|
+
exit 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# orch-helpers.sh — Shared helper functions for Orchestrator hooks
|
|
3
|
+
# ADR-013: Deterministic Orchestration
|
|
4
|
+
# Sourced by all hooks. NOT called directly.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
8
|
+
STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
|
|
9
|
+
DEBUG_LOG="$STATE_DIR/hook-debug.log"
|
|
10
|
+
|
|
11
|
+
# API Configuration (from .env or defaults)
|
|
12
|
+
API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
|
|
13
|
+
AUTH_EMAIL="${ORCHESTRATOR_ADMIN_EMAIL:-${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}}"
|
|
14
|
+
AUTH_PASSWORD="${ORCHESTRATOR_ADMIN_PASSWORD:-${ORCHESTRATOR_AUTH_PASSWORD:-admin123}}"
|
|
15
|
+
PROJECT_ID="${ORCHESTRATOR_PROJECT_ID}"
|
|
16
|
+
|
|
17
|
+
mkdir -p "$STATE_DIR"
|
|
18
|
+
|
|
19
|
+
orch_log() {
|
|
20
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Read stdin (Claude Code passes JSON via stdin)
|
|
24
|
+
orch_read_stdin() {
|
|
25
|
+
if [ ! -t 0 ]; then
|
|
26
|
+
cat
|
|
27
|
+
else
|
|
28
|
+
echo ""
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Get cached JWT token (login if expired)
|
|
33
|
+
orch_get_token() {
|
|
34
|
+
local cached="$STATE_DIR/jwt-token"
|
|
35
|
+
if [ -f "$cached" ]; then
|
|
36
|
+
local age
|
|
37
|
+
age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
|
|
38
|
+
if [ "$age" -lt 3500 ]; then
|
|
39
|
+
cat "$cached"
|
|
40
|
+
return
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
[ -z "$PROJECT_ID" ] && return 1
|
|
45
|
+
|
|
46
|
+
local resp
|
|
47
|
+
resp=$(curl -sf --max-time 5 -X POST "${API_URL}/api/v1/auth/login" \
|
|
48
|
+
-H "Content-Type: application/json" \
|
|
49
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
50
|
+
-d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
|
|
51
|
+
|
|
52
|
+
local token
|
|
53
|
+
token=$(echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.accessToken||j.data?.accessToken||'')}catch{console.log('')}})" 2>/dev/null)
|
|
54
|
+
|
|
55
|
+
if [ -n "$token" ] && [ "$token" != "" ]; then
|
|
56
|
+
echo "$token" > "$cached"
|
|
57
|
+
echo "$token"
|
|
58
|
+
else
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Get the active (in_progress) workflow ID
|
|
64
|
+
orch_get_active_workflow() {
|
|
65
|
+
local token
|
|
66
|
+
token=$(orch_get_token) || return 1
|
|
67
|
+
|
|
68
|
+
local resp
|
|
69
|
+
resp=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows?status=in_progress&limit=1" \
|
|
70
|
+
-H "Authorization: Bearer $token" \
|
|
71
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || return 1
|
|
72
|
+
|
|
73
|
+
echo "$resp" | node -e "
|
|
74
|
+
let d='';
|
|
75
|
+
process.stdin.on('data',c=>d+=c);
|
|
76
|
+
process.stdin.on('end',()=>{
|
|
77
|
+
try {
|
|
78
|
+
const j=JSON.parse(d);
|
|
79
|
+
const wfs=j.data||j;
|
|
80
|
+
const wf=Array.isArray(wfs)?wfs[0]:wfs;
|
|
81
|
+
const id=wf?.id||'';
|
|
82
|
+
if(id) console.log(id); else process.exit(1);
|
|
83
|
+
} catch { process.exit(1); }
|
|
84
|
+
});" 2>/dev/null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Call getNextAction for a workflow
|
|
88
|
+
orch_get_next_action() {
|
|
89
|
+
local workflow_id="$1"
|
|
90
|
+
local token
|
|
91
|
+
token=$(orch_get_token) || return 1
|
|
92
|
+
|
|
93
|
+
curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/next-action" \
|
|
94
|
+
-H "Authorization: Bearer $token" \
|
|
95
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Call evaluateGate for a workflow
|
|
99
|
+
orch_evaluate_gate() {
|
|
100
|
+
local workflow_id="$1"
|
|
101
|
+
local target_phase="$2"
|
|
102
|
+
local token
|
|
103
|
+
token=$(orch_get_token) || return 1
|
|
104
|
+
|
|
105
|
+
curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${workflow_id}/evaluate-gate" \
|
|
106
|
+
-H "Content-Type: application/json" \
|
|
107
|
+
-H "Authorization: Bearer $token" \
|
|
108
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
109
|
+
-d "{\"targetPhase\":\"${target_phase}\"}" 2>/dev/null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Extract a field from JSON string using node
|
|
113
|
+
orch_json_field() {
|
|
114
|
+
local json="$1"
|
|
115
|
+
local field="$2"
|
|
116
|
+
echo "$json" | node -e "
|
|
117
|
+
let d='';
|
|
118
|
+
process.stdin.on('data',c=>d+=c);
|
|
119
|
+
process.stdin.on('end',()=>{
|
|
120
|
+
try {
|
|
121
|
+
const j=JSON.parse(d);
|
|
122
|
+
const parts='${field}'.split('.');
|
|
123
|
+
let v=j;
|
|
124
|
+
for(const p of parts) v=v?.[p];
|
|
125
|
+
console.log(v||'');
|
|
126
|
+
} catch { console.log(''); }
|
|
127
|
+
});" 2>/dev/null
|
|
128
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ping-pong-enforcer.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: PostToolUse on Agent
|
|
4
|
+
# Purpose: After ANY sub-agent returns, automatically call getNextAction
|
|
5
|
+
# and inject the result into the conversation.
|
|
6
|
+
#
|
|
7
|
+
# This makes Ping-Pong DETERMINISTIC: the LLM always receives the next
|
|
8
|
+
# action regardless of whether it "remembers" to call getNextAction.
|
|
9
|
+
#
|
|
10
|
+
# Output goes to stdout → injected into Claude conversation context.
|
|
11
|
+
# Exit 0 always (non-blocking — informational injection).
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
17
|
+
|
|
18
|
+
# Read stdin (Claude Code passes tool result JSON)
|
|
19
|
+
STDIN_DATA=$(orch_read_stdin)
|
|
20
|
+
orch_log "PING-PONG: PostToolUse Agent triggered"
|
|
21
|
+
|
|
22
|
+
# Find active workflow
|
|
23
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || {
|
|
24
|
+
orch_log "PING-PONG: No active workflow, skipping"
|
|
25
|
+
exit 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
orch_log "PING-PONG: Active workflow=$WORKFLOW_ID"
|
|
29
|
+
|
|
30
|
+
# Call getNextAction
|
|
31
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || {
|
|
32
|
+
orch_log "PING-PONG: getNextAction failed, skipping"
|
|
33
|
+
exit 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Parse response
|
|
37
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
38
|
+
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
39
|
+
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
40
|
+
CURRENT_PHASE=$(orch_json_field "$NEXT_ACTION" "currentPhase")
|
|
41
|
+
|
|
42
|
+
orch_log "PING-PONG: hasAction=$HAS_ACTION status=$STATUS agent=$AGENT phase=$CURRENT_PHASE"
|
|
43
|
+
|
|
44
|
+
# Inject result into conversation
|
|
45
|
+
if [ "$HAS_ACTION" = "true" ]; then
|
|
46
|
+
if [ "$STATUS" = "awaiting_agent" ]; then
|
|
47
|
+
echo ""
|
|
48
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — next action: invoke agent '$AGENT'"
|
|
49
|
+
echo "You MUST invoke this agent via Task tool with subagent_type='$AGENT'."
|
|
50
|
+
echo "Workflow ID: $WORKFLOW_ID | Phase: $CURRENT_PHASE"
|
|
51
|
+
elif [ "$STATUS" = "awaiting_approval" ]; then
|
|
52
|
+
echo ""
|
|
53
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — AWAITING HUMAN APPROVAL"
|
|
54
|
+
echo "Phase: $CURRENT_PHASE. Ask the user to review artifacts and approve before continuing."
|
|
55
|
+
echo "After approval, call: mcp__orchestrator-tools__approveAction({ workflowId: '$WORKFLOW_ID' })"
|
|
56
|
+
else
|
|
57
|
+
echo ""
|
|
58
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — pending action status: $STATUS"
|
|
59
|
+
echo "Full response: $NEXT_ACTION"
|
|
60
|
+
fi
|
|
61
|
+
else
|
|
62
|
+
if [ "$CURRENT_PHASE" = "implement" ]; then
|
|
63
|
+
echo ""
|
|
64
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — no more actions. Phase: implement."
|
|
65
|
+
echo "You MUST call: mcp__orchestrator-tools__completeWorkflow({ workflowId: '$WORKFLOW_ID' })"
|
|
66
|
+
else
|
|
67
|
+
echo ""
|
|
68
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — no pending actions. Phase: $CURRENT_PHASE."
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
exit 0
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"Write(tests/**)",
|
|
20
20
|
"Write(docs/**)",
|
|
21
21
|
"mcp__orchestrator-tools__*",
|
|
22
|
+
"mcp__orchestrator-extended__*",
|
|
22
23
|
"mcp__perplexity__*",
|
|
23
24
|
"mcp__knowledge-base__*"
|
|
24
25
|
],
|
|
@@ -47,6 +48,17 @@
|
|
|
47
48
|
"on_failure": "ignore"
|
|
48
49
|
}
|
|
49
50
|
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"matcher": "mcp__orchestrator-tools__advancePhase",
|
|
54
|
+
"hooks": [
|
|
55
|
+
{
|
|
56
|
+
"type": "command",
|
|
57
|
+
"command": ".claude/hooks/gate-guardian.sh",
|
|
58
|
+
"timeout": 15000,
|
|
59
|
+
"on_failure": "warn"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
50
62
|
}
|
|
51
63
|
],
|
|
52
64
|
"PostToolUse": [
|
|
@@ -61,14 +73,8 @@
|
|
|
61
73
|
},
|
|
62
74
|
{
|
|
63
75
|
"type": "command",
|
|
64
|
-
"command": ".claude/hooks/
|
|
76
|
+
"command": ".claude/hooks/ping-pong-enforcer.sh",
|
|
65
77
|
"timeout": 15000,
|
|
66
|
-
"on_failure": "ignore"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"type": "command",
|
|
70
|
-
"command": ".claude/hooks/post-implement-validate.sh",
|
|
71
|
-
"timeout": 10000,
|
|
72
78
|
"on_failure": "warn"
|
|
73
79
|
},
|
|
74
80
|
{
|
|
@@ -79,6 +85,19 @@
|
|
|
79
85
|
}
|
|
80
86
|
]
|
|
81
87
|
}
|
|
88
|
+
],
|
|
89
|
+
"Stop": [
|
|
90
|
+
{
|
|
91
|
+
"matcher": "",
|
|
92
|
+
"hooks": [
|
|
93
|
+
{
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": ".claude/hooks/dangling-workflow-guard.sh",
|
|
96
|
+
"timeout": 15000,
|
|
97
|
+
"on_failure": "ignore"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
82
101
|
]
|
|
83
102
|
},
|
|
84
103
|
"enableAllProjectMcpServers": true,
|
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# dangling-workflow-guard.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: Stop (session end)
|
|
4
|
+
# Purpose: Before the conversation ends, check for workflows still
|
|
5
|
+
# in "in_progress" state. Warn the user and attempt to
|
|
6
|
+
# complete the workflow automatically.
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 always (non-blocking — we don't want to prevent session exit).
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
14
|
+
|
|
15
|
+
orch_log "DANGLING-GUARD: Stop hook triggered"
|
|
16
|
+
|
|
17
|
+
# Find active workflow
|
|
18
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || {
|
|
19
|
+
orch_log "DANGLING-GUARD: No active workflow, clean exit"
|
|
20
|
+
exit 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
orch_log "DANGLING-GUARD: Found active workflow=$WORKFLOW_ID"
|
|
24
|
+
|
|
25
|
+
# Get workflow status
|
|
26
|
+
TOKEN=$(orch_get_token 2>/dev/null) || exit 0
|
|
27
|
+
|
|
28
|
+
STATUS_RESP=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/status" \
|
|
29
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
30
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || exit 0
|
|
31
|
+
|
|
32
|
+
CURRENT_PHASE=$(orch_json_field "$STATUS_RESP" "currentPhase")
|
|
33
|
+
WF_STATUS=$(orch_json_field "$STATUS_RESP" "status")
|
|
34
|
+
|
|
35
|
+
orch_log "DANGLING-GUARD: status=$WF_STATUS phase=$CURRENT_PHASE"
|
|
36
|
+
|
|
37
|
+
if [ "$WF_STATUS" = "in_progress" ] || [ "$WF_STATUS" = "running" ]; then
|
|
38
|
+
echo ""
|
|
39
|
+
echo "[DANGLING-GUARD] WARNING: Workflow $WORKFLOW_ID is still active (status: $WF_STATUS, phase: $CURRENT_PHASE)."
|
|
40
|
+
|
|
41
|
+
# If in implement phase with no pending actions, auto-complete
|
|
42
|
+
if [ "$CURRENT_PHASE" = "implement" ]; then
|
|
43
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || exit 0
|
|
44
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
45
|
+
|
|
46
|
+
if [ "$HAS_ACTION" != "true" ]; then
|
|
47
|
+
echo "[DANGLING-GUARD] Auto-completing workflow (implement phase, no pending actions)..."
|
|
48
|
+
curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/complete" \
|
|
49
|
+
-H "Content-Type: application/json" \
|
|
50
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
51
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
52
|
+
-d "{}" >> "$DEBUG_LOG" 2>&1 || true
|
|
53
|
+
echo "[DANGLING-GUARD] Workflow completed."
|
|
54
|
+
orch_log "DANGLING-GUARD: Auto-completed workflow $WORKFLOW_ID"
|
|
55
|
+
else
|
|
56
|
+
echo "[DANGLING-GUARD] Workflow has pending actions — cannot auto-complete."
|
|
57
|
+
echo "Resume this session to continue the workflow."
|
|
58
|
+
fi
|
|
59
|
+
else
|
|
60
|
+
echo "[DANGLING-GUARD] Workflow is in '$CURRENT_PHASE' phase — not auto-completing."
|
|
61
|
+
echo "Resume this session to continue the workflow."
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
exit 0
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# gate-guardian.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: PreToolUse on mcp__orchestrator-tools__advancePhase
|
|
4
|
+
# Purpose: Before any phase advance, evaluate the gate.
|
|
5
|
+
# BLOCKS the advance if the gate fails (exit 1).
|
|
6
|
+
# Also enforces human approval for IMPLEMENT phase.
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 = allow advancePhase to proceed
|
|
9
|
+
# Exit 1 = BLOCK advancePhase (gate failed or approval required)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
15
|
+
|
|
16
|
+
# Read stdin (Claude Code passes tool input JSON)
|
|
17
|
+
STDIN_DATA=$(orch_read_stdin)
|
|
18
|
+
orch_log "GATE-GUARDIAN: PreToolUse advancePhase triggered"
|
|
19
|
+
|
|
20
|
+
# Extract workflowId and targetPhase from tool input
|
|
21
|
+
WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "tool_input.workflowId")
|
|
22
|
+
TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "tool_input.targetPhase")
|
|
23
|
+
|
|
24
|
+
# Fallback: try without tool_input wrapper
|
|
25
|
+
[ -z "$WORKFLOW_ID" ] && WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "workflowId")
|
|
26
|
+
[ -z "$TARGET_PHASE" ] && TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "targetPhase")
|
|
27
|
+
|
|
28
|
+
orch_log "GATE-GUARDIAN: workflow=$WORKFLOW_ID targetPhase=$TARGET_PHASE"
|
|
29
|
+
|
|
30
|
+
# If we can't parse the input, allow (don't break on edge cases)
|
|
31
|
+
if [ -z "$WORKFLOW_ID" ] || [ -z "$TARGET_PHASE" ]; then
|
|
32
|
+
orch_log "GATE-GUARDIAN: Could not parse input, allowing"
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# --- IMPLEMENT phase: check for human approval ---
|
|
37
|
+
TARGET_LOWER=$(echo "$TARGET_PHASE" | tr '[:upper:]' '[:lower:]')
|
|
38
|
+
|
|
39
|
+
if [ "$TARGET_LOWER" = "implement" ]; then
|
|
40
|
+
orch_log "GATE-GUARDIAN: Checking approval for IMPLEMENT phase"
|
|
41
|
+
|
|
42
|
+
# Check pending action status
|
|
43
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || {
|
|
44
|
+
orch_log "GATE-GUARDIAN: Could not check approval, allowing"
|
|
45
|
+
exit 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
49
|
+
|
|
50
|
+
if [ "$STATUS" = "awaiting_approval" ]; then
|
|
51
|
+
echo ""
|
|
52
|
+
echo "[GATE-GUARDIAN] BLOCKED: Cannot advance to IMPLEMENT without human approval."
|
|
53
|
+
echo "The workflow requires user approval before implementation begins."
|
|
54
|
+
echo "Call mcp__orchestrator-tools__approveAction({ workflowId: '$WORKFLOW_ID' }) after user approves."
|
|
55
|
+
orch_log "GATE-GUARDIAN: BLOCKED — awaiting_approval for IMPLEMENT"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# --- All phases: evaluate gate ---
|
|
61
|
+
GATE_RESULT=$(orch_evaluate_gate "$WORKFLOW_ID" "$TARGET_PHASE" 2>/dev/null) || {
|
|
62
|
+
orch_log "GATE-GUARDIAN: evaluateGate call failed, allowing (API may not support this gate)"
|
|
63
|
+
exit 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
PASSED=$(orch_json_field "$GATE_RESULT" "passed")
|
|
67
|
+
|
|
68
|
+
if [ "$PASSED" = "false" ]; then
|
|
69
|
+
REASONS=$(orch_json_field "$GATE_RESULT" "reasons")
|
|
70
|
+
echo ""
|
|
71
|
+
echo "[GATE-GUARDIAN] BLOCKED: Gate to '$TARGET_PHASE' did not pass."
|
|
72
|
+
echo "Reasons: $REASONS"
|
|
73
|
+
echo "Fix the issues before advancing."
|
|
74
|
+
orch_log "GATE-GUARDIAN: BLOCKED — gate failed for $TARGET_PHASE: $REASONS"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
orch_log "GATE-GUARDIAN: Gate to $TARGET_PHASE PASSED"
|
|
79
|
+
echo "[GATE-GUARDIAN] Gate to '$TARGET_PHASE' PASSED."
|
|
80
|
+
exit 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# orch-helpers.sh — Shared helper functions for Orchestrator hooks
|
|
3
|
+
# ADR-013: Deterministic Orchestration
|
|
4
|
+
# Sourced by all hooks. NOT called directly.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
8
|
+
STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
|
|
9
|
+
DEBUG_LOG="$STATE_DIR/hook-debug.log"
|
|
10
|
+
|
|
11
|
+
# API Configuration (from .env or defaults)
|
|
12
|
+
API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
|
|
13
|
+
AUTH_EMAIL="${ORCHESTRATOR_ADMIN_EMAIL:-${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}}"
|
|
14
|
+
AUTH_PASSWORD="${ORCHESTRATOR_ADMIN_PASSWORD:-${ORCHESTRATOR_AUTH_PASSWORD:-admin123}}"
|
|
15
|
+
PROJECT_ID="${ORCHESTRATOR_PROJECT_ID}"
|
|
16
|
+
|
|
17
|
+
mkdir -p "$STATE_DIR"
|
|
18
|
+
|
|
19
|
+
orch_log() {
|
|
20
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Read stdin (Claude Code passes JSON via stdin)
|
|
24
|
+
orch_read_stdin() {
|
|
25
|
+
if [ ! -t 0 ]; then
|
|
26
|
+
cat
|
|
27
|
+
else
|
|
28
|
+
echo ""
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Get cached JWT token (login if expired)
|
|
33
|
+
orch_get_token() {
|
|
34
|
+
local cached="$STATE_DIR/jwt-token"
|
|
35
|
+
if [ -f "$cached" ]; then
|
|
36
|
+
local age
|
|
37
|
+
age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
|
|
38
|
+
if [ "$age" -lt 3500 ]; then
|
|
39
|
+
cat "$cached"
|
|
40
|
+
return
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
[ -z "$PROJECT_ID" ] && return 1
|
|
45
|
+
|
|
46
|
+
local resp
|
|
47
|
+
resp=$(curl -sf --max-time 5 -X POST "${API_URL}/api/v1/auth/login" \
|
|
48
|
+
-H "Content-Type: application/json" \
|
|
49
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
50
|
+
-d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
|
|
51
|
+
|
|
52
|
+
local token
|
|
53
|
+
token=$(echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.accessToken||j.data?.accessToken||'')}catch{console.log('')}})" 2>/dev/null)
|
|
54
|
+
|
|
55
|
+
if [ -n "$token" ] && [ "$token" != "" ]; then
|
|
56
|
+
echo "$token" > "$cached"
|
|
57
|
+
echo "$token"
|
|
58
|
+
else
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Get the active (in_progress) workflow ID
|
|
64
|
+
orch_get_active_workflow() {
|
|
65
|
+
local token
|
|
66
|
+
token=$(orch_get_token) || return 1
|
|
67
|
+
|
|
68
|
+
local resp
|
|
69
|
+
resp=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows?status=in_progress&limit=1" \
|
|
70
|
+
-H "Authorization: Bearer $token" \
|
|
71
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || return 1
|
|
72
|
+
|
|
73
|
+
echo "$resp" | node -e "
|
|
74
|
+
let d='';
|
|
75
|
+
process.stdin.on('data',c=>d+=c);
|
|
76
|
+
process.stdin.on('end',()=>{
|
|
77
|
+
try {
|
|
78
|
+
const j=JSON.parse(d);
|
|
79
|
+
const wfs=j.data||j;
|
|
80
|
+
const wf=Array.isArray(wfs)?wfs[0]:wfs;
|
|
81
|
+
const id=wf?.id||'';
|
|
82
|
+
if(id) console.log(id); else process.exit(1);
|
|
83
|
+
} catch { process.exit(1); }
|
|
84
|
+
});" 2>/dev/null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Call getNextAction for a workflow
|
|
88
|
+
orch_get_next_action() {
|
|
89
|
+
local workflow_id="$1"
|
|
90
|
+
local token
|
|
91
|
+
token=$(orch_get_token) || return 1
|
|
92
|
+
|
|
93
|
+
curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/next-action" \
|
|
94
|
+
-H "Authorization: Bearer $token" \
|
|
95
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Call evaluateGate for a workflow
|
|
99
|
+
orch_evaluate_gate() {
|
|
100
|
+
local workflow_id="$1"
|
|
101
|
+
local target_phase="$2"
|
|
102
|
+
local token
|
|
103
|
+
token=$(orch_get_token) || return 1
|
|
104
|
+
|
|
105
|
+
curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${workflow_id}/evaluate-gate" \
|
|
106
|
+
-H "Content-Type: application/json" \
|
|
107
|
+
-H "Authorization: Bearer $token" \
|
|
108
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
109
|
+
-d "{\"targetPhase\":\"${target_phase}\"}" 2>/dev/null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Extract a field from JSON string using node
|
|
113
|
+
orch_json_field() {
|
|
114
|
+
local json="$1"
|
|
115
|
+
local field="$2"
|
|
116
|
+
echo "$json" | node -e "
|
|
117
|
+
let d='';
|
|
118
|
+
process.stdin.on('data',c=>d+=c);
|
|
119
|
+
process.stdin.on('end',()=>{
|
|
120
|
+
try {
|
|
121
|
+
const j=JSON.parse(d);
|
|
122
|
+
const parts='${field}'.split('.');
|
|
123
|
+
let v=j;
|
|
124
|
+
for(const p of parts) v=v?.[p];
|
|
125
|
+
console.log(v||'');
|
|
126
|
+
} catch { console.log(''); }
|
|
127
|
+
});" 2>/dev/null
|
|
128
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ping-pong-enforcer.sh — ADR-013 Core Hook
|
|
3
|
+
# Trigger: PostToolUse on Agent
|
|
4
|
+
# Purpose: After ANY sub-agent returns, automatically call getNextAction
|
|
5
|
+
# and inject the result into the conversation.
|
|
6
|
+
#
|
|
7
|
+
# This makes Ping-Pong DETERMINISTIC: the LLM always receives the next
|
|
8
|
+
# action regardless of whether it "remembers" to call getNextAction.
|
|
9
|
+
#
|
|
10
|
+
# Output goes to stdout → injected into Claude conversation context.
|
|
11
|
+
# Exit 0 always (non-blocking — informational injection).
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
17
|
+
|
|
18
|
+
# Read stdin (Claude Code passes tool result JSON)
|
|
19
|
+
STDIN_DATA=$(orch_read_stdin)
|
|
20
|
+
orch_log "PING-PONG: PostToolUse Agent triggered"
|
|
21
|
+
|
|
22
|
+
# Find active workflow
|
|
23
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || {
|
|
24
|
+
orch_log "PING-PONG: No active workflow, skipping"
|
|
25
|
+
exit 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
orch_log "PING-PONG: Active workflow=$WORKFLOW_ID"
|
|
29
|
+
|
|
30
|
+
# Call getNextAction
|
|
31
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || {
|
|
32
|
+
orch_log "PING-PONG: getNextAction failed, skipping"
|
|
33
|
+
exit 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Parse response
|
|
37
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
38
|
+
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
39
|
+
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
40
|
+
CURRENT_PHASE=$(orch_json_field "$NEXT_ACTION" "currentPhase")
|
|
41
|
+
|
|
42
|
+
orch_log "PING-PONG: hasAction=$HAS_ACTION status=$STATUS agent=$AGENT phase=$CURRENT_PHASE"
|
|
43
|
+
|
|
44
|
+
# Inject result into conversation
|
|
45
|
+
if [ "$HAS_ACTION" = "true" ]; then
|
|
46
|
+
if [ "$STATUS" = "awaiting_agent" ]; then
|
|
47
|
+
echo ""
|
|
48
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — next action: invoke agent '$AGENT'"
|
|
49
|
+
echo "You MUST invoke this agent via Task tool with subagent_type='$AGENT'."
|
|
50
|
+
echo "Workflow ID: $WORKFLOW_ID | Phase: $CURRENT_PHASE"
|
|
51
|
+
elif [ "$STATUS" = "awaiting_approval" ]; then
|
|
52
|
+
echo ""
|
|
53
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — AWAITING HUMAN APPROVAL"
|
|
54
|
+
echo "Phase: $CURRENT_PHASE. Ask the user to review artifacts and approve before continuing."
|
|
55
|
+
echo "After approval, call: mcp__orchestrator-tools__approveAction({ workflowId: '$WORKFLOW_ID' })"
|
|
56
|
+
else
|
|
57
|
+
echo ""
|
|
58
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — pending action status: $STATUS"
|
|
59
|
+
echo "Full response: $NEXT_ACTION"
|
|
60
|
+
fi
|
|
61
|
+
else
|
|
62
|
+
if [ "$CURRENT_PHASE" = "implement" ]; then
|
|
63
|
+
echo ""
|
|
64
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — no more actions. Phase: implement."
|
|
65
|
+
echo "You MUST call: mcp__orchestrator-tools__completeWorkflow({ workflowId: '$WORKFLOW_ID' })"
|
|
66
|
+
else
|
|
67
|
+
echo ""
|
|
68
|
+
echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — no pending actions. Phase: $CURRENT_PHASE."
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
exit 0
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"Write(tests/**)",
|
|
20
20
|
"Write(docs/**)",
|
|
21
21
|
"mcp__orchestrator-tools__*",
|
|
22
|
+
"mcp__orchestrator-extended__*",
|
|
22
23
|
"mcp__perplexity__*",
|
|
23
24
|
"mcp__knowledge-base__*"
|
|
24
25
|
],
|
|
@@ -47,6 +48,17 @@
|
|
|
47
48
|
"on_failure": "ignore"
|
|
48
49
|
}
|
|
49
50
|
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"matcher": "mcp__orchestrator-tools__advancePhase",
|
|
54
|
+
"hooks": [
|
|
55
|
+
{
|
|
56
|
+
"type": "command",
|
|
57
|
+
"command": ".claude/hooks/gate-guardian.sh",
|
|
58
|
+
"timeout": 15000,
|
|
59
|
+
"on_failure": "warn"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
50
62
|
}
|
|
51
63
|
],
|
|
52
64
|
"PostToolUse": [
|
|
@@ -61,14 +73,8 @@
|
|
|
61
73
|
},
|
|
62
74
|
{
|
|
63
75
|
"type": "command",
|
|
64
|
-
"command": ".claude/hooks/
|
|
76
|
+
"command": ".claude/hooks/ping-pong-enforcer.sh",
|
|
65
77
|
"timeout": 15000,
|
|
66
|
-
"on_failure": "ignore"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"type": "command",
|
|
70
|
-
"command": ".claude/hooks/post-implement-validate.sh",
|
|
71
|
-
"timeout": 10000,
|
|
72
78
|
"on_failure": "warn"
|
|
73
79
|
},
|
|
74
80
|
{
|
|
@@ -79,6 +85,19 @@
|
|
|
79
85
|
}
|
|
80
86
|
]
|
|
81
87
|
}
|
|
88
|
+
],
|
|
89
|
+
"Stop": [
|
|
90
|
+
{
|
|
91
|
+
"matcher": "",
|
|
92
|
+
"hooks": [
|
|
93
|
+
{
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": ".claude/hooks/dangling-workflow-guard.sh",
|
|
96
|
+
"timeout": 15000,
|
|
97
|
+
"on_failure": "ignore"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
82
101
|
]
|
|
83
102
|
},
|
|
84
103
|
"enableAllProjectMcpServers": true,
|