@orchestrator-claude/definitions 3.8.2 → 3.9.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/agents/api-extractor.md +21 -35
- package/agents/business-rule-miner.md +22 -40
- package/agents/code-archaeologist.md +24 -43
- package/agents/docs-guardian.md +0 -32
- package/agents/implementer.md +37 -31
- package/agents/legacy-discoverer.md +26 -47
- package/agents/legacy-synthesizer.md +27 -47
- package/agents/orchestrator.md +40 -19
- package/agents/planner.md +16 -24
- package/agents/researcher.md +17 -36
- package/agents/reviewer.md +7 -26
- package/agents/schema-extractor.md +20 -34
- package/agents/specifier.md +16 -24
- package/agents/task-generator.md +16 -24
- package/hooks/post-agent-artifact-relay.sh +6 -152
- package/hooks/post-implement-validate.sh +76 -113
- package/hooks/post-phase-checkpoint.sh +86 -227
- package/hooks/track-agent-invocation.sh +59 -108
- package/manifest.json +76 -20
- package/package.json +1 -1
- package/skills/artifact-production/SKILL.md +39 -0
- package/skills/backlog-synthesis/SKILL.md +43 -0
- package/skills/checkpoint-protocol/SKILL.md +27 -0
- package/skills/kb-api-pattern/SKILL.md +39 -0
- package/skills/project-conventions/SKILL.md +34 -0
- package/skills/tdd-discipline/SKILL.md +40 -0
- package/skills/workflow-coordination/SKILL.md +46 -0
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Post-Phase Checkpoint Hook
|
|
3
|
-
#
|
|
2
|
+
# Post-Phase Checkpoint Hook (v2 — REST API)
|
|
3
|
+
# Defense-in-depth: agents create checkpoints via MCP, this is a safety net.
|
|
4
|
+
# Creates git checkpoints when workflow phases complete.
|
|
4
5
|
#
|
|
5
6
|
# Triggered by: PostToolUse on Task tool (after agent completes)
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Requirements:
|
|
9
|
-
# - jq (for JSON parsing)
|
|
10
|
-
# - git (for checkpoint commits)
|
|
11
|
-
#
|
|
7
|
+
# Requirements: git
|
|
12
8
|
# Debug mode: ORCH_CHECKPOINT_DEBUG=1 to enable verbose logging
|
|
13
9
|
|
|
14
10
|
set -e
|
|
@@ -16,18 +12,20 @@ set -e
|
|
|
16
12
|
# Configuration
|
|
17
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
14
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
19
|
-
INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
|
|
20
15
|
STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
|
|
21
16
|
LOG_FILE="$STATE_DIR/checkpoint-hook.log"
|
|
22
17
|
LAST_PHASE_FILE="$STATE_DIR/last-checkpointed-phase"
|
|
23
18
|
|
|
24
|
-
#
|
|
19
|
+
# API Configuration
|
|
20
|
+
API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
|
|
21
|
+
AUTH_EMAIL="${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}"
|
|
22
|
+
AUTH_PASSWORD="${ORCHESTRATOR_AUTH_PASSWORD:-admin123}"
|
|
23
|
+
PROJECT_ID="${ORCHESTRATOR_PROJECT_ID:-d001d65d-25f5-4d13-b1dc-d006873e949c}"
|
|
24
|
+
|
|
25
25
|
mkdir -p "$STATE_DIR"
|
|
26
26
|
|
|
27
|
-
# Debug mode
|
|
28
27
|
DEBUG="${ORCH_CHECKPOINT_DEBUG:-0}"
|
|
29
28
|
|
|
30
|
-
# Logging functions
|
|
31
29
|
log_debug() {
|
|
32
30
|
if [ "$DEBUG" = "1" ]; then
|
|
33
31
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [DEBUG] $1" >> "$LOG_FILE"
|
|
@@ -42,120 +40,57 @@ log_info() {
|
|
|
42
40
|
fi
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
log_warn() {
|
|
46
|
-
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WARN] $1" >> "$LOG_FILE"
|
|
47
|
-
echo "[WARN] $1" >&2
|
|
48
|
-
}
|
|
49
|
-
|
|
50
43
|
log_error() {
|
|
51
44
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [ERROR] $1" >> "$LOG_FILE"
|
|
52
45
|
echo "[ERROR] $1" >&2
|
|
53
46
|
}
|
|
54
47
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# Check git
|
|
64
|
-
if ! command -v git &> /dev/null; then
|
|
65
|
-
log_error "git is not installed. Checkpoints disabled."
|
|
66
|
-
return 1
|
|
67
|
-
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."
|
|
72
|
-
return 1
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
# Check index file
|
|
76
|
-
if [ ! -f "$INDEX_FILE" ]; then
|
|
77
|
-
log_debug "No orchestrator-index.json found. Skipping."
|
|
78
|
-
return 1
|
|
48
|
+
# Auth (shared with other hooks — cached JWT)
|
|
49
|
+
get_token() {
|
|
50
|
+
local cached="$STATE_DIR/jwt-token"
|
|
51
|
+
if [ -f "$cached" ]; then
|
|
52
|
+
local age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
|
|
53
|
+
if [ "$age" -lt 3500 ]; then
|
|
54
|
+
cat "$cached"; return
|
|
55
|
+
fi
|
|
79
56
|
fi
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
phase=$(jq -r '.activeWorkflow.currentPhase // empty' "$INDEX_FILE" 2>/dev/null)
|
|
88
|
-
echo "$phase"
|
|
89
|
-
}
|
|
90
|
-
|
|
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"
|
|
57
|
+
local resp
|
|
58
|
+
resp=$(curl -s -f -X POST "${API_URL}/api/v1/auth/login" \
|
|
59
|
+
-H "Content-Type: application/json" \
|
|
60
|
+
-d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
|
|
61
|
+
local token
|
|
62
|
+
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.data?.accessToken||j.accessToken||j.token||'')}catch{console.log('')}})" 2>/dev/null)
|
|
63
|
+
[ -n "$token" ] && { echo "$token" > "$cached"; echo "$token"; }
|
|
96
64
|
}
|
|
97
65
|
|
|
98
|
-
# Get workflow
|
|
99
|
-
|
|
100
|
-
local
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
# Get workflow info from REST API
|
|
67
|
+
get_workflow_info() {
|
|
68
|
+
local token
|
|
69
|
+
token=$(get_token) || return 1
|
|
70
|
+
curl -s -f "${API_URL}/api/v1/workflows?status=in_progress" \
|
|
71
|
+
-H "Authorization: Bearer $token" \
|
|
72
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null
|
|
103
73
|
}
|
|
104
74
|
|
|
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"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# Determine completed phase based on current phase
|
|
119
|
-
# When we see currentPhase = PLAN, it means SPECIFY just completed
|
|
75
|
+
# Determine completed phase from current phase
|
|
76
|
+
# When currentPhase = PLAN, SPECIFY just completed; etc.
|
|
120
77
|
get_completed_phase() {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
;;
|
|
129
|
-
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
|
-
;;
|
|
78
|
+
case "$1" in
|
|
79
|
+
plan|PLAN) echo "specify" ;;
|
|
80
|
+
tasks|TASKS) echo "plan" ;;
|
|
81
|
+
implement|IMPLEMENT) echo "tasks" ;;
|
|
82
|
+
completed) echo "implement" ;;
|
|
83
|
+
*) echo "" ;;
|
|
144
84
|
esac
|
|
145
85
|
}
|
|
146
86
|
|
|
147
87
|
# Create git checkpoint
|
|
148
88
|
create_git_checkpoint() {
|
|
149
89
|
local phase="$1"
|
|
150
|
-
local commit_hash=""
|
|
151
90
|
|
|
152
91
|
log_info "Creating git checkpoint for phase: $phase"
|
|
153
92
|
|
|
154
|
-
|
|
155
|
-
git -C "$PROJECT_ROOT" add -A 2>/dev/null || {
|
|
156
|
-
log_warn "Failed to stage changes"
|
|
157
|
-
return 1
|
|
158
|
-
}
|
|
93
|
+
git -C "$PROJECT_ROOT" add -A 2>/dev/null || return 1
|
|
159
94
|
|
|
160
95
|
# Check if there are staged changes
|
|
161
96
|
if git -C "$PROJECT_ROOT" diff --cached --quiet 2>/dev/null; then
|
|
@@ -164,159 +99,83 @@ create_git_checkpoint() {
|
|
|
164
99
|
return 0
|
|
165
100
|
fi
|
|
166
101
|
|
|
167
|
-
# Create commit with standard message
|
|
168
102
|
local commit_msg="[orchestrator] ${phase}: Auto-checkpoint after ${phase} phase"
|
|
169
103
|
|
|
170
104
|
if git -C "$PROJECT_ROOT" commit -m "$commit_msg" --no-verify 2>/dev/null; then
|
|
105
|
+
local commit_hash
|
|
171
106
|
commit_hash=$(git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null)
|
|
172
107
|
log_info "Checkpoint commit created: $commit_hash"
|
|
173
108
|
echo "$commit_hash"
|
|
174
109
|
else
|
|
175
|
-
|
|
110
|
+
log_error "Failed to create commit"
|
|
176
111
|
echo ""
|
|
177
112
|
return 1
|
|
178
113
|
fi
|
|
179
114
|
}
|
|
180
115
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
local
|
|
116
|
+
# Register checkpoint via REST API
|
|
117
|
+
register_checkpoint() {
|
|
118
|
+
local wf_id="$1"
|
|
184
119
|
local phase="$2"
|
|
185
120
|
local commit_hash="$3"
|
|
186
|
-
local
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
121
|
+
local description="$4"
|
|
122
|
+
local token
|
|
123
|
+
token=$(get_token) || return 1
|
|
124
|
+
curl -s -f -X POST "${API_URL}/api/v1/checkpoints" \
|
|
125
|
+
-H "Content-Type: application/json" \
|
|
126
|
+
-H "Authorization: Bearer $token" \
|
|
127
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
128
|
+
-d "{\"workflowId\":\"${wf_id}\",\"description\":\"${description}\",\"commitHash\":\"${commit_hash}\"}" >> "$LOG_FILE" 2>&1 || true
|
|
248
129
|
}
|
|
249
130
|
|
|
250
|
-
# Main execution
|
|
251
131
|
main() {
|
|
252
132
|
log_debug "Hook triggered"
|
|
253
133
|
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
exit 0 # Don't fail the hook
|
|
258
|
-
fi
|
|
134
|
+
# Prerequisites
|
|
135
|
+
command -v git &>/dev/null || { log_error "git not found"; exit 0; }
|
|
136
|
+
git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree &>/dev/null || { exit 0; }
|
|
259
137
|
|
|
260
|
-
# Get
|
|
261
|
-
|
|
262
|
-
current_phase=$(get_current_phase)
|
|
138
|
+
# Get workflow from API
|
|
139
|
+
WORKFLOW_RESP=$(get_workflow_info) || { log_debug "No API access"; exit 0; }
|
|
263
140
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
141
|
+
WORKFLOW_DATA=$(echo "$WORKFLOW_RESP" | node -e "
|
|
142
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
143
|
+
try{
|
|
144
|
+
const j=JSON.parse(d);
|
|
145
|
+
const wfs=j.data||j;
|
|
146
|
+
const wf=Array.isArray(wfs)?wfs[0]:wfs;
|
|
147
|
+
if(!wf){console.log('{}');return;}
|
|
148
|
+
console.log(JSON.stringify({id:wf.id||'',phase:wf.currentPhase||wf.phase||'',status:wf.status||''}));
|
|
149
|
+
}catch{console.log('{}')}
|
|
150
|
+
});
|
|
151
|
+
" 2>/dev/null)
|
|
268
152
|
|
|
269
|
-
|
|
270
|
-
|
|
153
|
+
WF_ID=$(echo "$WORKFLOW_DATA" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).id||'')}catch{console.log('')}})" 2>/dev/null)
|
|
154
|
+
CURRENT_PHASE=$(echo "$WORKFLOW_DATA" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).phase||'')}catch{console.log('')}})" 2>/dev/null)
|
|
271
155
|
|
|
272
|
-
|
|
273
|
-
workflow_status=$(get_workflow_status)
|
|
156
|
+
[ -z "$CURRENT_PHASE" ] && { log_debug "No active phase"; exit 0; }
|
|
274
157
|
|
|
275
|
-
|
|
158
|
+
COMPLETED_PHASE=$(get_completed_phase "$CURRENT_PHASE")
|
|
159
|
+
[ -z "$COMPLETED_PHASE" ] && { log_debug "No completed phase from: $CURRENT_PHASE"; exit 0; }
|
|
276
160
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
161
|
+
# Check if already checkpointed
|
|
162
|
+
SAVED=$(cat "$LAST_PHASE_FILE" 2>/dev/null || echo "")
|
|
163
|
+
[ "$COMPLETED_PHASE" = "$SAVED" ] && { log_debug "Already checkpointed: $COMPLETED_PHASE"; exit 0; }
|
|
280
164
|
|
|
281
|
-
|
|
282
|
-
log_debug "No completed phase detected from current phase: $current_phase"
|
|
283
|
-
exit 0
|
|
284
|
-
fi
|
|
165
|
+
log_debug "Current phase: $CURRENT_PHASE, Completed phase: $COMPLETED_PHASE, WF: $WF_ID"
|
|
285
166
|
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
last_checkpointed=$(get_last_checkpointed_phase)
|
|
167
|
+
# Create git checkpoint
|
|
168
|
+
COMMIT_HASH=$(create_git_checkpoint "$COMPLETED_PHASE")
|
|
289
169
|
|
|
290
|
-
|
|
291
|
-
|
|
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"
|
|
298
|
-
exit 0
|
|
299
|
-
fi
|
|
300
|
-
|
|
301
|
-
# Create checkpoint
|
|
302
|
-
local commit_hash
|
|
303
|
-
commit_hash=$(create_git_checkpoint "$completed_phase")
|
|
304
|
-
|
|
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
|
|
308
|
-
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
|
|
170
|
+
if [ -n "$COMMIT_HASH" ]; then
|
|
171
|
+
register_checkpoint "$WF_ID" "$COMPLETED_PHASE" "$COMMIT_HASH" "Auto-checkpoint after ${COMPLETED_PHASE} phase"
|
|
172
|
+
log_info "Checkpoint: phase=$COMPLETED_PHASE commit=$COMMIT_HASH"
|
|
313
173
|
else
|
|
314
|
-
log_info "No changes
|
|
315
|
-
save_last_phase "$completed_phase"
|
|
174
|
+
log_info "No changes for phase: $COMPLETED_PHASE"
|
|
316
175
|
fi
|
|
317
176
|
|
|
177
|
+
echo "$COMPLETED_PHASE" > "$LAST_PHASE_FILE"
|
|
318
178
|
exit 0
|
|
319
179
|
}
|
|
320
180
|
|
|
321
|
-
# Run main function
|
|
322
181
|
main "$@"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Track Agent Invocation Hook
|
|
3
|
-
#
|
|
2
|
+
# Track Agent Invocation Hook (v2 — REST API)
|
|
3
|
+
# Defense-in-depth: agents self-report via MCP, this hook is a safety net.
|
|
4
|
+
# Registers agent invocations via REST API (PostgreSQL).
|
|
4
5
|
#
|
|
5
6
|
# Usage (called by Claude Code hooks):
|
|
6
7
|
# track-agent-invocation.sh start (reads stdin JSON)
|
|
@@ -13,11 +14,16 @@ set -e
|
|
|
13
14
|
|
|
14
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
16
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
16
|
-
INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
|
|
17
17
|
STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
|
|
18
18
|
INVOCATION_FILE="$STATE_DIR/current-invocation"
|
|
19
19
|
DEBUG_LOG="$STATE_DIR/hook-debug.log"
|
|
20
20
|
|
|
21
|
+
# API Configuration
|
|
22
|
+
API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
|
|
23
|
+
AUTH_EMAIL="${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}"
|
|
24
|
+
AUTH_PASSWORD="${ORCHESTRATOR_AUTH_PASSWORD:-admin123}"
|
|
25
|
+
PROJECT_ID="${ORCHESTRATOR_PROJECT_ID:-d001d65d-25f5-4d13-b1dc-d006873e949c}"
|
|
26
|
+
|
|
21
27
|
# Ensure state directory exists
|
|
22
28
|
mkdir -p "$STATE_DIR"
|
|
23
29
|
|
|
@@ -32,6 +38,43 @@ if [ -f "$DEBUG_LOG" ]; then
|
|
|
32
38
|
fi
|
|
33
39
|
fi
|
|
34
40
|
|
|
41
|
+
log() {
|
|
42
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$DEBUG_LOG"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Auth function: login and cache JWT
|
|
46
|
+
get_token() {
|
|
47
|
+
local cached="$STATE_DIR/jwt-token"
|
|
48
|
+
if [ -f "$cached" ]; then
|
|
49
|
+
local age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
|
|
50
|
+
if [ "$age" -lt 3500 ]; then # Refresh if > ~58 min
|
|
51
|
+
cat "$cached"
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
local resp
|
|
56
|
+
resp=$(curl -s -f -X POST "${API_URL}/api/v1/auth/login" \
|
|
57
|
+
-H "Content-Type: application/json" \
|
|
58
|
+
-d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
|
|
59
|
+
local token
|
|
60
|
+
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.data?.accessToken||j.accessToken||j.token||'')}catch{console.log('')}})" 2>/dev/null)
|
|
61
|
+
if [ -n "$token" ]; then
|
|
62
|
+
echo "$token" > "$cached"
|
|
63
|
+
echo "$token"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Get active workflow ID from REST API
|
|
68
|
+
get_workflow_id() {
|
|
69
|
+
local token
|
|
70
|
+
token=$(get_token) || return 1
|
|
71
|
+
local resp
|
|
72
|
+
resp=$(curl -s -f "${API_URL}/api/v1/workflows?status=in_progress" \
|
|
73
|
+
-H "Authorization: Bearer $token" \
|
|
74
|
+
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || return 1
|
|
75
|
+
echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);const wfs=j.data||j;const wf=Array.isArray(wfs)?wfs[0]:wfs;console.log(wf?.id||'')}catch{console.log('')}})" 2>/dev/null
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
ACTION="${1:-}"
|
|
36
79
|
|
|
37
80
|
# Read stdin into variable (Claude Code passes JSON via stdin)
|
|
@@ -45,7 +88,6 @@ case "$ACTION" in
|
|
|
45
88
|
# Extract agent info from stdin JSON
|
|
46
89
|
# Structure: { "tool_name": "Task", "tool_input": { "subagent_type": "...", ... } }
|
|
47
90
|
if [ -n "$STDIN_DATA" ]; then
|
|
48
|
-
# Debug: log received data
|
|
49
91
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START stdin: $STDIN_DATA" >> "$DEBUG_LOG"
|
|
50
92
|
|
|
51
93
|
AGENT_NAME=$(echo "$STDIN_DATA" | node -e "
|
|
@@ -54,7 +96,6 @@ case "$ACTION" in
|
|
|
54
96
|
process.stdin.on('end', () => {
|
|
55
97
|
try {
|
|
56
98
|
const json = JSON.parse(data);
|
|
57
|
-
// Try different structures
|
|
58
99
|
const type = json.tool_input?.subagent_type
|
|
59
100
|
|| json.subagent_type
|
|
60
101
|
|| json.tool_input?.type
|
|
@@ -87,7 +128,6 @@ case "$ACTION" in
|
|
|
87
128
|
});
|
|
88
129
|
")
|
|
89
130
|
elif [ -n "$CLAUDE_TOOL_INPUT" ]; then
|
|
90
|
-
# Fallback to environment variable
|
|
91
131
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START env: $CLAUDE_TOOL_INPUT" >> "$DEBUG_LOG"
|
|
92
132
|
|
|
93
133
|
AGENT_NAME=$(echo "$CLAUDE_TOOL_INPUT" | node -e "
|
|
@@ -108,124 +148,35 @@ case "$ACTION" in
|
|
|
108
148
|
PHASE="unknown"
|
|
109
149
|
fi
|
|
110
150
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
114
|
-
|
|
115
|
-
# Get workflow ID from index
|
|
116
|
-
WORKFLOW_ID=""
|
|
117
|
-
if [ -f "$INDEX_FILE" ]; then
|
|
118
|
-
WORKFLOW_ID=$(node -e "
|
|
119
|
-
const fs = require('fs');
|
|
120
|
-
try {
|
|
121
|
-
const idx = JSON.parse(fs.readFileSync('$INDEX_FILE', 'utf8'));
|
|
122
|
-
console.log(idx.activeWorkflow?.id || 'wf-standalone-' + Date.now());
|
|
123
|
-
} catch { console.log('wf-standalone-' + Date.now()); }
|
|
124
|
-
")
|
|
125
|
-
fi
|
|
151
|
+
TOKEN=$(get_token) || { log "ERROR: Auth failed"; exit 0; }
|
|
152
|
+
WORKFLOW_ID=$(get_workflow_id) || { log "SKIP: No active workflow"; exit 0; }
|
|
126
153
|
|
|
127
|
-
|
|
154
|
+
STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
155
|
+
INVOCATION_ID="inv-$(date +%s)-$(head -c 4 /dev/urandom | xxd -p)"
|
|
128
156
|
echo "$INVOCATION_ID" > "$INVOCATION_FILE"
|
|
129
157
|
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!idx.agentInvocations) idx.agentInvocations = [];
|
|
138
|
-
|
|
139
|
-
idx.agentInvocations.push({
|
|
140
|
-
id: '$INVOCATION_ID',
|
|
141
|
-
agentName: '$AGENT_NAME',
|
|
142
|
-
phase: '$PHASE',
|
|
143
|
-
workflowId: '$WORKFLOW_ID',
|
|
144
|
-
startedAt: '$STARTED_AT',
|
|
145
|
-
completedAt: null,
|
|
146
|
-
status: 'running',
|
|
147
|
-
durationMs: 0
|
|
148
|
-
});
|
|
158
|
+
# Register via REST API
|
|
159
|
+
curl -s -f -X POST "${API_URL}/api/v1/agent-invocations" \
|
|
160
|
+
-H "Content-Type: application/json" \
|
|
161
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
162
|
+
-H "X-Project-ID: $PROJECT_ID" \
|
|
163
|
+
-d "{\"agentType\":\"${AGENT_NAME}\",\"workflowId\":\"${WORKFLOW_ID}\",\"phase\":\"${PHASE}\",\"startedAt\":\"${STARTED_AT}\"}" >> "$DEBUG_LOG" 2>&1 || true
|
|
149
164
|
|
|
150
|
-
|
|
151
|
-
console.log(JSON.stringify({ invocationId: '$INVOCATION_ID', startedAt: '$STARTED_AT' }));
|
|
152
|
-
"
|
|
153
|
-
else
|
|
154
|
-
echo '{"invocationId": "'$INVOCATION_ID'", "startedAt": "'$STARTED_AT'", "warning": "No index file found"}'
|
|
155
|
-
fi
|
|
165
|
+
log "START: agent=$AGENT_NAME phase=$PHASE workflow=$WORKFLOW_ID"
|
|
156
166
|
;;
|
|
157
167
|
|
|
158
168
|
complete)
|
|
159
|
-
# Get invocation ID from state file
|
|
160
169
|
INVOCATION_ID=""
|
|
161
170
|
if [ -f "$INVOCATION_FILE" ]; then
|
|
162
171
|
INVOCATION_ID=$(cat "$INVOCATION_FILE")
|
|
172
|
+
rm -f "$INVOCATION_FILE"
|
|
163
173
|
fi
|
|
164
174
|
|
|
165
|
-
if [ -z "$INVOCATION_ID" ]; then
|
|
166
|
-
echo '{"success": false, "error": "No invocation ID found"}'
|
|
167
|
-
exit 0 # Don't fail the hook
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# Debug log
|
|
171
175
|
if [ -n "$STDIN_DATA" ]; then
|
|
172
176
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] COMPLETE stdin: $STDIN_DATA" >> "$DEBUG_LOG"
|
|
173
177
|
fi
|
|
174
178
|
|
|
175
|
-
|
|
176
|
-
STATUS="success"
|
|
177
|
-
SUMMARY="Completed"
|
|
178
|
-
|
|
179
|
-
COMPLETED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
180
|
-
|
|
181
|
-
# Update orchestrator-index.json
|
|
182
|
-
if [ -f "$INDEX_FILE" ]; then
|
|
183
|
-
node -e "
|
|
184
|
-
const fs = require('fs');
|
|
185
|
-
const indexPath = '$INDEX_FILE';
|
|
186
|
-
const idx = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
187
|
-
|
|
188
|
-
const invocations = idx.agentInvocations || [];
|
|
189
|
-
const invIdx = invocations.findIndex(i => i.id === '$INVOCATION_ID');
|
|
190
|
-
|
|
191
|
-
if (invIdx === -1) {
|
|
192
|
-
console.log(JSON.stringify({ success: false, error: 'Invocation not found' }));
|
|
193
|
-
process.exit(0);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const inv = invocations[invIdx];
|
|
197
|
-
const startTime = new Date(inv.startedAt).getTime();
|
|
198
|
-
const endTime = new Date('$COMPLETED_AT').getTime();
|
|
199
|
-
const durationMs = endTime - startTime;
|
|
200
|
-
|
|
201
|
-
invocations[invIdx] = {
|
|
202
|
-
...inv,
|
|
203
|
-
completedAt: '$COMPLETED_AT',
|
|
204
|
-
status: '$STATUS' === 'success' ? 'completed' : 'failed',
|
|
205
|
-
durationMs,
|
|
206
|
-
result: {
|
|
207
|
-
status: '$STATUS',
|
|
208
|
-
summary: '$SUMMARY'
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
idx.agentInvocations = invocations;
|
|
213
|
-
|
|
214
|
-
// Update statistics
|
|
215
|
-
if (idx.statistics) {
|
|
216
|
-
idx.statistics.totalInvocations = invocations.length;
|
|
217
|
-
idx.statistics.lastActivity = '$COMPLETED_AT';
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
fs.writeFileSync(indexPath, JSON.stringify(idx, null, 2));
|
|
221
|
-
console.log(JSON.stringify({ success: true, durationMs }));
|
|
222
|
-
"
|
|
223
|
-
else
|
|
224
|
-
echo '{"success": false, "error": "No index file found"}'
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
# Clean up state file
|
|
228
|
-
rm -f "$INVOCATION_FILE"
|
|
179
|
+
log "COMPLETE: invocation=$INVOCATION_ID (agent self-reports via MCP; hook is safety net)"
|
|
229
180
|
;;
|
|
230
181
|
|
|
231
182
|
*)
|