@orchestrator-claude/cli 3.11.0 → 3.12.1

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 (35) 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 +35 -332
  4. package/dist/templates/base/claude/hooks/dangling-workflow-guard.sh +33 -41
  5. package/dist/templates/base/claude/hooks/gate-guardian.sh +42 -43
  6. package/dist/templates/base/claude/hooks/orch-helpers.sh +25 -20
  7. package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +26 -40
  8. package/dist/templates/base/claude/hooks/post-phase-checkpoint.sh +135 -254
  9. package/dist/templates/base/claude/hooks/prompt-orchestrator.sh +41 -0
  10. package/dist/templates/base/claude/hooks/session-orchestrator.sh +54 -0
  11. package/dist/templates/base/claude/hooks/workflow-guard.sh +53 -0
  12. package/dist/templates/base/claude/settings.json +37 -0
  13. package/dist/templates/base/claude/skills/workflow-status/SKILL.md +59 -291
  14. package/dist/templates/base/docker-compose.yml.hbs +2 -1
  15. package/package.json +1 -1
  16. package/templates/base/CLAUDE.md.hbs +35 -332
  17. package/templates/base/claude/hooks/dangling-workflow-guard.sh +33 -41
  18. package/templates/base/claude/hooks/gate-guardian.sh +42 -43
  19. package/templates/base/claude/hooks/orch-helpers.sh +25 -20
  20. package/templates/base/claude/hooks/ping-pong-enforcer.sh +26 -40
  21. package/templates/base/claude/hooks/post-phase-checkpoint.sh +135 -254
  22. package/templates/base/claude/hooks/prompt-orchestrator.sh +41 -0
  23. package/templates/base/claude/hooks/session-orchestrator.sh +54 -0
  24. package/templates/base/claude/hooks/workflow-guard.sh +53 -0
  25. package/templates/base/claude/settings.json +37 -0
  26. package/templates/base/claude/skills/workflow-status/SKILL.md +59 -291
  27. package/templates/base/docker-compose.yml.hbs +2 -1
  28. package/dist/templates/base/claude/hooks/post-artifact-generate.sh +0 -39
  29. package/dist/templates/base/claude/hooks/post-implement-validate.sh +0 -139
  30. package/dist/templates/base/claude/hooks/pre-agent-invoke.sh +0 -34
  31. package/dist/templates/base/claude/hooks/pre-phase-advance.sh +0 -40
  32. package/templates/base/claude/hooks/post-artifact-generate.sh +0 -39
  33. package/templates/base/claude/hooks/post-implement-validate.sh +0 -139
  34. package/templates/base/claude/hooks/pre-agent-invoke.sh +0 -34
  35. package/templates/base/claude/hooks/pre-phase-advance.sh +0 -40
@@ -60,28 +60,33 @@ orch_get_token() {
60
60
  fi
61
61
  }
62
62
 
63
- # Get the active (in_progress) workflow ID
63
+ # Get the active (non-terminal) workflow ID
64
+ # Checks in_progress, awaiting_agent, and awaiting_approval statuses
64
65
  orch_get_active_workflow() {
65
66
  local token
66
67
  token=$(orch_get_token) || return 1
67
68
 
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
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
85
90
  }
86
91
 
87
92
  # Call getNextAction for a workflow
@@ -90,7 +95,7 @@ orch_get_next_action() {
90
95
  local token
91
96
  token=$(orch_get_token) || return 1
92
97
 
93
- curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/next-action" \
98
+ curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/pending-action" \
94
99
  -H "Authorization: Bearer $token" \
95
100
  -H "X-Project-ID: $PROJECT_ID" 2>/dev/null
96
101
  }
@@ -102,7 +107,7 @@ orch_evaluate_gate() {
102
107
  local token
103
108
  token=$(orch_get_token) || return 1
104
109
 
105
- curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${workflow_id}/evaluate-gate" \
110
+ curl -sf --max-time 10 -X POST "${API_URL}/api/v1/workflows/${workflow_id}/gate/evaluate" \
106
111
  -H "Content-Type: application/json" \
107
112
  -H "Authorization: Bearer $token" \
108
113
  -H "X-Project-ID: $PROJECT_ID" \
@@ -1,72 +1,58 @@
1
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.
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
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).
7
+ # Output: JSON with additionalContext containing next action instructions
12
8
 
13
9
  set -euo pipefail
14
10
 
15
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
12
  source "$SCRIPT_DIR/orch-helpers.sh"
17
13
 
18
- # Read stdin (Claude Code passes tool result JSON)
19
- STDIN_DATA=$(orch_read_stdin)
20
14
  orch_log "PING-PONG: PostToolUse Agent triggered"
21
15
 
22
16
  # Find active workflow
23
- WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || {
17
+ WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
18
+
19
+ if [ -z "$WORKFLOW_ID" ]; then
24
20
  orch_log "PING-PONG: No active workflow, skipping"
25
21
  exit 0
26
- }
22
+ fi
27
23
 
28
24
  orch_log "PING-PONG: Active workflow=$WORKFLOW_ID"
29
25
 
30
26
  # Call getNextAction
31
- NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || {
32
- orch_log "PING-PONG: getNextAction failed, skipping"
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.\"}}"
33
32
  exit 0
34
- }
33
+ fi
35
34
 
36
- # Parse response
35
+ # Extract key fields for the context message
37
36
  HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
38
- STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
39
37
  AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
40
- CURRENT_PHASE=$(orch_json_field "$NEXT_ACTION" "currentPhase")
38
+ STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
39
+ PROMPT=$(orch_json_field "$NEXT_ACTION" "pendingAction.prompt")
41
40
 
42
- orch_log "PING-PONG: hasAction=$HAS_ACTION status=$STATUS agent=$AGENT phase=$CURRENT_PHASE"
41
+ orch_log "PING-PONG: hasAction=$HAS_ACTION agent=$AGENT status=$STATUS"
43
42
 
44
- # Inject result into conversation
45
43
  if [ "$HAS_ACTION" = "true" ]; then
46
44
  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"
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}"
51
47
  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' })"
48
+ CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} requires human approval before continuing. Ask the user to approve, then call mcp__orchestrator-tools__approveAction."
56
49
  else
57
- echo ""
58
- echo "[ORCHESTRATOR] Workflow $WORKFLOW_ID — pending action status: $STATUS"
59
- echo "Full response: $NEXT_ACTION"
50
+ CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} pending action: agent=${AGENT}, status=${STATUS}."
60
51
  fi
61
52
  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
53
+ CONTEXT="[PING-PONG] No pending actions for workflow ${WORKFLOW_ID}. If current phase is implement, call mcp__orchestrator-extended__completeWorkflow to finalize."
70
54
  fi
71
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)}}"
72
58
  exit 0
@@ -1,322 +1,203 @@
1
1
  #!/bin/bash
2
- # Post-Phase Checkpoint Hook
3
- # Automatically creates git checkpoints when workflow phases complete
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).
4
5
  #
5
6
  # Triggered by: PostToolUse on Task tool (after agent completes)
6
- # Behavior: Detects phase transitions and creates git commits
7
+ # Behavior: Infers the just-completed phase from the active workflow state
8
+ # and registers a checkpoint via the REST API.
7
9
  #
8
- # Requirements:
9
- # - jq (for JSON parsing)
10
- # - git (for checkpoint commits)
10
+ # Required env vars:
11
+ # ORCHESTRATOR_API_URL - Base URL (e.g. http://localhost:3000)
12
+ # ORCHESTRATOR_PROJECT_TOKEN - Bearer token for API authentication
11
13
  #
12
- # Debug mode: ORCH_CHECKPOINT_DEBUG=1 to enable verbose logging
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).
13
19
 
14
- set -e
20
+ set -euo pipefail
15
21
 
16
- # Configuration
17
22
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
19
- INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
20
- STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
21
- LOG_FILE="$STATE_DIR/checkpoint-hook.log"
22
- LAST_PHASE_FILE="$STATE_DIR/last-checkpointed-phase"
23
-
24
- # Ensure state directory exists
25
- mkdir -p "$STATE_DIR"
26
-
27
- # Debug mode
23
+ STATE_DIR="$SCRIPT_DIR/../../.orchestrator/.state"
24
+ LOG_FILE="${STATE_DIR}/checkpoint-hook.log"
25
+ LAST_PHASE_FILE="${STATE_DIR}/last-checkpointed-phase"
28
26
  DEBUG="${ORCH_CHECKPOINT_DEBUG:-0}"
29
27
 
30
- # Logging functions
31
- log_debug() {
32
- if [ "$DEBUG" = "1" ]; then
33
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [DEBUG] $1" >> "$LOG_FILE"
34
- echo "[DEBUG] $1" >&2
35
- fi
36
- }
37
-
38
- log_info() {
39
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [INFO] $1" >> "$LOG_FILE"
40
- if [ "$DEBUG" = "1" ]; then
41
- echo "[INFO] $1" >&2
42
- fi
43
- }
28
+ mkdir -p "$STATE_DIR"
44
29
 
45
- log_warn() {
46
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WARN] $1" >> "$LOG_FILE"
47
- echo "[WARN] $1" >&2
48
- }
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; }
49
33
 
50
- log_error() {
51
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [ERROR] $1" >> "$LOG_FILE"
52
- echo "[ERROR] $1" >&2
53
- }
34
+ # ── prerequisites ─────────────────────────────────────────────────────────────
54
35
 
55
- # Check prerequisites
56
36
  check_prerequisites() {
57
- # Check jq
58
- if ! command -v jq &> /dev/null; then
59
- log_error "jq is not installed. Checkpoints disabled."
37
+ if ! command -v curl &>/dev/null; then
38
+ log_warn "curl not found. Checkpoints disabled."
60
39
  return 1
61
40
  fi
62
-
63
- # Check git
64
- if ! command -v git &> /dev/null; then
65
- log_error "git is not installed. Checkpoints disabled."
41
+ if ! command -v jq &>/dev/null; then
42
+ log_warn "jq not found. Checkpoints disabled."
66
43
  return 1
67
44
  fi
68
-
69
- # Check if we're in a git repo
70
- if ! git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree &> /dev/null; then
71
- log_warn "Not a git repository. Checkpoints disabled."
45
+ if [ -z "${ORCHESTRATOR_API_URL:-}" ]; then
46
+ log_debug "ORCHESTRATOR_API_URL not set. Checkpoints disabled."
72
47
  return 1
73
48
  fi
74
-
75
- # Check index file
76
- if [ ! -f "$INDEX_FILE" ]; then
77
- log_debug "No orchestrator-index.json found. Skipping."
49
+ if [ -z "${ORCHESTRATOR_PROJECT_TOKEN:-}" ]; then
50
+ log_debug "ORCHESTRATOR_PROJECT_TOKEN not set. Checkpoints disabled."
78
51
  return 1
79
52
  fi
80
-
81
53
  return 0
82
54
  }
83
55
 
84
- # Get current phase from orchestrator-index.json
85
- get_current_phase() {
86
- local phase
87
- phase=$(jq -r '.activeWorkflow.currentPhase // empty' "$INDEX_FILE" 2>/dev/null)
88
- echo "$phase"
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
89
85
  }
90
86
 
91
- # Get workflow ID
92
- get_workflow_id() {
93
- local wf_id
94
- wf_id=$(jq -r '.activeWorkflow.id // empty' "$INDEX_FILE" 2>/dev/null)
95
- echo "$wf_id"
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"
96
107
  }
97
108
 
98
- # Get workflow status
99
- get_workflow_status() {
100
- local status
101
- status=$(jq -r '.activeWorkflow.status // empty' "$INDEX_FILE" 2>/dev/null)
102
- echo "$status"
109
+ # ── dedup guard ────────────────────────────────────────────────────────────────
110
+
111
+ get_saved_last_phase() {
112
+ [ -f "$LAST_PHASE_FILE" ] && cat "$LAST_PHASE_FILE" || echo ""
103
113
  }
104
114
 
105
- # Get last checkpointed phase from checkpoints array (one with commitHash)
106
- get_last_checkpointed_phase() {
107
- local phase
108
- # Find the most recent checkpoint that has a commitHash (indicating it was git-committed)
109
- phase=$(jq -r '
110
- [.checkpoints[] | select(.commitHash != null and .commitHash != "")]
111
- | sort_by(.createdAt)
112
- | last
113
- | .phase // empty
114
- ' "$INDEX_FILE" 2>/dev/null)
115
- echo "$phase"
115
+ save_last_phase() {
116
+ echo "$1" > "$LAST_PHASE_FILE"
116
117
  }
117
118
 
118
- # Determine completed phase based on current phase
119
- # When we see currentPhase = PLAN, it means SPECIFY just completed
120
- get_completed_phase() {
121
- local current_phase="$1"
122
- local workflow_status="$2"
119
+ # ── phase inference ────────────────────────────────────────────────────────────
123
120
 
124
- case "$workflow_status" in
125
- "completed")
126
- echo "IMPLEMENT"
127
- return
128
- ;;
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 ;;
129
127
  esac
130
-
131
- case "$current_phase" in
132
- "PLAN")
133
- echo "SPECIFY"
134
- ;;
135
- "TASKS")
136
- echo "PLAN"
137
- ;;
138
- "IMPLEMENT")
139
- echo "TASKS"
140
- ;;
141
- *)
142
- echo ""
143
- ;;
128
+ case "${current,,}" in
129
+ "plan") echo "specify" ;;
130
+ "tasks") echo "plan" ;;
131
+ "implement") echo "tasks" ;;
132
+ *) echo "" ;;
144
133
  esac
145
134
  }
146
135
 
147
- # Create git checkpoint
148
- create_git_checkpoint() {
149
- local phase="$1"
150
- local commit_hash=""
151
-
152
- log_info "Creating git checkpoint for phase: $phase"
153
-
154
- # Stage all changes
155
- git -C "$PROJECT_ROOT" add -A 2>/dev/null || {
156
- log_warn "Failed to stage changes"
157
- return 1
158
- }
159
-
160
- # Check if there are staged changes
161
- if git -C "$PROJECT_ROOT" diff --cached --quiet 2>/dev/null; then
162
- log_info "No changes to commit for phase: $phase"
163
- echo ""
164
- return 0
165
- fi
166
-
167
- # Create commit with standard message
168
- local commit_msg="[orchestrator] ${phase}: Auto-checkpoint after ${phase} phase"
169
-
170
- if git -C "$PROJECT_ROOT" commit -m "$commit_msg" --no-verify 2>/dev/null; then
171
- commit_hash=$(git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null)
172
- log_info "Checkpoint commit created: $commit_hash"
173
- echo "$commit_hash"
174
- else
175
- log_warn "Failed to create commit"
176
- echo ""
177
- return 1
178
- fi
179
- }
180
-
181
- # Update orchestrator-index.json with checkpoint
182
- update_orchestrator_index() {
183
- local workflow_id="$1"
184
- local phase="$2"
185
- local commit_hash="$3"
186
- local created_at
187
-
188
- created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
189
- local checkpoint_id="cp_${workflow_id}_$(date +%s)"
190
- local description="Auto-checkpoint after ${phase} phase"
191
-
192
- log_debug "Updating index with checkpoint: $checkpoint_id"
193
-
194
- # Create temp file for atomic update
195
- local temp_file="${INDEX_FILE}.tmp.$$"
196
-
197
- # Use jq to add checkpoint and update statistics
198
- if jq --arg id "$checkpoint_id" \
199
- --arg wfId "$workflow_id" \
200
- --arg phase "$phase" \
201
- --arg hash "$commit_hash" \
202
- --arg date "$created_at" \
203
- --arg desc "$description" '
204
- # Add checkpoint to array
205
- .checkpoints += [{
206
- id: $id,
207
- workflowId: $wfId,
208
- phase: $phase,
209
- commitHash: $hash,
210
- createdAt: $date,
211
- description: $desc
212
- }] |
213
- # Update statistics
214
- .statistics.totalCheckpoints = (.checkpoints | length) |
215
- .statistics.lastActivity = $date
216
- ' "$INDEX_FILE" > "$temp_file" 2>/dev/null; then
217
-
218
- # Validate JSON
219
- if jq empty "$temp_file" 2>/dev/null; then
220
- mv "$temp_file" "$INDEX_FILE"
221
- log_info "Index updated with checkpoint: $checkpoint_id"
222
- return 0
223
- else
224
- log_error "Generated invalid JSON"
225
- rm -f "$temp_file"
226
- return 1
227
- fi
228
- else
229
- log_error "Failed to update index with jq"
230
- rm -f "$temp_file"
231
- return 1
232
- fi
233
- }
234
-
235
- # Save last checkpointed phase
236
- save_last_phase() {
237
- local phase="$1"
238
- echo "$phase" > "$LAST_PHASE_FILE"
239
- }
240
-
241
- # Get saved last phase (from state file, not index)
242
- get_saved_last_phase() {
243
- if [ -f "$LAST_PHASE_FILE" ]; then
244
- cat "$LAST_PHASE_FILE"
245
- else
246
- echo ""
247
- fi
248
- }
136
+ # ── main ───────────────────────────────────────────────────────────────────────
249
137
 
250
- # Main execution
251
138
  main() {
252
139
  log_debug "Hook triggered"
253
140
 
254
- # Check prerequisites
255
141
  if ! check_prerequisites; then
256
- log_debug "Prerequisites not met, exiting"
257
- exit 0 # Don't fail the hook
142
+ exit 0
258
143
  fi
259
144
 
260
- # Get current state
261
- local current_phase
262
- current_phase=$(get_current_phase)
145
+ local workflow_json
146
+ workflow_json=$(get_active_workflow 2>/dev/null) || workflow_json=""
263
147
 
264
- if [ -z "$current_phase" ]; then
265
- log_debug "No active workflow phase found"
148
+ if [ -z "$workflow_json" ]; then
149
+ log_debug "No active workflow found via API."
266
150
  exit 0
267
151
  fi
268
152
 
269
- local workflow_id
270
- workflow_id=$(get_workflow_id)
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 "")
271
157
 
272
- local workflow_status
273
- workflow_status=$(get_workflow_status)
274
-
275
- log_debug "Current phase: $current_phase, Status: $workflow_status, Workflow: $workflow_id"
158
+ if [ -z "$workflow_id" ] || [ -z "$current_phase" ]; then
159
+ log_debug "Could not parse workflow ID or phase."
160
+ exit 0
161
+ fi
276
162
 
277
- # Determine which phase just completed
278
163
  local completed_phase
279
164
  completed_phase=$(get_completed_phase "$current_phase" "$workflow_status")
280
165
 
281
166
  if [ -z "$completed_phase" ]; then
282
- log_debug "No completed phase detected from current phase: $current_phase"
167
+ log_debug "No completed phase inferred from current_phase=${current_phase}."
283
168
  exit 0
284
169
  fi
285
170
 
286
- # Check if we already checkpointed this phase
287
- local last_checkpointed
288
- last_checkpointed=$(get_last_checkpointed_phase)
289
-
290
- local saved_last_phase
291
- saved_last_phase=$(get_saved_last_phase)
292
-
293
- log_debug "Completed phase: $completed_phase, Last checkpointed: $last_checkpointed, Saved: $saved_last_phase"
294
-
295
- # Skip if already checkpointed
296
- if [ "$completed_phase" = "$last_checkpointed" ] || [ "$completed_phase" = "$saved_last_phase" ]; then
297
- log_debug "Phase $completed_phase already checkpointed, skipping"
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."
298
176
  exit 0
299
177
  fi
300
178
 
301
- # Create checkpoint
302
- local commit_hash
303
- commit_hash=$(create_git_checkpoint "$completed_phase")
179
+ local description="Auto-checkpoint after ${completed_phase} phase"
180
+ local http_code
181
+ http_code=$(create_checkpoint "$workflow_id" "$completed_phase" "$description")
304
182
 
305
- # Update index (even if no commit, to track the checkpoint)
306
- if [ -n "$commit_hash" ]; then
307
- if update_orchestrator_index "$workflow_id" "$completed_phase" "$commit_hash"; then
183
+ case "$http_code" in
184
+ 201|200)
308
185
  save_last_phase "$completed_phase"
309
- log_info "Checkpoint complete for phase: $completed_phase (commit: $commit_hash)"
310
- else
311
- log_error "Failed to update index for phase: $completed_phase"
312
- fi
313
- else
314
- log_info "No changes to checkpoint for phase: $completed_phase"
315
- save_last_phase "$completed_phase"
316
- fi
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
317
199
 
318
200
  exit 0
319
201
  }
320
202
 
321
- # Run main function
322
203
  main "$@"