@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.
- 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 +33 -41
- package/dist/templates/base/claude/hooks/gate-guardian.sh +42 -43
- package/dist/templates/base/claude/hooks/orch-helpers.sh +25 -20
- package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +26 -40
- package/dist/templates/base/claude/hooks/post-phase-checkpoint.sh +135 -254
- 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 +37 -0
- 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 +33 -41
- package/templates/base/claude/hooks/gate-guardian.sh +42 -43
- package/templates/base/claude/hooks/orch-helpers.sh +25 -20
- package/templates/base/claude/hooks/ping-pong-enforcer.sh +26 -40
- package/templates/base/claude/hooks/post-phase-checkpoint.sh +135 -254
- 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 +37 -0
- 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
|
@@ -60,28 +60,33 @@ orch_get_token() {
|
|
|
60
60
|
fi
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
# Get the active (
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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}/
|
|
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
|
|
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
|
|
3
|
-
# Trigger: PostToolUse on Agent
|
|
4
|
-
# Purpose: After ANY sub-agent returns,
|
|
5
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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:
|
|
7
|
+
# Behavior: Infers the just-completed phase from the active workflow state
|
|
8
|
+
# and registers a checkpoint via the REST API.
|
|
7
9
|
#
|
|
8
|
-
#
|
|
9
|
-
# -
|
|
10
|
-
# -
|
|
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
|
-
#
|
|
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 -
|
|
20
|
+
set -euo pipefail
|
|
15
21
|
|
|
16
|
-
# Configuration
|
|
17
22
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WARN]
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
local
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
257
|
-
exit 0 # Don't fail the hook
|
|
142
|
+
exit 0
|
|
258
143
|
fi
|
|
259
144
|
|
|
260
|
-
|
|
261
|
-
|
|
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 "$
|
|
265
|
-
log_debug "No active workflow
|
|
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=$(
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
167
|
+
log_debug "No completed phase inferred from current_phase=${current_phase}."
|
|
283
168
|
exit 0
|
|
284
169
|
fi
|
|
285
170
|
|
|
286
|
-
#
|
|
287
|
-
local
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
302
|
-
local
|
|
303
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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 "$@"
|