@orchestrator-claude/cli 3.10.2 → 3.12.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.md.hbs +35 -332
- package/dist/templates/base/claude/hooks/dangling-workflow-guard.sh +57 -0
- package/dist/templates/base/claude/hooks/gate-guardian.sh +79 -0
- package/dist/templates/base/claude/hooks/orch-helpers.sh +133 -0
- package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +58 -0
- package/dist/templates/base/claude/hooks/prompt-orchestrator.sh +41 -0
- package/dist/templates/base/claude/hooks/session-orchestrator.sh +54 -0
- package/dist/templates/base/claude/hooks/workflow-guard.sh +53 -0
- package/dist/templates/base/claude/settings.json +63 -7
- package/dist/templates/base/claude/skills/workflow-status/SKILL.md +59 -291
- package/dist/templates/base/docker-compose.yml.hbs +2 -1
- package/package.json +1 -1
- package/templates/base/CLAUDE.md.hbs +35 -332
- package/templates/base/claude/hooks/dangling-workflow-guard.sh +57 -0
- package/templates/base/claude/hooks/gate-guardian.sh +79 -0
- package/templates/base/claude/hooks/orch-helpers.sh +133 -0
- package/templates/base/claude/hooks/ping-pong-enforcer.sh +58 -0
- package/templates/base/claude/hooks/prompt-orchestrator.sh +41 -0
- package/templates/base/claude/hooks/session-orchestrator.sh +54 -0
- package/templates/base/claude/hooks/workflow-guard.sh +53 -0
- package/templates/base/claude/settings.json +63 -7
- package/templates/base/claude/skills/workflow-status/SKILL.md +59 -291
- package/templates/base/docker-compose.yml.hbs +2 -1
- package/dist/templates/base/claude/hooks/post-artifact-generate.sh +0 -39
- package/dist/templates/base/claude/hooks/post-implement-validate.sh +0 -139
- package/dist/templates/base/claude/hooks/pre-agent-invoke.sh +0 -34
- package/dist/templates/base/claude/hooks/pre-phase-advance.sh +0 -40
- package/templates/base/claude/hooks/post-artifact-generate.sh +0 -39
- package/templates/base/claude/hooks/post-implement-validate.sh +0 -139
- package/templates/base/claude/hooks/pre-agent-invoke.sh +0 -34
- package/templates/base/claude/hooks/pre-phase-advance.sh +0 -40
|
@@ -0,0 +1,133 @@
|
|
|
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 (non-terminal) workflow ID
|
|
64
|
+
# Checks in_progress, awaiting_agent, and awaiting_approval statuses
|
|
65
|
+
orch_get_active_workflow() {
|
|
66
|
+
local token
|
|
67
|
+
token=$(orch_get_token) || return 1
|
|
68
|
+
|
|
69
|
+
local status id
|
|
70
|
+
for status in in_progress awaiting_agent awaiting_approval; do
|
|
71
|
+
local resp
|
|
72
|
+
resp=$(curl -sf --max-time 3 "${API_URL}/api/v1/workflows?status=${status}&limit=1" \
|
|
73
|
+
-H "Authorization: Bearer $token" \
|
|
74
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || continue
|
|
75
|
+
|
|
76
|
+
id=$(echo "$resp" | node -e "
|
|
77
|
+
let d='';
|
|
78
|
+
process.stdin.on('data',c=>d+=c);
|
|
79
|
+
process.stdin.on('end',()=>{
|
|
80
|
+
try {
|
|
81
|
+
const j=JSON.parse(d);
|
|
82
|
+
const wfs=j.data||j;
|
|
83
|
+
const wf=Array.isArray(wfs)?wfs[0]:wfs;
|
|
84
|
+
const id=wf?.id||'';
|
|
85
|
+
if(id) console.log(id); else process.exit(1);
|
|
86
|
+
} catch { process.exit(1); }
|
|
87
|
+
});" 2>/dev/null) && [ -n "$id" ] && echo "$id" && return 0
|
|
88
|
+
done
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Call getNextAction for a workflow
|
|
93
|
+
orch_get_next_action() {
|
|
94
|
+
local workflow_id="$1"
|
|
95
|
+
local token
|
|
96
|
+
token=$(orch_get_token) || return 1
|
|
97
|
+
|
|
98
|
+
curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/pending-action" \
|
|
99
|
+
-H "Authorization: Bearer $token" \
|
|
100
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Call evaluateGate for a workflow
|
|
104
|
+
orch_evaluate_gate() {
|
|
105
|
+
local workflow_id="$1"
|
|
106
|
+
local target_phase="$2"
|
|
107
|
+
local token
|
|
108
|
+
token=$(orch_get_token) || return 1
|
|
109
|
+
|
|
110
|
+
curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${workflow_id}/gate/evaluate" \
|
|
111
|
+
-H "Content-Type: application/json" \
|
|
112
|
+
-H "Authorization: Bearer $token" \
|
|
113
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
114
|
+
-d "{\"targetPhase\":\"${target_phase}\"}" 2>/dev/null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Extract a field from JSON string using node
|
|
118
|
+
orch_json_field() {
|
|
119
|
+
local json="$1"
|
|
120
|
+
local field="$2"
|
|
121
|
+
echo "$json" | node -e "
|
|
122
|
+
let d='';
|
|
123
|
+
process.stdin.on('data',c=>d+=c);
|
|
124
|
+
process.stdin.on('end',()=>{
|
|
125
|
+
try {
|
|
126
|
+
const j=JSON.parse(d);
|
|
127
|
+
const parts='${field}'.split('.');
|
|
128
|
+
let v=j;
|
|
129
|
+
for(const p of parts) v=v?.[p];
|
|
130
|
+
console.log(v||'');
|
|
131
|
+
} catch { console.log(''); }
|
|
132
|
+
});" 2>/dev/null
|
|
133
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ping-pong-enforcer.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
+
# Trigger: PostToolUse on Task (Agent tool)
|
|
4
|
+
# Purpose: After ANY sub-agent returns, call getNextAction and inject
|
|
5
|
+
# the result as additionalContext into the main conversation.
|
|
6
|
+
#
|
|
7
|
+
# Output: JSON with additionalContext containing next action instructions
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
13
|
+
|
|
14
|
+
orch_log "PING-PONG: PostToolUse Agent triggered"
|
|
15
|
+
|
|
16
|
+
# Find active workflow
|
|
17
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
18
|
+
|
|
19
|
+
if [ -z "$WORKFLOW_ID" ]; then
|
|
20
|
+
orch_log "PING-PONG: No active workflow, skipping"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
orch_log "PING-PONG: Active workflow=$WORKFLOW_ID"
|
|
25
|
+
|
|
26
|
+
# Call getNextAction
|
|
27
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || NEXT_ACTION=""
|
|
28
|
+
|
|
29
|
+
if [ -z "$NEXT_ACTION" ]; then
|
|
30
|
+
orch_log "PING-PONG: getNextAction failed or empty"
|
|
31
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":\"[PING-PONG] Workflow ${WORKFLOW_ID} is active but getNextAction returned empty. Check workflow status with mcp__orchestrator-tools__getStatus.\"}}"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Extract key fields for the context message
|
|
36
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
37
|
+
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
38
|
+
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
39
|
+
PROMPT=$(orch_json_field "$NEXT_ACTION" "pendingAction.prompt")
|
|
40
|
+
|
|
41
|
+
orch_log "PING-PONG: hasAction=$HAS_ACTION agent=$AGENT status=$STATUS"
|
|
42
|
+
|
|
43
|
+
if [ "$HAS_ACTION" = "true" ]; then
|
|
44
|
+
if [ "$STATUS" = "awaiting_agent" ]; then
|
|
45
|
+
CONTEXT="[PING-PONG] Next action for workflow ${WORKFLOW_ID}: Invoke agent '${AGENT}' via Agent tool. Status: awaiting_agent."
|
|
46
|
+
[ -n "$PROMPT" ] && CONTEXT="${CONTEXT} Prompt: ${PROMPT}"
|
|
47
|
+
elif [ "$STATUS" = "awaiting_approval" ]; then
|
|
48
|
+
CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} requires human approval before continuing. Ask the user to approve, then call mcp__orchestrator-tools__approveAction."
|
|
49
|
+
else
|
|
50
|
+
CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} pending action: agent=${AGENT}, status=${STATUS}."
|
|
51
|
+
fi
|
|
52
|
+
else
|
|
53
|
+
CONTEXT="[PING-PONG] No pending actions for workflow ${WORKFLOW_ID}. If current phase is implement, call mcp__orchestrator-extended__completeWorkflow to finalize."
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Output structured JSON
|
|
57
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$(echo "$CONTEXT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.stringify(d)))" 2>/dev/null)}}"
|
|
58
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# prompt-orchestrator.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
+
# Trigger: UserPromptSubmit
|
|
4
|
+
# Purpose: On every user prompt, inject orchestrator context.
|
|
5
|
+
# If no workflow active, remind about workflow requirement.
|
|
6
|
+
# If workflow active, inject current state.
|
|
7
|
+
#
|
|
8
|
+
# Output: JSON with additionalContext (never blocks prompts)
|
|
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 "PROMPT-ORCH: UserPromptSubmit triggered"
|
|
16
|
+
|
|
17
|
+
# Find active workflow
|
|
18
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
19
|
+
|
|
20
|
+
if [ -z "$WORKFLOW_ID" ]; then
|
|
21
|
+
# No workflow — inject reminder (lightweight, no API calls)
|
|
22
|
+
orch_log "PROMPT-ORCH: No active workflow, injecting reminder"
|
|
23
|
+
echo '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"[ORCHESTRATOR] No active workflow. If this request involves creating, modifying, or fixing code, you must start a workflow first (detectWorkflow → startWorkflow). The workflow-guard hook will block writes to src/ without a workflow."}}'
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Active workflow — inject state
|
|
28
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || NEXT_ACTION=""
|
|
29
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
30
|
+
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
31
|
+
PA_STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
32
|
+
|
|
33
|
+
if [ "$HAS_ACTION" = "true" ]; then
|
|
34
|
+
CONTEXT="[ORCHESTRATOR] Active workflow: ${WORKFLOW_ID}. Next: invoke '${AGENT}' (${PA_STATUS})."
|
|
35
|
+
else
|
|
36
|
+
CONTEXT="[ORCHESTRATOR] Active workflow: ${WORKFLOW_ID}. No pending actions."
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
orch_log "PROMPT-ORCH: workflow=$WORKFLOW_ID hasAction=$HAS_ACTION"
|
|
40
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"UserPromptSubmit\",\"additionalContext\":$(echo "$CONTEXT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.stringify(d)))" 2>/dev/null)}}"
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# session-orchestrator.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
+
# Trigger: SessionStart
|
|
4
|
+
# Purpose: On session start, check for active workflow and inject context.
|
|
5
|
+
# Helps the LLM resume work from where it left off.
|
|
6
|
+
#
|
|
7
|
+
# Output: JSON with additionalContext containing workflow state
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
13
|
+
|
|
14
|
+
orch_log "SESSION-ORCH: SessionStart hook triggered"
|
|
15
|
+
|
|
16
|
+
# Find active workflow
|
|
17
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
18
|
+
|
|
19
|
+
if [ -z "$WORKFLOW_ID" ]; then
|
|
20
|
+
orch_log "SESSION-ORCH: No active workflow"
|
|
21
|
+
echo '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[ORCHESTRATOR] No active workflow. For feature requests, bug fixes, or refactoring, start with:\n1. mcp__orchestrator-tools__detectWorkflow\n2. mcp__orchestrator-tools__startWorkflow\n3. Follow the nextStep returned\n\nDirect implementation is blocked by the workflow-guard hook."}}'
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Get workflow details
|
|
26
|
+
TOKEN=$(orch_get_token 2>/dev/null) || TOKEN=""
|
|
27
|
+
if [ -z "$TOKEN" ]; then
|
|
28
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"[ORCHESTRATOR] Active workflow: ${WORKFLOW_ID} (could not fetch details — auth failed).\"}}"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
STATUS_RESP=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/status" \
|
|
33
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
34
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || STATUS_RESP=""
|
|
35
|
+
|
|
36
|
+
PHASE=$(orch_json_field "$STATUS_RESP" "currentPhase")
|
|
37
|
+
STATUS=$(orch_json_field "$STATUS_RESP" "status")
|
|
38
|
+
WF_TYPE=$(orch_json_field "$STATUS_RESP" "type")
|
|
39
|
+
|
|
40
|
+
# Get next action
|
|
41
|
+
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || NEXT_ACTION=""
|
|
42
|
+
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
43
|
+
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
44
|
+
PA_STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
45
|
+
|
|
46
|
+
CONTEXT="[ORCHESTRATOR] Active workflow: ${WORKFLOW_ID}\nType: ${WF_TYPE}\nPhase: ${PHASE}\nStatus: ${STATUS}"
|
|
47
|
+
|
|
48
|
+
if [ "$HAS_ACTION" = "true" ]; then
|
|
49
|
+
CONTEXT="${CONTEXT}\nNext action: invoke '${AGENT}' (${PA_STATUS})"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
orch_log "SESSION-ORCH: Injecting workflow context (wf=$WORKFLOW_ID phase=$PHASE)"
|
|
53
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":$(echo "$CONTEXT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.stringify(d)))" 2>/dev/null)}}"
|
|
54
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# workflow-guard.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
+
# Trigger: PreToolUse on Write|Edit
|
|
4
|
+
# Purpose: Block writes to src/ and tests/ when no active workflow exists.
|
|
5
|
+
#
|
|
6
|
+
# Output: JSON with permissionDecision (deny/allow) + additionalContext
|
|
7
|
+
# Exit 0 with JSON = structured decision
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
13
|
+
|
|
14
|
+
STDIN_DATA=$(orch_read_stdin)
|
|
15
|
+
|
|
16
|
+
# Extract file path (Claude Code wraps in tool_input)
|
|
17
|
+
FILE_PATH=$(orch_json_field "$STDIN_DATA" "tool_input.file_path")
|
|
18
|
+
[ -z "$FILE_PATH" ] && FILE_PATH=$(orch_json_field "$STDIN_DATA" "file_path")
|
|
19
|
+
[ -z "$FILE_PATH" ] && FILE_PATH=$(orch_json_field "$STDIN_DATA" "input.file_path")
|
|
20
|
+
|
|
21
|
+
orch_log "workflow-guard: file_path=$FILE_PATH"
|
|
22
|
+
|
|
23
|
+
# Only guard src/ and tests/ paths (production code)
|
|
24
|
+
case "$FILE_PATH" in
|
|
25
|
+
*/src/*|*/tests/*)
|
|
26
|
+
;;
|
|
27
|
+
*)
|
|
28
|
+
orch_log "workflow-guard: ALLOW (non-guarded path)"
|
|
29
|
+
exit 0
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
# Allow config and non-code files
|
|
34
|
+
case "$FILE_PATH" in
|
|
35
|
+
*.json|*.yml|*.yaml|*.md|*.env|*.env.*)
|
|
36
|
+
orch_log "workflow-guard: ALLOW (config/doc file)"
|
|
37
|
+
exit 0
|
|
38
|
+
;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
# Check for active workflow
|
|
42
|
+
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
43
|
+
|
|
44
|
+
if [ -n "$WORKFLOW_ID" ]; then
|
|
45
|
+
orch_log "workflow-guard: ALLOW (active workflow: $WORKFLOW_ID)"
|
|
46
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Active workflow: ${WORKFLOW_ID}. Write allowed.\"}}"
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# No active workflow — DENY with structured instructions
|
|
51
|
+
orch_log "workflow-guard: DENY (no active workflow)"
|
|
52
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Workflow Guard: No active workflow. Direct implementation is not allowed.","additionalContext":"You MUST start a workflow before writing code:\n1. mcp__orchestrator-tools__detectWorkflow({ prompt: \"...\" })\n2. mcp__orchestrator-tools__startWorkflow({ workflowType: \"...\", prompt: \"...\" })\n3. Follow the nextStep returned by startWorkflow\n\nThe workflow-guard hook blocks all writes to src/ and tests/ without an active workflow."}}'
|
|
53
|
+
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
|
],
|
|
@@ -36,6 +37,32 @@
|
|
|
36
37
|
"NODE_ENV": "development"
|
|
37
38
|
},
|
|
38
39
|
"hooks": {
|
|
40
|
+
"SessionStart": [
|
|
41
|
+
{
|
|
42
|
+
"matcher": "startup",
|
|
43
|
+
"hooks": [
|
|
44
|
+
{
|
|
45
|
+
"type": "command",
|
|
46
|
+
"command": ".claude/hooks/session-orchestrator.sh",
|
|
47
|
+
"timeout": 10000,
|
|
48
|
+
"on_failure": "ignore"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"UserPromptSubmit": [
|
|
54
|
+
{
|
|
55
|
+
"matcher": "",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": ".claude/hooks/prompt-orchestrator.sh",
|
|
60
|
+
"timeout": 10000,
|
|
61
|
+
"on_failure": "ignore"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
39
66
|
"PreToolUse": [
|
|
40
67
|
{
|
|
41
68
|
"matcher": "Task",
|
|
@@ -47,6 +74,28 @@
|
|
|
47
74
|
"on_failure": "ignore"
|
|
48
75
|
}
|
|
49
76
|
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"matcher": "mcp__orchestrator-tools__advancePhase",
|
|
80
|
+
"hooks": [
|
|
81
|
+
{
|
|
82
|
+
"type": "command",
|
|
83
|
+
"command": ".claude/hooks/gate-guardian.sh",
|
|
84
|
+
"timeout": 15000,
|
|
85
|
+
"on_failure": "warn"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"matcher": "Write|Edit",
|
|
91
|
+
"hooks": [
|
|
92
|
+
{
|
|
93
|
+
"type": "command",
|
|
94
|
+
"command": ".claude/hooks/workflow-guard.sh",
|
|
95
|
+
"timeout": 10000,
|
|
96
|
+
"on_failure": "warn"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
50
99
|
}
|
|
51
100
|
],
|
|
52
101
|
"PostToolUse": [
|
|
@@ -61,14 +110,8 @@
|
|
|
61
110
|
},
|
|
62
111
|
{
|
|
63
112
|
"type": "command",
|
|
64
|
-
"command": ".claude/hooks/
|
|
113
|
+
"command": ".claude/hooks/ping-pong-enforcer.sh",
|
|
65
114
|
"timeout": 15000,
|
|
66
|
-
"on_failure": "ignore"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"type": "command",
|
|
70
|
-
"command": ".claude/hooks/post-implement-validate.sh",
|
|
71
|
-
"timeout": 10000,
|
|
72
115
|
"on_failure": "warn"
|
|
73
116
|
},
|
|
74
117
|
{
|
|
@@ -79,6 +122,19 @@
|
|
|
79
122
|
}
|
|
80
123
|
]
|
|
81
124
|
}
|
|
125
|
+
],
|
|
126
|
+
"Stop": [
|
|
127
|
+
{
|
|
128
|
+
"matcher": "",
|
|
129
|
+
"hooks": [
|
|
130
|
+
{
|
|
131
|
+
"type": "command",
|
|
132
|
+
"command": ".claude/hooks/dangling-workflow-guard.sh",
|
|
133
|
+
"timeout": 15000,
|
|
134
|
+
"on_failure": "ignore"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
82
138
|
]
|
|
83
139
|
},
|
|
84
140
|
"enableAllProjectMcpServers": true,
|