@orchestrator-claude/cli 3.17.1 → 3.19.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.
Files changed (55) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/templates/base/CLAUDE.md.hbs +45 -28
  4. package/dist/templates/base/claude/agents/orchestrator.md +84 -117
  5. package/dist/templates/base/claude/hooks/dangling-guard.ts +53 -0
  6. package/dist/templates/base/claude/hooks/gate-guardian.ts +102 -0
  7. package/dist/templates/base/claude/hooks/lib/api-client.ts +293 -0
  8. package/dist/templates/base/claude/hooks/lib/git-checkpoint.ts +91 -0
  9. package/dist/templates/base/claude/hooks/package.json +13 -0
  10. package/dist/templates/base/claude/hooks/post-compact.ts +44 -0
  11. package/dist/templates/base/claude/hooks/session-start.ts +97 -0
  12. package/dist/templates/base/claude/hooks/subagent-start.ts +50 -0
  13. package/dist/templates/base/claude/hooks/subagent-stop.ts +57 -0
  14. package/dist/templates/base/claude/hooks/tsconfig.json +18 -0
  15. package/dist/templates/base/claude/hooks/user-prompt.ts +95 -0
  16. package/dist/templates/base/claude/hooks/workflow-guard.ts +120 -0
  17. package/dist/templates/base/claude/settings.json +23 -22
  18. package/dist/templates/base/claude/skills/orchestrator/SKILL.md +108 -0
  19. package/package.json +1 -1
  20. package/templates/base/CLAUDE.md.hbs +45 -28
  21. package/templates/base/claude/agents/orchestrator.md +84 -117
  22. package/templates/base/claude/hooks/dangling-guard.ts +53 -0
  23. package/templates/base/claude/hooks/gate-guardian.ts +102 -0
  24. package/templates/base/claude/hooks/lib/api-client.ts +293 -0
  25. package/templates/base/claude/hooks/lib/git-checkpoint.ts +91 -0
  26. package/templates/base/claude/hooks/package.json +13 -0
  27. package/templates/base/claude/hooks/post-compact.ts +44 -0
  28. package/templates/base/claude/hooks/session-start.ts +97 -0
  29. package/templates/base/claude/hooks/subagent-start.ts +50 -0
  30. package/templates/base/claude/hooks/subagent-stop.ts +57 -0
  31. package/templates/base/claude/hooks/tsconfig.json +18 -0
  32. package/templates/base/claude/hooks/user-prompt.ts +95 -0
  33. package/templates/base/claude/hooks/workflow-guard.ts +120 -0
  34. package/templates/base/claude/settings.json +23 -22
  35. package/templates/base/claude/skills/orchestrator/SKILL.md +108 -0
  36. package/dist/templates/base/claude/hooks/approval-guardian.sh +0 -62
  37. package/dist/templates/base/claude/hooks/dangling-workflow-guard.sh +0 -57
  38. package/dist/templates/base/claude/hooks/gate-guardian.sh +0 -84
  39. package/dist/templates/base/claude/hooks/orch-helpers.sh +0 -135
  40. package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +0 -58
  41. package/dist/templates/base/claude/hooks/post-phase-checkpoint.sh +0 -203
  42. package/dist/templates/base/claude/hooks/prompt-orchestrator.sh +0 -41
  43. package/dist/templates/base/claude/hooks/session-orchestrator.sh +0 -54
  44. package/dist/templates/base/claude/hooks/track-agent-invocation.sh +0 -230
  45. package/dist/templates/base/claude/hooks/workflow-guard.sh +0 -79
  46. package/templates/base/claude/hooks/approval-guardian.sh +0 -62
  47. package/templates/base/claude/hooks/dangling-workflow-guard.sh +0 -57
  48. package/templates/base/claude/hooks/gate-guardian.sh +0 -84
  49. package/templates/base/claude/hooks/orch-helpers.sh +0 -135
  50. package/templates/base/claude/hooks/ping-pong-enforcer.sh +0 -58
  51. package/templates/base/claude/hooks/post-phase-checkpoint.sh +0 -203
  52. package/templates/base/claude/hooks/prompt-orchestrator.sh +0 -41
  53. package/templates/base/claude/hooks/session-orchestrator.sh +0 -54
  54. package/templates/base/claude/hooks/track-agent-invocation.sh +0 -230
  55. package/templates/base/claude/hooks/workflow-guard.sh +0 -79
@@ -1,203 +0,0 @@
1
- #!/bin/bash
2
- # Post-Phase Checkpoint Hook (RFC-012 compatible)
3
- # Registers a checkpoint in PostgreSQL via the Orchestrator REST API.
4
- # Replaces the deprecated orchestrator-index.json approach (TD-105 F-04).
5
- #
6
- # Triggered by: PostToolUse on Task tool (after agent completes)
7
- # Behavior: Infers the just-completed phase from the active workflow state
8
- # and registers a checkpoint via the REST API.
9
- #
10
- # Required env vars:
11
- # ORCHESTRATOR_API_URL - Base URL (e.g. http://localhost:3000)
12
- # ORCHESTRATOR_PROJECT_TOKEN - Bearer token for API authentication
13
- #
14
- # Optional env vars:
15
- # ORCHESTRATOR_PROJECT_ID - Project ID for project-scoped workflow query
16
- # ORCH_CHECKPOINT_DEBUG=1 - Enable verbose logging
17
- #
18
- # Always exits 0 (non-blocking).
19
-
20
- set -euo pipefail
21
-
22
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
- STATE_DIR="$SCRIPT_DIR/../../.orchestrator/.state"
24
- LOG_FILE="${STATE_DIR}/checkpoint-hook.log"
25
- LAST_PHASE_FILE="${STATE_DIR}/last-checkpointed-phase"
26
- DEBUG="${ORCH_CHECKPOINT_DEBUG:-0}"
27
-
28
- mkdir -p "$STATE_DIR"
29
-
30
- log_info() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [INFO] $1" >> "$LOG_FILE"; }
31
- log_warn() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WARN] $1" >> "$LOG_FILE"; echo "[WARN] $1" >&2; }
32
- log_debug() { [ "$DEBUG" = "1" ] && echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [DEBUG] $1" >> "$LOG_FILE" || true; }
33
-
34
- # ── prerequisites ─────────────────────────────────────────────────────────────
35
-
36
- check_prerequisites() {
37
- if ! command -v curl &>/dev/null; then
38
- log_warn "curl not found. Checkpoints disabled."
39
- return 1
40
- fi
41
- if ! command -v jq &>/dev/null; then
42
- log_warn "jq not found. Checkpoints disabled."
43
- return 1
44
- fi
45
- if [ -z "${ORCHESTRATOR_API_URL:-}" ]; then
46
- log_debug "ORCHESTRATOR_API_URL not set. Checkpoints disabled."
47
- return 1
48
- fi
49
- if [ -z "${ORCHESTRATOR_PROJECT_TOKEN:-}" ]; then
50
- log_debug "ORCHESTRATOR_PROJECT_TOKEN not set. Checkpoints disabled."
51
- return 1
52
- fi
53
- return 0
54
- }
55
-
56
- # ── API helpers ────────────────────────────────────────────────────────────────
57
-
58
- get_active_workflow() {
59
- local project_id="${ORCHESTRATOR_PROJECT_ID:-}"
60
- local url="${ORCHESTRATOR_API_URL}/api/v1/workflows?status=in_progress&limit=1"
61
- [ -n "$project_id" ] && url="${url}&projectId=${project_id}"
62
-
63
- local tmpfile
64
- tmpfile=$(mktemp 2>/dev/null) || return 1
65
-
66
- curl -sf \
67
- -H "Authorization: Bearer ${ORCHESTRATOR_PROJECT_TOKEN}" \
68
- -H "Content-Type: application/json" \
69
- "${url}" \
70
- -o "$tmpfile" 2>/dev/null || { rm -f "$tmpfile"; return 1; }
71
-
72
- local result
73
- result=$(node -e "
74
- const fs=require('fs');
75
- try {
76
- const d=fs.readFileSync('${tmpfile}','utf8');
77
- const j=JSON.parse(d);
78
- const wf=Array.isArray(j)?j[0]:j;
79
- if(wf&&wf.id){ process.stdout.write(JSON.stringify(wf)); process.exit(0); }
80
- process.exit(1);
81
- } catch { process.exit(1); }" 2>/dev/null)
82
- local rc=$?
83
- rm -f "$tmpfile"
84
- [ $rc -eq 0 ] && echo "$result" || return 1
85
- }
86
-
87
- create_checkpoint() {
88
- local workflow_id="$1"
89
- local phase="$2"
90
- local description="$3"
91
-
92
- local body
93
- body=$(jq -n \
94
- --arg phase "$phase" \
95
- --arg desc "$description" \
96
- '{ phase: $phase, description: $desc }')
97
-
98
- local http_code
99
- http_code=$(curl -sf -o /dev/null -w "%{http_code}" \
100
- -X POST \
101
- -H "Authorization: Bearer ${ORCHESTRATOR_PROJECT_TOKEN}" \
102
- -H "Content-Type: application/json" \
103
- -d "$body" \
104
- "${ORCHESTRATOR_API_URL}/api/v1/workflows/${workflow_id}/checkpoints" 2>/dev/null) || echo ""
105
-
106
- echo "$http_code"
107
- }
108
-
109
- # ── dedup guard ────────────────────────────────────────────────────────────────
110
-
111
- get_saved_last_phase() {
112
- [ -f "$LAST_PHASE_FILE" ] && cat "$LAST_PHASE_FILE" || echo ""
113
- }
114
-
115
- save_last_phase() {
116
- echo "$1" > "$LAST_PHASE_FILE"
117
- }
118
-
119
- # ── phase inference ────────────────────────────────────────────────────────────
120
-
121
- # When workflow is at PLAN, it means SPECIFY just completed, etc.
122
- get_completed_phase() {
123
- local current="$1"
124
- local status="$2"
125
- case "$status" in
126
- "completed") echo "implement"; return ;;
127
- esac
128
- case "${current,,}" in
129
- "plan") echo "specify" ;;
130
- "tasks") echo "plan" ;;
131
- "implement") echo "tasks" ;;
132
- *) echo "" ;;
133
- esac
134
- }
135
-
136
- # ── main ───────────────────────────────────────────────────────────────────────
137
-
138
- main() {
139
- log_debug "Hook triggered"
140
-
141
- if ! check_prerequisites; then
142
- exit 0
143
- fi
144
-
145
- local workflow_json
146
- workflow_json=$(get_active_workflow 2>/dev/null) || workflow_json=""
147
-
148
- if [ -z "$workflow_json" ]; then
149
- log_debug "No active workflow found via API."
150
- exit 0
151
- fi
152
-
153
- local workflow_id current_phase workflow_status
154
- workflow_id=$(echo "$workflow_json" | jq -r '.id // empty' 2>/dev/null || echo "")
155
- current_phase=$(echo "$workflow_json" | jq -r '.currentPhase // empty' 2>/dev/null || echo "")
156
- workflow_status=$(echo "$workflow_json" | jq -r '.status // empty' 2>/dev/null || echo "")
157
-
158
- if [ -z "$workflow_id" ] || [ -z "$current_phase" ]; then
159
- log_debug "Could not parse workflow ID or phase."
160
- exit 0
161
- fi
162
-
163
- local completed_phase
164
- completed_phase=$(get_completed_phase "$current_phase" "$workflow_status")
165
-
166
- if [ -z "$completed_phase" ]; then
167
- log_debug "No completed phase inferred from current_phase=${current_phase}."
168
- exit 0
169
- fi
170
-
171
- # Dedup guard — skip if we already checkpointed this phase
172
- local saved_last
173
- saved_last=$(get_saved_last_phase)
174
- if [ "$completed_phase" = "$saved_last" ]; then
175
- log_debug "Phase ${completed_phase} already checkpointed. Skipping."
176
- exit 0
177
- fi
178
-
179
- local description="Auto-checkpoint after ${completed_phase} phase"
180
- local http_code
181
- http_code=$(create_checkpoint "$workflow_id" "$completed_phase" "$description")
182
-
183
- case "$http_code" in
184
- 201|200)
185
- save_last_phase "$completed_phase"
186
- log_info "Checkpoint registered: workflow=${workflow_id}, phase=${completed_phase}"
187
- ;;
188
- 409)
189
- save_last_phase "$completed_phase"
190
- log_info "Checkpoint already exists (409): phase=${completed_phase}"
191
- ;;
192
- "")
193
- log_warn "API unreachable. Checkpoint skipped for phase=${completed_phase}."
194
- ;;
195
- *)
196
- log_warn "Unexpected HTTP ${http_code} creating checkpoint for phase=${completed_phase}."
197
- ;;
198
- esac
199
-
200
- exit 0
201
- }
202
-
203
- main "$@"
@@ -1,41 +0,0 @@
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
@@ -1,54 +0,0 @@
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
@@ -1,230 +0,0 @@
1
- #!/bin/bash
2
- # Track Agent Invocation Hook
3
- # Registers agent invocations in orchestrator-index.json
4
- #
5
- # Usage (called by Claude Code hooks):
6
- # track-agent-invocation.sh start (reads stdin JSON)
7
- # track-agent-invocation.sh complete (reads stdin JSON)
8
- #
9
- # Claude Code passes JSON via stdin with structure:
10
- # { "tool_name": "Task", "tool_input": { "subagent_type": "...", ... } }
11
-
12
- set -e
13
-
14
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
16
- INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
17
- STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
18
- INVOCATION_FILE="$STATE_DIR/current-invocation"
19
- DEBUG_LOG="$STATE_DIR/hook-debug.log"
20
-
21
- # Ensure state directory exists
22
- mkdir -p "$STATE_DIR"
23
-
24
- ACTION="${1:-}"
25
-
26
- # Read stdin into variable (Claude Code passes JSON via stdin)
27
- STDIN_DATA=""
28
- if [ ! -t 0 ]; then
29
- STDIN_DATA=$(cat)
30
- fi
31
-
32
- case "$ACTION" in
33
- start)
34
- # Extract agent info from stdin JSON
35
- # Structure: { "tool_name": "Task", "tool_input": { "subagent_type": "...", ... } }
36
- if [ -n "$STDIN_DATA" ]; then
37
- # Debug: log received data
38
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START stdin: $STDIN_DATA" >> "$DEBUG_LOG"
39
-
40
- AGENT_NAME=$(echo "$STDIN_DATA" | node -e "
41
- let data = '';
42
- process.stdin.on('data', chunk => data += chunk);
43
- process.stdin.on('end', () => {
44
- try {
45
- const json = JSON.parse(data);
46
- // Try different structures
47
- const type = json.tool_input?.subagent_type
48
- || json.subagent_type
49
- || json.tool_input?.type
50
- || 'unknown';
51
- console.log(type);
52
- } catch { console.log('unknown'); }
53
- });
54
- ")
55
- PHASE=$(echo "$STDIN_DATA" | node -e "
56
- let data = '';
57
- process.stdin.on('data', chunk => data += chunk);
58
- process.stdin.on('end', () => {
59
- try {
60
- const json = JSON.parse(data);
61
- const type = json.tool_input?.subagent_type
62
- || json.subagent_type
63
- || json.tool_input?.type
64
- || 'unknown';
65
- const phaseMap = {
66
- 'specifier': 'specify',
67
- 'planner': 'plan',
68
- 'task-generator': 'tasks',
69
- 'implementer': 'implement',
70
- 'researcher': 'research',
71
- 'reviewer': 'review',
72
- 'orchestrator': 'orchestrate'
73
- };
74
- console.log(phaseMap[type] || type);
75
- } catch { console.log('unknown'); }
76
- });
77
- ")
78
- elif [ -n "$CLAUDE_TOOL_INPUT" ]; then
79
- # Fallback to environment variable
80
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START env: $CLAUDE_TOOL_INPUT" >> "$DEBUG_LOG"
81
-
82
- AGENT_NAME=$(echo "$CLAUDE_TOOL_INPUT" | node -e "
83
- let data = '';
84
- process.stdin.on('data', chunk => data += chunk);
85
- process.stdin.on('end', () => {
86
- try {
87
- const json = JSON.parse(data);
88
- const type = json.subagent_type || json.tool_input?.subagent_type || 'unknown';
89
- console.log(type);
90
- } catch { console.log('unknown'); }
91
- });
92
- ")
93
- PHASE="unknown"
94
- else
95
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START no input received" >> "$DEBUG_LOG"
96
- AGENT_NAME="unknown"
97
- PHASE="unknown"
98
- fi
99
-
100
- # Generate invocation ID
101
- INVOCATION_ID="inv-$(date +%s)-$(head -c 4 /dev/urandom | xxd -p)"
102
- STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
103
-
104
- # Get workflow ID from index
105
- WORKFLOW_ID=""
106
- if [ -f "$INDEX_FILE" ]; then
107
- WORKFLOW_ID=$(node -e "
108
- const fs = require('fs');
109
- try {
110
- const idx = JSON.parse(fs.readFileSync('$INDEX_FILE', 'utf8'));
111
- console.log(idx.activeWorkflow?.id || 'wf-standalone-' + Date.now());
112
- } catch { console.log('wf-standalone-' + Date.now()); }
113
- ")
114
- fi
115
-
116
- # Save invocation state
117
- echo "$INVOCATION_ID" > "$INVOCATION_FILE"
118
-
119
- # Update orchestrator-index.json
120
- if [ -f "$INDEX_FILE" ]; then
121
- node -e "
122
- const fs = require('fs');
123
- const indexPath = '$INDEX_FILE';
124
- const idx = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
125
-
126
- if (!idx.agentInvocations) idx.agentInvocations = [];
127
-
128
- idx.agentInvocations.push({
129
- id: '$INVOCATION_ID',
130
- agentName: '$AGENT_NAME',
131
- phase: '$PHASE',
132
- workflowId: '$WORKFLOW_ID',
133
- startedAt: '$STARTED_AT',
134
- completedAt: null,
135
- status: 'running',
136
- durationMs: 0
137
- });
138
-
139
- fs.writeFileSync(indexPath, JSON.stringify(idx, null, 2));
140
- console.log(JSON.stringify({ invocationId: '$INVOCATION_ID', startedAt: '$STARTED_AT' }));
141
- "
142
- else
143
- echo '{"invocationId": "'$INVOCATION_ID'", "startedAt": "'$STARTED_AT'", "warning": "No index file found"}'
144
- fi
145
- ;;
146
-
147
- complete)
148
- # Get invocation ID from state file
149
- INVOCATION_ID=""
150
- if [ -f "$INVOCATION_FILE" ]; then
151
- INVOCATION_ID=$(cat "$INVOCATION_FILE")
152
- fi
153
-
154
- if [ -z "$INVOCATION_ID" ]; then
155
- echo '{"success": false, "error": "No invocation ID found"}'
156
- exit 0 # Don't fail the hook
157
- fi
158
-
159
- # Debug log
160
- if [ -n "$STDIN_DATA" ]; then
161
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] COMPLETE stdin: $STDIN_DATA" >> "$DEBUG_LOG"
162
- fi
163
-
164
- # Determine status - default to success
165
- STATUS="success"
166
- SUMMARY="Completed"
167
-
168
- COMPLETED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
169
-
170
- # Update orchestrator-index.json
171
- if [ -f "$INDEX_FILE" ]; then
172
- node -e "
173
- const fs = require('fs');
174
- const indexPath = '$INDEX_FILE';
175
- const idx = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
176
-
177
- const invocations = idx.agentInvocations || [];
178
- const invIdx = invocations.findIndex(i => i.id === '$INVOCATION_ID');
179
-
180
- if (invIdx === -1) {
181
- console.log(JSON.stringify({ success: false, error: 'Invocation not found' }));
182
- process.exit(0);
183
- }
184
-
185
- const inv = invocations[invIdx];
186
- const startTime = new Date(inv.startedAt).getTime();
187
- const endTime = new Date('$COMPLETED_AT').getTime();
188
- const durationMs = endTime - startTime;
189
-
190
- invocations[invIdx] = {
191
- ...inv,
192
- completedAt: '$COMPLETED_AT',
193
- status: '$STATUS' === 'success' ? 'completed' : 'failed',
194
- durationMs,
195
- result: {
196
- status: '$STATUS',
197
- summary: '$SUMMARY'
198
- }
199
- };
200
-
201
- idx.agentInvocations = invocations;
202
-
203
- // Update statistics
204
- if (idx.statistics) {
205
- idx.statistics.totalInvocations = invocations.length;
206
- idx.statistics.lastActivity = '$COMPLETED_AT';
207
- }
208
-
209
- fs.writeFileSync(indexPath, JSON.stringify(idx, null, 2));
210
- console.log(JSON.stringify({ success: true, durationMs }));
211
- "
212
- else
213
- echo '{"success": false, "error": "No index file found"}'
214
- fi
215
-
216
- # Clean up state file
217
- rm -f "$INVOCATION_FILE"
218
- ;;
219
-
220
- *)
221
- echo "Usage: $0 {start|complete}"
222
- echo ""
223
- echo "This script is called by Claude Code hooks."
224
- echo "It reads JSON from stdin with structure:"
225
- echo ' { "tool_name": "Task", "tool_input": { "subagent_type": "...", ... } }'
226
- echo ""
227
- echo "Debug log: $DEBUG_LOG"
228
- exit 0
229
- ;;
230
- esac
@@ -1,79 +0,0 @@
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
- # Explicit bypass via env var (for direct implementation without dogfooding)
24
- if [ "${SKIP_WORKFLOW_GUARD:-}" = "true" ]; then
25
- orch_log "workflow-guard: ALLOW (SKIP_WORKFLOW_GUARD=true)"
26
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"Workflow guard bypassed via SKIP_WORKFLOW_GUARD=true."}}'
27
- exit 0
28
- fi
29
-
30
- # Only guard src/ and tests/ paths (production code)
31
- case "$FILE_PATH" in
32
- */src/*|*/tests/*)
33
- ;;
34
- *)
35
- orch_log "workflow-guard: ALLOW (non-guarded path)"
36
- exit 0
37
- ;;
38
- esac
39
-
40
- # Allow config and non-code files
41
- case "$FILE_PATH" in
42
- *.json|*.yml|*.yaml|*.md|*.env|*.env.*)
43
- orch_log "workflow-guard: ALLOW (config/doc file)"
44
- exit 0
45
- ;;
46
- esac
47
-
48
- # Check for active workflow
49
- WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
50
-
51
- if [ -n "$WORKFLOW_ID" ]; then
52
- # Check if running inside a sub-agent (agent_id present) or main agent (absent)
53
- AGENT_ID=$(orch_json_field "$STDIN_DATA" "agent_id")
54
-
55
- if [ -n "$AGENT_ID" ]; then
56
- # Sub-agent writing — ALLOW
57
- AGENT_TYPE=$(orch_json_field "$STDIN_DATA" "agent_type")
58
- orch_log "workflow-guard: ALLOW (sub-agent: ${AGENT_TYPE:-unknown}, workflow: $WORKFLOW_ID)"
59
- echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Active workflow: ${WORKFLOW_ID}. Sub-agent ${AGENT_TYPE:-unknown} write allowed.\"}}"
60
- exit 0
61
- fi
62
-
63
- # Main agent writing directly — check if SKIP_SUBAGENT_GUARD allows it
64
- if [ "${SKIP_SUBAGENT_GUARD:-}" = "true" ]; then
65
- orch_log "workflow-guard: ALLOW (SKIP_SUBAGENT_GUARD=true, workflow: $WORKFLOW_ID)"
66
- echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Active workflow: ${WORKFLOW_ID}. Direct write allowed via SKIP_SUBAGENT_GUARD.\"}}"
67
- exit 0
68
- fi
69
-
70
- # Main agent writing directly — DENY, must use sub-agent
71
- orch_log "workflow-guard: DENY (main agent direct write, no sub-agent invocation)"
72
- echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Workflow Guard: Direct code writes are blocked. You must invoke a sub-agent (e.g. implementer) to write code. The sub-agent will have write access.\",\"additionalContext\":\"Use the Agent tool to spawn an implementer sub-agent for code changes. The workflow-guard allows writes only from sub-agents (identified by agent_id in hook input).\"}}"
73
- exit 0
74
- fi
75
-
76
- # No active workflow — DENY with structured instructions
77
- orch_log "workflow-guard: DENY (no active workflow)"
78
- 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."}}'
79
- exit 0
@@ -1,62 +0,0 @@
1
- #!/bin/bash
2
- # approval-guardian.sh — TD-128 F-09 Hook
3
- # Trigger: PreToolUse on mcp__orchestrator-tools__approveAction and mcp__orchestrator-extended__completeWorkflow
4
- # Purpose: Block auto-approve and auto-complete when workflow is awaiting_approval.
5
- # Requires explicit human confirmation before proceeding.
6
- #
7
- # Output: JSON with permissionDecision (deny/allow)
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
- TOOL_NAME=$(orch_json_field "$STDIN_DATA" "tool_name")
16
- orch_log "APPROVAL-GUARDIAN: PreToolUse $TOOL_NAME triggered"
17
-
18
- # Extract workflow ID from tool_input
19
- WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "tool_input.workflowId")
20
- [ -z "$WORKFLOW_ID" ] && WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "workflowId")
21
-
22
- if [ -z "$WORKFLOW_ID" ]; then
23
- # No workflow ID — allow (fail-open for non-workflow calls)
24
- orch_log "APPROVAL-GUARDIAN: ALLOW (no workflowId)"
25
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"No workflowId found, allowing."}}'
26
- exit 0
27
- fi
28
-
29
- # Get auth token
30
- TOKEN=$(orch_get_token 2>/dev/null) || TOKEN=""
31
- if [ -z "$TOKEN" ]; then
32
- # Can't check status — fail-open (auth issues shouldn't block workflow completion)
33
- orch_log "APPROVAL-GUARDIAN: ALLOW (auth failed, fail-open)"
34
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"Auth failed, allowing."}}'
35
- exit 0
36
- fi
37
-
38
- # Get workflow status
39
- STATUS=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}" \
40
- -H "Authorization: Bearer $TOKEN" \
41
- -H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || STATUS=""
42
-
43
- if [ -z "$STATUS" ]; then
44
- orch_log "APPROVAL-GUARDIAN: ALLOW (could not fetch workflow status)"
45
- echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"Could not fetch workflow status, allowing."}}'
46
- exit 0
47
- fi
48
-
49
- WORKFLOW_STATUS=$(orch_json_field "$STATUS" "status")
50
- orch_log "APPROVAL-GUARDIAN: workflow=$WORKFLOW_ID status=$WORKFLOW_STATUS tool=$TOOL_NAME"
51
-
52
- # Block approveAction AND completeWorkflow when awaiting_approval
53
- if [ "$WORKFLOW_STATUS" = "awaiting_approval" ]; then
54
- orch_log "APPROVAL-GUARDIAN: DENY (workflow awaiting_approval — human confirmation required for $TOOL_NAME)"
55
- echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Approval Guardian: Workflow is awaiting human approval. You MUST ask the user for explicit confirmation before calling ${TOOL_NAME}.\",\"additionalContext\":\"Present the workflow summary to the user and ask: 'Do you approve advancing to IMPLEMENT?' Wait for their response. Do NOT auto-approve or auto-complete.\"}}"
56
- exit 0
57
- fi
58
-
59
- # All other statuses: ALLOW
60
- orch_log "APPROVAL-GUARDIAN: ALLOW ($TOOL_NAME, status=$WORKFLOW_STATUS)"
61
- echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"${TOOL_NAME} allowed (status=${WORKFLOW_STATUS}).\"}}"
62
- exit 0