orchestrix 15.14.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/bin/o8x.js +91 -0
- package/lib/doctor.js +150 -0
- package/lib/embedded/commands.js +163 -0
- package/lib/embedded/config.js +74 -0
- package/lib/embedded/handoff-detector.sh +374 -0
- package/lib/embedded/hooks.js +21 -0
- package/lib/embedded/scripts.js +24 -0
- package/lib/embedded/start-orchestrix.sh +373 -0
- package/lib/install.js +240 -0
- package/lib/license.js +79 -0
- package/lib/mcp-client.js +111 -0
- package/lib/merge.js +200 -0
- package/lib/ui.js +109 -0
- package/lib/uninstall.js +57 -0
- package/lib/upgrade.js +19 -0
- package/package.json +31 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Orchestrix HANDOFF Detector - tmux Automation Hook (MCP Version)
|
|
3
|
+
# Triggers on Claude Code Stop event, detects HANDOFF and routes to target agent
|
|
4
|
+
#
|
|
5
|
+
# Design principles:
|
|
6
|
+
# - NO dependency on environment variables (robust)
|
|
7
|
+
# - Scans ALL tmux windows to find HANDOFF message
|
|
8
|
+
# - Uses hash-based deduplication to prevent re-processing
|
|
9
|
+
# - Background process handles cleanup and lock release
|
|
10
|
+
#
|
|
11
|
+
# Pro/Team Feature: This script is only available for Pro and Team subscribers.
|
|
12
|
+
|
|
13
|
+
set +e # Don't exit on errors
|
|
14
|
+
|
|
15
|
+
# ============================================
|
|
16
|
+
# Configuration
|
|
17
|
+
# ============================================
|
|
18
|
+
# Try to get session from env, otherwise detect from tmux context
|
|
19
|
+
SESSION_NAME="${ORCHESTRIX_SESSION:-}"
|
|
20
|
+
|
|
21
|
+
# Agent mappings
|
|
22
|
+
get_agent_name() {
|
|
23
|
+
case "$1" in
|
|
24
|
+
0) echo "architect" ;;
|
|
25
|
+
1) echo "sm" ;;
|
|
26
|
+
2) echo "dev" ;;
|
|
27
|
+
3) echo "qa" ;;
|
|
28
|
+
*) echo "" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get_window_num() {
|
|
33
|
+
case "$1" in
|
|
34
|
+
architect) echo "0" ;;
|
|
35
|
+
sm) echo "1" ;;
|
|
36
|
+
dev) echo "2" ;;
|
|
37
|
+
qa) echo "3" ;;
|
|
38
|
+
*) echo "" ;;
|
|
39
|
+
esac
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# MCP version: use /o {agent} format
|
|
43
|
+
get_agent_command() {
|
|
44
|
+
case "$1" in
|
|
45
|
+
architect) echo "/o architect" ;;
|
|
46
|
+
sm) echo "/o sm" ;;
|
|
47
|
+
dev) echo "/o dev" ;;
|
|
48
|
+
qa) echo "/o qa" ;;
|
|
49
|
+
*) echo "" ;;
|
|
50
|
+
esac
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Infer target agent from command (for simplified format like "*develop-story 10.4")
|
|
54
|
+
get_target_from_command() {
|
|
55
|
+
local cmd="$1"
|
|
56
|
+
case "$cmd" in
|
|
57
|
+
develop-story|apply-qa-fixes|quick-develop)
|
|
58
|
+
echo "dev" ;;
|
|
59
|
+
review|quick-verify|test-design|finalize-commit)
|
|
60
|
+
echo "qa" ;;
|
|
61
|
+
draft|revise-story|revise|apply-proposal|create-next-story)
|
|
62
|
+
echo "sm" ;;
|
|
63
|
+
review-escalation|resolve-change)
|
|
64
|
+
echo "architect" ;;
|
|
65
|
+
*)
|
|
66
|
+
echo "" ;;
|
|
67
|
+
esac
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ============================================
|
|
71
|
+
# Find Orchestrix Session (before any logging)
|
|
72
|
+
# ============================================
|
|
73
|
+
|
|
74
|
+
# Priority 1: Explicit env var
|
|
75
|
+
# Priority 2: Detect current tmux session (if inside tmux and it's an orchestrix session)
|
|
76
|
+
# Priority 3: Scan all tmux sessions for orchestrix prefix
|
|
77
|
+
if [[ -z "$SESSION_NAME" && -n "$TMUX" ]]; then
|
|
78
|
+
CURRENT_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
79
|
+
if [[ "$CURRENT_SESSION" == orchestrix* ]]; then
|
|
80
|
+
SESSION_NAME="$CURRENT_SESSION"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ -z "$SESSION_NAME" ]]; then
|
|
85
|
+
SESSION_NAME=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -E '^orchestrix' | head -1)
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ -z "$SESSION_NAME" ]]; then
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Session-specific log file (all logging starts AFTER session detection)
|
|
97
|
+
LOG_FILE="/tmp/${SESSION_NAME}-handoff.log"
|
|
98
|
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
|
|
99
|
+
|
|
100
|
+
log "========== Hook triggered =========="
|
|
101
|
+
log "Session: $SESSION_NAME"
|
|
102
|
+
|
|
103
|
+
# ============================================
|
|
104
|
+
# Scan All Windows for HANDOFF
|
|
105
|
+
# ============================================
|
|
106
|
+
PROCESSED_FILE="/tmp/${SESSION_NAME}-processed.txt"
|
|
107
|
+
touch "$PROCESSED_FILE"
|
|
108
|
+
|
|
109
|
+
SOURCE_WIN=""
|
|
110
|
+
TARGET=""
|
|
111
|
+
CMD=""
|
|
112
|
+
HANDOFF_HASH=""
|
|
113
|
+
|
|
114
|
+
for win in 0 1 2 3; do
|
|
115
|
+
OUTPUT=$(tmux capture-pane -t "$SESSION_NAME:$win" -p -S - 2>/dev/null)
|
|
116
|
+
[[ -z "$OUTPUT" ]] && continue
|
|
117
|
+
|
|
118
|
+
# Pattern 1: Standard HANDOFF format (🎯 HANDOFF TO agent: *command args)
|
|
119
|
+
LINE=$(echo "$OUTPUT" | grep -E '🎯.*HANDOFF.*TO' | tail -1)
|
|
120
|
+
if [[ -n "$LINE" ]]; then
|
|
121
|
+
# Calculate hash to avoid re-processing
|
|
122
|
+
HASH=$(echo "$LINE" | md5 2>/dev/null || echo "$LINE" | md5sum 2>/dev/null | cut -d' ' -f1)
|
|
123
|
+
|
|
124
|
+
# Skip if already processed
|
|
125
|
+
if grep -q "$HASH" "$PROCESSED_FILE" 2>/dev/null; then
|
|
126
|
+
continue
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Parse HANDOFF message
|
|
130
|
+
if [[ "$LINE" =~ HANDOFF[[:space:]]+TO[[:space:]]+([a-zA-Z0-9_-]+):[[:space:]]*\*?([a-z0-9-]+)([[:space:]]+([0-9]+\.[0-9]+))? ]]; then
|
|
131
|
+
SOURCE_WIN=$win
|
|
132
|
+
TARGET=$(echo "${BASH_REMATCH[1]}" | tr '[:upper:]' '[:lower:]')
|
|
133
|
+
CMD="*${BASH_REMATCH[2]}${BASH_REMATCH[4]:+ ${BASH_REMATCH[4]}}"
|
|
134
|
+
HANDOFF_HASH=$HASH
|
|
135
|
+
log "Found HANDOFF in window $win: $LINE"
|
|
136
|
+
break
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Pattern 2: Simplified format - more flexible detection
|
|
141
|
+
# Handles: "*develop-story 10.4", "*draft", "* review 10.4", etc.
|
|
142
|
+
if [[ -z "$TARGET" ]]; then
|
|
143
|
+
# Search last 30 lines for command patterns (more tolerant)
|
|
144
|
+
LAST_LINES=$(echo "$OUTPUT" | tail -30)
|
|
145
|
+
|
|
146
|
+
# Try multiple patterns in order of specificity
|
|
147
|
+
# Pattern 2a: *command story_id (e.g., "*develop-story 10.4")
|
|
148
|
+
SIMPLE_LINE=$(echo "$LAST_LINES" | grep -E '\*[a-z]+-?[a-z-]*\s+[0-9]+\.[0-9]+' | tail -1)
|
|
149
|
+
|
|
150
|
+
if [[ -z "$SIMPLE_LINE" ]]; then
|
|
151
|
+
# Pattern 2b: *command without story_id (e.g., "*draft")
|
|
152
|
+
SIMPLE_LINE=$(echo "$LAST_LINES" | grep -E '^\s*\*[a-z]+-?[a-z-]*\s*$' | tail -1)
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if [[ -n "$SIMPLE_LINE" ]]; then
|
|
156
|
+
# Extract command and optional story_id
|
|
157
|
+
# Handle: "*develop-story 10.4", "* review 10.4", "*draft"
|
|
158
|
+
if [[ "$SIMPLE_LINE" =~ \*[[:space:]]*([a-z][a-z-]*)[[:space:]]*([0-9]+\.[0-9]+)? ]]; then
|
|
159
|
+
simple_cmd="${BASH_REMATCH[1]}"
|
|
160
|
+
story_id="${BASH_REMATCH[2]}"
|
|
161
|
+
inferred_target=$(get_target_from_command "$simple_cmd")
|
|
162
|
+
|
|
163
|
+
if [[ -n "$inferred_target" ]]; then
|
|
164
|
+
# Calculate hash
|
|
165
|
+
HASH=$(echo "$SIMPLE_LINE" | md5 2>/dev/null || echo "$SIMPLE_LINE" | md5sum 2>/dev/null | cut -d' ' -f1)
|
|
166
|
+
|
|
167
|
+
# Skip if already processed
|
|
168
|
+
if grep -q "$HASH" "$PROCESSED_FILE" 2>/dev/null; then
|
|
169
|
+
continue
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
SOURCE_WIN=$win
|
|
173
|
+
TARGET=$inferred_target
|
|
174
|
+
if [[ -n "$story_id" ]]; then
|
|
175
|
+
CMD="*${simple_cmd} ${story_id}"
|
|
176
|
+
else
|
|
177
|
+
CMD="*${simple_cmd}"
|
|
178
|
+
fi
|
|
179
|
+
HANDOFF_HASH=$HASH
|
|
180
|
+
log "[SIMPLE] Found in window $win: '$SIMPLE_LINE' -> $TARGET: $CMD"
|
|
181
|
+
break
|
|
182
|
+
fi
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
fi
|
|
186
|
+
done
|
|
187
|
+
|
|
188
|
+
# No HANDOFF found - try fallback from pending-handoff file
|
|
189
|
+
if [[ -z "$TARGET" || -z "$CMD" ]]; then
|
|
190
|
+
log "No HANDOFF in terminal output, checking fallback file..."
|
|
191
|
+
|
|
192
|
+
# ============================================
|
|
193
|
+
# Fallback: Check pending-handoff.json
|
|
194
|
+
# ============================================
|
|
195
|
+
# Find project root (look for .orchestrix-core directory)
|
|
196
|
+
FALLBACK_FILE=""
|
|
197
|
+
for win in 0 1 2 3; do
|
|
198
|
+
# Get the pane's current directory
|
|
199
|
+
PANE_DIR=$(tmux display-message -t "$SESSION_NAME:$win" -p '#{pane_current_path}' 2>/dev/null)
|
|
200
|
+
if [[ -n "$PANE_DIR" && -f "$PANE_DIR/.orchestrix-core/runtime/pending-handoff.json" ]]; then
|
|
201
|
+
FALLBACK_FILE="$PANE_DIR/.orchestrix-core/runtime/pending-handoff.json"
|
|
202
|
+
SOURCE_WIN=$win
|
|
203
|
+
break
|
|
204
|
+
fi
|
|
205
|
+
done
|
|
206
|
+
|
|
207
|
+
if [[ -z "$FALLBACK_FILE" ]]; then
|
|
208
|
+
log "No pending-handoff.json found"
|
|
209
|
+
exit 0
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
log "Found fallback file: $FALLBACK_FILE"
|
|
213
|
+
|
|
214
|
+
# Read and parse the JSON file
|
|
215
|
+
if command -v jq &>/dev/null; then
|
|
216
|
+
# Use jq if available
|
|
217
|
+
STATUS=$(jq -r '.status // "unknown"' "$FALLBACK_FILE" 2>/dev/null)
|
|
218
|
+
if [[ "$STATUS" != "pending" ]]; then
|
|
219
|
+
log "Fallback file status is '$STATUS', not 'pending'. Skipping."
|
|
220
|
+
exit 0
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
TARGET=$(jq -r '.target_agent // ""' "$FALLBACK_FILE" 2>/dev/null)
|
|
224
|
+
CMD=$(jq -r '.command // ""' "$FALLBACK_FILE" 2>/dev/null)
|
|
225
|
+
STORY_ID=$(jq -r '.story_id // ""' "$FALLBACK_FILE" 2>/dev/null)
|
|
226
|
+
SOURCE_AGENT=$(jq -r '.source_agent // ""' "$FALLBACK_FILE" 2>/dev/null)
|
|
227
|
+
else
|
|
228
|
+
# Fallback to grep/sed parsing
|
|
229
|
+
STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$FALLBACK_FILE" | sed 's/.*"\([^"]*\)"$/\1/')
|
|
230
|
+
if [[ "$STATUS" != "pending" ]]; then
|
|
231
|
+
log "Fallback file status is '$STATUS', not 'pending'. Skipping."
|
|
232
|
+
exit 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
TARGET=$(grep -o '"target_agent"[[:space:]]*:[[:space:]]*"[^"]*"' "$FALLBACK_FILE" | sed 's/.*"\([^"]*\)"$/\1/')
|
|
236
|
+
CMD=$(grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' "$FALLBACK_FILE" | sed 's/.*"\([^"]*\)"$/\1/')
|
|
237
|
+
STORY_ID=$(grep -o '"story_id"[[:space:]]*:[[:space:]]*"[^"]*"' "$FALLBACK_FILE" | sed 's/.*"\([^"]*\)"$/\1/')
|
|
238
|
+
SOURCE_AGENT=$(grep -o '"source_agent"[[:space:]]*:[[:space:]]*"[^"]*"' "$FALLBACK_FILE" | sed 's/.*"\([^"]*\)"$/\1/')
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
if [[ -z "$TARGET" || -z "$CMD" ]]; then
|
|
242
|
+
log "Invalid fallback file content"
|
|
243
|
+
exit 0
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Create a hash to prevent re-processing
|
|
247
|
+
# Include STORY_ID to differentiate handoffs for different stories with same command
|
|
248
|
+
HANDOFF_HASH=$(echo "fallback-$SOURCE_AGENT-$TARGET-$CMD-$STORY_ID" | md5 2>/dev/null || echo "fallback-$SOURCE_AGENT-$TARGET-$CMD-$STORY_ID" | md5sum 2>/dev/null | cut -d' ' -f1)
|
|
249
|
+
|
|
250
|
+
# Skip if already processed
|
|
251
|
+
if grep -q "$HANDOFF_HASH" "$PROCESSED_FILE" 2>/dev/null; then
|
|
252
|
+
log "Fallback handoff already processed"
|
|
253
|
+
exit 0
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Get SOURCE_WIN from SOURCE_AGENT (override the window where file was found)
|
|
257
|
+
SOURCE_WIN=$(get_window_num "$SOURCE_AGENT")
|
|
258
|
+
|
|
259
|
+
log "[FALLBACK] Recovered handoff from file: $SOURCE_AGENT (win $SOURCE_WIN) -> $TARGET: $CMD"
|
|
260
|
+
|
|
261
|
+
# Mark the fallback file as completed
|
|
262
|
+
if command -v jq &>/dev/null; then
|
|
263
|
+
jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.status = "completed_by_fallback" | .completed_at = $ts' "$FALLBACK_FILE" > "$FALLBACK_FILE.tmp.$$" 2>/dev/null && mv "$FALLBACK_FILE.tmp.$$" "$FALLBACK_FILE"
|
|
264
|
+
else
|
|
265
|
+
sed -i.bak 's/"status"[[:space:]]*:[[:space:]]*"pending"/"status": "completed_by_fallback"/' "$FALLBACK_FILE" 2>/dev/null
|
|
266
|
+
rm -f "$FALLBACK_FILE.bak" 2>/dev/null
|
|
267
|
+
fi
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# Mark as processed
|
|
271
|
+
echo "$HANDOFF_HASH" >> "$PROCESSED_FILE"
|
|
272
|
+
|
|
273
|
+
# Keep processed file small (last 100 entries)
|
|
274
|
+
# Use PID in tmp filename to avoid race with concurrent hook instances or background processes
|
|
275
|
+
tail -100 "$PROCESSED_FILE" > "$PROCESSED_FILE.tmp.$$" 2>/dev/null && mv "$PROCESSED_FILE.tmp.$$" "$PROCESSED_FILE"
|
|
276
|
+
|
|
277
|
+
# Get source agent name (only if not already set by fallback)
|
|
278
|
+
if [[ -z "$SOURCE_AGENT" ]]; then
|
|
279
|
+
SOURCE_AGENT=$(get_agent_name "$SOURCE_WIN")
|
|
280
|
+
fi
|
|
281
|
+
TARGET_WIN=$(get_window_num "$TARGET")
|
|
282
|
+
|
|
283
|
+
# Validate
|
|
284
|
+
if [[ -z "$TARGET_WIN" ]]; then
|
|
285
|
+
log "ERROR: Unknown target agent '$TARGET'"
|
|
286
|
+
exit 0
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
if [[ "$TARGET_WIN" == "$SOURCE_WIN" ]]; then
|
|
290
|
+
log "ERROR: Source and target are same window"
|
|
291
|
+
exit 0
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
log "HANDOFF: $SOURCE_AGENT (win $SOURCE_WIN) -> $TARGET (win $TARGET_WIN)"
|
|
295
|
+
log "Command: $CMD"
|
|
296
|
+
|
|
297
|
+
# ============================================
|
|
298
|
+
# Atomic Lock
|
|
299
|
+
# ============================================
|
|
300
|
+
LOCK="/tmp/${SESSION_NAME}-${SOURCE_WIN}.lock"
|
|
301
|
+
LOCK_TIMEOUT=60
|
|
302
|
+
|
|
303
|
+
if ! mkdir "$LOCK" 2>/dev/null; then
|
|
304
|
+
if [[ -f "$LOCK/ts" ]]; then
|
|
305
|
+
ts=$(cat "$LOCK/ts" 2>/dev/null || echo 0)
|
|
306
|
+
now=$(date +%s)
|
|
307
|
+
age=$((now - ts))
|
|
308
|
+
if [[ $age -lt $LOCK_TIMEOUT ]]; then
|
|
309
|
+
log "SKIP: Window $SOURCE_WIN locked (${age}s ago)"
|
|
310
|
+
exit 0
|
|
311
|
+
fi
|
|
312
|
+
log "Stale lock (${age}s), cleaning"
|
|
313
|
+
fi
|
|
314
|
+
rm -rf "$LOCK" 2>/dev/null
|
|
315
|
+
mkdir "$LOCK" 2>/dev/null || { log "SKIP: lock race"; exit 0; }
|
|
316
|
+
fi
|
|
317
|
+
date +%s > "$LOCK/ts"
|
|
318
|
+
|
|
319
|
+
# ============================================
|
|
320
|
+
# Send to Target
|
|
321
|
+
# ============================================
|
|
322
|
+
log "Sending '$CMD' to $TARGET (window $TARGET_WIN)..."
|
|
323
|
+
|
|
324
|
+
if tmux send-keys -t "$SESSION_NAME:$TARGET_WIN" "$CMD" 2>/dev/null; then
|
|
325
|
+
sleep 0.5
|
|
326
|
+
tmux send-keys -t "$SESSION_NAME:$TARGET_WIN" Enter
|
|
327
|
+
log "SUCCESS: Command sent to $TARGET"
|
|
328
|
+
else
|
|
329
|
+
log "ERROR: Failed to send command"
|
|
330
|
+
rm -rf "$LOCK"
|
|
331
|
+
exit 0
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# ============================================
|
|
335
|
+
# Background: Clear & Reload Source Agent
|
|
336
|
+
# ============================================
|
|
337
|
+
RELOAD_CMD=$(get_agent_command "$SOURCE_AGENT")
|
|
338
|
+
|
|
339
|
+
(
|
|
340
|
+
log "[BG] Starting cleanup for $SOURCE_AGENT (window $SOURCE_WIN)"
|
|
341
|
+
sleep 2
|
|
342
|
+
|
|
343
|
+
# Clear
|
|
344
|
+
tmux send-keys -t "$SESSION_NAME:$SOURCE_WIN" "/clear" 2>/dev/null
|
|
345
|
+
sleep 0.5
|
|
346
|
+
tmux send-keys -t "$SESSION_NAME:$SOURCE_WIN" Enter
|
|
347
|
+
log "[BG] /clear sent to $SOURCE_AGENT"
|
|
348
|
+
|
|
349
|
+
sleep 5
|
|
350
|
+
|
|
351
|
+
# Reload
|
|
352
|
+
if [[ -n "$RELOAD_CMD" ]]; then
|
|
353
|
+
tmux send-keys -t "$SESSION_NAME:$SOURCE_WIN" "$RELOAD_CMD" 2>/dev/null
|
|
354
|
+
sleep 0.5
|
|
355
|
+
tmux send-keys -t "$SESSION_NAME:$SOURCE_WIN" Enter
|
|
356
|
+
log "[BG] Reload sent: $RELOAD_CMD"
|
|
357
|
+
sleep 15
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
# Remove hash from processed file to allow future same-message HANDOFF
|
|
361
|
+
# This fixes the issue where repeated identical messages (e.g., "*draft") are skipped
|
|
362
|
+
if [[ -n "$HANDOFF_HASH" && -f "$PROCESSED_FILE" ]]; then
|
|
363
|
+
grep -v "^${HANDOFF_HASH}$" "$PROCESSED_FILE" > "$PROCESSED_FILE.tmp.$$" 2>/dev/null && mv -f "$PROCESSED_FILE.tmp.$$" "$PROCESSED_FILE"
|
|
364
|
+
log "[BG] Hash removed from processed file: $HANDOFF_HASH"
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
# Release lock
|
|
368
|
+
rm -rf "$LOCK"
|
|
369
|
+
log "[BG] Cleanup complete, lock released"
|
|
370
|
+
) >> "$LOG_FILE" 2>&1 &
|
|
371
|
+
|
|
372
|
+
log "Background process started (PID $!)"
|
|
373
|
+
log "========== Hook complete =========="
|
|
374
|
+
exit 0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Embedded settings.local.json template for Claude Code Stop hooks
|
|
4
|
+
|
|
5
|
+
const SETTINGS_LOCAL_TEMPLATE = {
|
|
6
|
+
hooks: {
|
|
7
|
+
Stop: [
|
|
8
|
+
{
|
|
9
|
+
matcher: '',
|
|
10
|
+
hooks: [
|
|
11
|
+
{
|
|
12
|
+
type: 'command',
|
|
13
|
+
command: "bash -c 'cd \"$(git rev-parse --show-toplevel)\" && .orchestrix-core/scripts/handoff-detector.sh'",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = { SETTINGS_LOCAL_TEMPLATE };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Shell scripts are loaded from sibling .sh files at runtime
|
|
7
|
+
// This avoids embedding 400+ line scripts as JS strings
|
|
8
|
+
|
|
9
|
+
function loadScript(name) {
|
|
10
|
+
const scriptPath = path.join(__dirname, name);
|
|
11
|
+
if (fs.existsSync(scriptPath)) {
|
|
12
|
+
return fs.readFileSync(scriptPath, 'utf-8');
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Embedded script not found: ${name}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
getStartScript() {
|
|
19
|
+
return loadScript('start-orchestrix.sh');
|
|
20
|
+
},
|
|
21
|
+
getHandoffScript() {
|
|
22
|
+
return loadScript('handoff-detector.sh');
|
|
23
|
+
},
|
|
24
|
+
};
|